Multi-ROI 转 Multi-Mesh(表面确定)
Multi-ROI to Multi-Mesh (Surface Determination) - User Manual
Dragonfly Prototype Apps · Multi-ROI to Multi-Mesh (Surface Determination)...
版本 Version 1.4 · 2026-09-10
第一部分 中文手册
目录
1. 简介
2. 按当前设置执行全部步骤(无人值守)
3. 这里所说的“表面确定”是什么
4. 两种生成表面的方法,以及如何比较它们
5. 速度:哪些快、哪些不快,以及唯一的那个开关
6. 相互接触的类别无法由灰度确定
7. 运行报告些什么,“体积”又意味着什么
8. 测量,以及把结果回填到 Multi-ROI
9. 发布,以及发布前的核对
10. 第 6 个标签页:报告
11. 运行要求与已知限制
1. 简介
两个输入,都必须已经发布在会话中:一个图像通道和一个 Multi-ROI。对 Multi-ROI 的每一个类别,插件把它单独提取为一个 ROI,并用两种方法中的一种或两种在其外围生成表面——“表面确定”会用图像灰度对表面进行精修,而 Dragonfly 自带的 marching cubes 不会——最后把各类别的表面按方法各自合并为一个 Multi-Mesh。
Multi-Mesh 不是一种独立的对象类型:它就是一个 Mesh,每个类别对应其中一个标签,与 Multi-ROI 每个类别一个标签完全一致。每个标签沿用该类别在 Multi-ROI 中的名称与颜色,发布后的对象会自动切换为按标签着色,便于在三维视图中区分各类别。
六个标签页就是工作流,顺序即工作顺序:1 输入、2 网格生成、3 运行、4 测量、5 发布、6 报告。它们从一开始就全部可用、可编辑,因此你可以先把每个标签页都设好,再按“按当前设置执行全部步骤”;前一步尚未产出结果时,按下该步骤自己的按钮会被拒绝,并告诉你缺的是哪一步。只有“发布”和“回填到 Multi-ROI”会在会话中创建或修改对象,只有“报告”标签页会写出自己的文件。
2. 按当前设置执行全部步骤(无人值守)
第一个标签页上有一个大按钮:按当前设置执行全部步骤。它依次执行标签页 1 到 6——读取、生成表面、测量、把测量结果写入 Multi-ROI、发布、写出报告——每一步都使用该标签页此刻显示的设置。先在其他标签页设好参数,按下按钮即可离开;几小时后结果就在会话中,报告也已写到磁盘上。
结果文件。按钮上方有一个 .csv 路径输入框(完整路径;可选)。它在流程开始“之前”就会被检查——不是 .csv、是一个文件夹、或者所在文件夹无法写入,按钮都会保持禁用并显示原因——因为流程结束时没有人在电脑前。第 6 个标签页的报告文件也在同一时刻、用同样的方式检查。流程结束后,插件会为每个类别的每种网格生成方法各写一行,运行列(体素数、顶点数、面数、面积、体积、欧拉数、是否精修、秒数)和测量列(半径、平均曲率、两种积分平均曲率、比值、是否闭合)并排,单位为 Dragonfly 自己的单位;同时在旁边写一份带时间戳的日志 <名称>_log.txt,记录每一条状态信息。因此即使你回来之前 Dragonfly 已经关闭,数据也不会丢。
没有事可做的步骤会被跳过并写入日志,而不算失败——例如在没有平均曲率功能的版本上,测量步骤仍会写出面积和精确积分。某一步失败、运行没有生成任何表面、或者在“运行”/“测量”标签页按了取消,流程都会结束,并在第一个标签页说明在哪一步、为什么。
3. 这里所说的“表面确定”是什么
每个类别分两步,二者回答的问题并不相同。
- 初始表面是围绕该类别的体素生成的,因此它只能落在体素面上——最好的情况也是一个台阶为一个体素的阶梯面。
- 精修把每个顶点沿其法向移动到搜索距离内灰度梯度最强的位置。这一步才让表面落到体素之间,也正是它使结果成为对样品的测量,而不只是对分割结果的呈现。
参数与 Dragonfly 自带的 Surface Determination 完全一致,默认值也取自该工具(采样 1/1/1、松弛 1.0、衰减 0.5),熟悉那个工具的用户在这里会得到同样的第一个结果。
搜索距离以体素输入,界面同时打印换算出的长度。换算使用三个方向中最小的体素间距:搜索距离一旦超过最细的结构尺度,顶点就可能越过本该吸附的边界,吸附到另一条边界上。
4. 两种生成表面的方法,以及如何比较它们
2 网格生成标签页并列提供两种方法。可以只勾选一种,也可以两种都勾。
- 表面确定:吸附到灰度值上。先围绕该类别的体素生成表面,再把它吸附到图像的灰度梯度上,这一步才使表面落在体素之间。这就是亚体素方法,该标签页下面的所有参数都是在描述它。
- Marching cubes:Dragonfly 自带的 ROI 转网格,不吸附。这就是 Dragonfly 自带的 ROI 转网格功能,它本身就是 marching cubes。表面沿体素边界走,完全不参考灰度值,因此它是“基准”。采样、填充内部空洞和平滑对它有效;搜索距离、松弛、衰减以及 Poisson 重划分对它无效。
两种都勾选时,每个类别会生成两个表面,后续的一切都按“每个类别每种方法一行”给出,并用方法列加以区分:运行表格(合计也按方法分别给出)、测量表格、.csv 以及报告。这样两者的差异就是一个数字,而不是一种印象——可以直接比较同一类别的面积或积分平均曲率。
测量后缀。每种方法在第 2 个标签页都有自己的文本框,默认分别为-fromSurfaceMesh 和 -fromMarchingMesh,都可以自行修改。“回填到 Multi-ROI”写入测量结果时,每个名称都会带上这个后缀,因此由网格测得的数值永远不会被误认为由体素测得的;两种方法的数值也会写入不同的槽,而不是互相覆盖。
⚠ 后缀不能为空;两种方法都勾选时两个后缀也不能相同:运行会在生成任何表面之前被拒绝,并说明原因。
两种方法一起发布会产生两个对象——每种方法一个——并且每个对象的名称中都带有它的方法,例如“Multi-Mesh of segmentation (marching cubes)”,因为对象列表是唯一能区分它们的地方。
5. 速度:哪些快、哪些不快,以及唯一的那个开关
现在读取很快。 点击“读取”时,插件直接向 Dragonfly 索取每个类别的体素数和包围盒,并且只读取一次标签体数据、不做复制。在 256x256x256、含 300 个类别的 Multi-ROI 上,整个读取约需 0.08 秒。早期版本会在开始工作之前把整个标签体数据复制两次,小样品要几秒,大样品要几分钟。输入选项卡会打印读取耗时以及这些数字的来源,因此你可以看到你的版本走的是哪条路径。
运行不会冻结 Dragonfly。 网格生成步骤在插件自己的工作线程上运行,因此主窗口保持响应,也不会再每处理一个类别就变灰一次。它们有意逐个类别串行执行:实测表明,Dragonfly 的网格生成调用在两个同时运行时会返回错误的数值,因此插件不会这样做。插件自身的计算——接触检查和测量——已经在使用所有核心。
快速初始表面(原生 marching cubes)。 第 2 个选项卡上的这个勾选框,是长时间运行时唯一真正的提速手段。它让待确定的表面从 Dragonfly 自带的 ROI 转网格转换出发,而不是从表面确定步骤出发,随后仍然完全照旧吸附到灰度上。在 320x320x320 体数据的 40 个类别上实测:0.41 秒而不是 4.53 秒,快 11 倍。
它默认关闭,因为它给出的并不是同一个表面。每个类别的顶点数和面数都相同,但吸附之后两个表面平均相距约 0.12 个体素——单个顶点最差 1.8 个体素——体积相差约百分之一。作为参照,吸附本身会把 Dragonfly 自己的表面移动约 0.15 个体素。所以这是真实的差异,而不是舍入误差。当你需要让涉及数百个类别的运行跑完时使用它;当数值必须与 Dragonfly 自带的表面确定结果一致时,请保持关闭。
进度信息会合并显示。 涉及一千个类别的运行会为每个类别输出一行。这些行大约每秒分十二批显示,而不是每行重绘一次,因此日志保持可读,面板也不会闪烁。没有任何一行会丢失。
6. 相互接触的类别无法由灰度确定
⚠ 两个类别之间的共享边界是人为划分出来的,不是灰度边缘。精修在那里没有可吸附的目标,两个表面可能重叠,也可能留下缝隙。
因此“输入”标签页会在运行前实测这一点:统计每一对类别共享多少个体素面(只计 6 邻接——只在角点相遇的两个类别并不共享表面)。结果按接触面积列出,判断权在你,而且这个判断是基于数字的。
- 没有任何一对接触——每个表面都与背景相邻,全部可由图像确定。这正是本插件最适用的情形。
- 存在接触——远离共享边界的部分仍然被正确确定,共享部分的精度则取决于分割本身。关闭精修会让所有表面都退回体素边界,至少是自洽的。
对非常大的 Multi-ROI,接触统计会按步长抽样并明确说明,此时给出的数字只是下限。
7. 运行报告些什么,“体积”又意味着什么
运行按类别逐个进行,可以在类别之间取消。每个类别报告顶点数、面数、面积、体积、欧拉数和耗时,并说明实际使用了哪条内部路径。
⚠ 只有封闭表面才有体积。欧拉数为 2 表示单个封闭曲面;其他数值意味着该行的“体积”并不是体积。插件选择把数字显示出来,并在旁边给出欧拉数,由你判断哪些行可信,而不是把它藏起来。
面积和体积使用 Dragonfly 自身的单位——它以米存储世界坐标,因此两列分别是 m² 与 m³。本插件任何地方都不做预先缩放。
某个类别生成失败不会终止整次运行,也不会消失:它会作为一行出现,并写明原因。
8. 测量,以及把结果回填到 Multi-ROI
“Measurements(测量)”标签页对运行得到的表面进行测量。逐顶点(per-vertex)的结果是分布在每个表面上的一个场;逐标签(per-label)的结果是整个类别的一个数值,只有后者才能回填到 Multi-ROI。测量本身不创建也不修改任何对象。
平均曲率用的是 Dragonfly 自己的算法:本标签页调用 MeshHelper.computeCurvatureAsVertexScalarValues,与厂商的 Compute Mean Curvature 是同一个入口,然后从它写入的顶点标量槽中把数值读回来。厂商计算出的是一个“测度”,不是曲率:它在每个顶点周围,把落在给定半径球内的每段边长乘以该边的二面角再除以二后求和 —— 所以它的单位是长度。本标签页把它除以球截出的面片面积,得到单位为 1/长度 的曲率;这次相除是本插件中唯一的近似。
半径。自动半径为包围盒对角线除以 240,这也是 Dragonfly 自己的对话框给出的值。它取自每个类别各自的包围盒,因此大类别和小类别会得到不同的半径,表格按行显示所用半径。半径越大,曲率越平滑、越区域性,会忽略细小特征;半径越小则越局部,也更容易受噪声和表面上残留的体素台阶影响。
⚠ 自动半径(包围盒对角线 / 240)是 Dragonfly 面向“整个网格”的默认值;按类别使用时,凡是跨度小于约 140 个体素的类别,它都会小于一个三角形边长,而在这个尺度下厂商的数值根本不是曲率(它随半径增长——实测在十分之一边长处偏大 6 倍)。因此本插件绝不让自动半径低于该表面 4 个中值边长,并在提高半径时在该行说明。你手动输入的半径会照用,但低于 2 个边长时该行会警告其数值不是曲率。
独立算法的平均曲率(用于对比)。在 Dragonfly 自带数值旁边,本标签页还提供插件“自己的”逐顶点平均曲率,默认开启:把每条内部边的 Steiner 项(边长 x 二面角 / 2)平分到它的两个端点,再除以每个顶点所代表的面积。它不需要半径,因此不会落入上面所说的区间;它精确地积分为“积分平均曲率(独立算法)”一列;并且不需要 trimesh,因此在 2025.1 上同样可用。它会作为“Mean Curvature (independent)”标量槽写到网格上,与 Dragonfly 的“Mean Curvature”并列,便于在三维视图中逐顶点对比;两种均值也会各自以自己的名字进入表格、Multi-ROI 和结果文件。在粗网格上它集中于边(多面体的曲率就在边上);在细而光滑的网格上它收敛到真值(球体给出 1/R,误差小于 1%)。
积分平均曲率被有意给出两列。一列是在该标签的顶点上求 sum(H x A),其中 H 来自逐顶点曲率,A 是每个顶点所代表的重心对偶面积。另一列是与半径无关的离散值 —— 对内部边求“边长 x 二面角 / 2”之和 —— 它对三角网格是精确的,既不假设半径也不假设表面光滑。它已用解析解验证:边长为 a 的立方体精确得到 3pia,半径为 R 的球收敛到 4piR(在所测细分下误差 0.04%)。两者的比值就显示在它们旁边。
比值远离 1.00 说明该曲率半径不适合这个表面上的特征 —— 而不是说其中某个数值算错了。这正是两列都提供、而不是只给一列的原因。
Populate to Multi-ROI(回填到 Multi-ROI)把逐标签的各列作为标量值槽写入原始 Multi-ROI,于是它们会出现在 Dragonfly 自己的对象列表中,并可用于给标签着色。每个标题都带 -fromMesh 后缀,因此由网格测得的数值永远不会被误认为是由体素测得的。网格的标签 i 按类别自身的标签号对应到 Multi-ROI 的标签 i,绝不按 Multi-Mesh 内部 1..n 的重新编号;数组也按厂商自己的约定写入:长度为 labelCount + 1,索引 0 为背景。
重复测量会复用同名的槽,而不会不断堆积重复项;某一列如果在所有标签上都没有可用数值,则会被报告并且完全不写入 —— 全零的槽无法与“测得为零”区分开。“用写入的第一个数值给 Multi-ROI 着色”是一个独立的复选框,默认关闭:写入一个槽是增加内容、删除即可撤销,而给分割结果重新上色会改变它在每个视图中的外观。
⚠ 平均曲率需要 Dragonfly 2027.1(该计算功能自此版本才有),并且需要 trimesh —— 2027.1 自带它。在无法导入 trimesh 的情况下,厂商自己的代码会静默返回,因此本插件在调用之后会把槽读回来,明确告知“什么都没有测到”,而不是给你一堆零。与半径无关的积分和面积不依赖这两者,仍然可用。
9. 发布,以及发布前的核对
一个 Multi-Mesh:把每个类别作为一个标签放进同一个 Mesh。发布之前会核对三件事:对象报告的标签数、总顶点数与各部件之和是否一致、以及每个标签自身的大小。一个悄悄丢掉了某个类别的 Multi-Mesh 与完整的看起来毫无区别,所以这些数字是核对出来的,而不是假定的。
分开发布:每个类别一个 Mesh,标题包含类别名与来源。当各类别需要逐个导出或测量时,用这一条。
⚠ 网格标签是 Dragonfly 2027.1 才引入的。更早的版本里 Mesh 类根本没有标签相关接口,因此合并选项会列出但被禁用,并写明原因——绝不会悄悄改成别的做法。
在按下“发布”之前,各类别的表面都是临时对象:它们不会出现在对象列表中,重新运行会释放上一次的结果。因此可以尝试三种不同的搜索距离,而不会在会话里留下三套网格。
10. 第 6 个标签页:报告
关于整次运行的一份文档:读取了什么、表面是怎么生成的、得到了什么结果、往 Multi-ROI 写入了什么、发布了什么。这个标签页只有三项选择和一个按钮,不会改动会话中的任何对象。
- 包含哪些内容。共十个部分——涉及的对象;各类别以及它们是否接触;网格生成方法及其参数;每种方法生成的表面;测量结果;写入 Multi-ROI 的内容;发布到会话中的内容;集中列出的所有警告;无人值守运行的日志;以及本 Dragonfly 版本能做与不能做的事。默认全部勾选,不想要的可以取消。保留但没有数据的部分仍会出现并说明“没有可记录的内容”,而不是看起来像一个空结果。
- 格式。Word (.docx)、PDF (.pdf) 或 Excel (.xlsx)——三者内容相同。.docx 和 .xlsx 由插件自己写出,无需安装任何东西;.xlsx 会保留每个表格的每一行,并把每个表格放在独立工作表中,数值列按数字写入,可以直接求和。.pdf 通过 Dragonfly 自带的 Qt 渲染,写出时面板会短暂停顿。
- 保存位置。直接输入完整路径,或按浏览……选择文件夹和文件名。路径在输入时就会被检查:文件夹必须存在且可写,扩展名必须与格式一致。更改格式时扩展名会自动跟着改。
在无人值守运行中,报告是最后一步。它的路径会在流程开始之前就被检查——不可写时“执行全部步骤”会保持禁用并说明原因;如果没有设置报告文件,这一步会被直接跳过并写入日志。这样写出的报告里还包含本次运行的日志,因此没人盯着的运行事后也能读回。
11. 运行要求与已知限制
- 无需安装、无需联网、无需 GPU、无需虚拟环境:进程内 PyQt6 加 Dragonfly 自带的 numpy。
- 吸附前的可选图像降噪与泊松重新网格化是 Dragonfly 2027.1 的能力;缺少时运行会明确说明并跳过。
- 泊松重建会用重建结果替换已确定的表面,亚体素位置会被再次近似,因此默认关闭。
- 图像与 Multi-ROI 不必共用同一网格:表面来自 Multi-ROI 的网格,灰度按世界坐标读取。两者尺寸不同时会如实报告,便于确认它们确实是同一个样品。
- 尚未在运行中的 Dragonfly 内实测。所有接口签名均取自两个已安装版本的官方类型存根,并通过一个强制这些参数个数的伪对象模型进行验证;面板已在离屏环境下验证。实机运行仍待补做。
Part II English Manual
Contents
1. Introduction
2. Execute All Steps with Current Settings (unattended)
3. What 'surface determination' means here
4. Two ways to build the surfaces, and how to compare them
5. Speed: what is fast, what is not, and the one switch
6. Classes that touch cannot be determined from grey levels
7. What the run reports, and what a volume means
8. Measurements, and writing them back onto the Multi-ROI
9. Publishing, and what is verified first
10. Tab 6: the report
11. Requirements and limits
1. Introduction
Two inputs, both already published in the session: an image channel and a Multi-ROI. For every class of the Multi-ROI this plugin extracts that class as its own ROI and builds a surface around it by either or both of two methods - the surface determination, which refines the surface against the image's grey levels, and Dragonfly's own marching cubes, which does not - and finally combines all the class surfaces into one Multi-Mesh per method.
A Multi-Mesh is not a separate kind of object. It is one Mesh carrying one LABEL per class, the same way a Multi-ROI carries one label per class. Each label keeps the name and the colour its class had in the Multi-ROI, and the published object is switched to label colours so the classes are told apart in the 3-D view.
The six tabs are the workflow, in the order the work happens: 1 Inputs, 2 Meshing, 3 Run, 4 Measurements, 5 Publish, 6 Report. Every one of them is open and editable from the start, so you can set them all before pressing Execute All Steps; a step whose input has not been produced yet refuses when you press ITS button and says which step is missing. Only Publish and Populate create or change anything in the session, and only the Report tab writes a file of its own.
2. Execute All Steps with Current Settings (unattended)
The first tab has one large button, Execute All Steps with Current Settings. It runs tabs 1 to 6 in order - read, build the surfaces, measure, write the measurements onto the Multi-ROI, publish, write the report - each step with the settings its tab shows at that moment. Set the parameters on the other tabs first, press it, and leave; hours later the results are in the session and the report is on disk.
Results file. Above the button is a field for a .csv path (full path; optional). It is checked BEFORE the chain starts - a path that is not a .csv, a folder, or a folder that cannot be written keeps the button disabled with the reason shown - because nobody is there when the chain ends. The report file on tab 6 is checked the same way, at the same moment. When it finishes, the plugin writes one row per class per meshing method with the run's columns (voxels, vertices, faces, area, volume, Euler, refined, seconds) and the measurement columns (radius, mean curvature, both integrated mean curvatures, ratio, closed) side by side, in Dragonfly's own units, plus a time-stamped log of every status line as <name>_log.txt beside it. So the numbers survive even if Dragonfly is closed before you are back.
A step that has nothing to do is skipped and logged, not treated as a failure — for example Measure on a build without mean curvature still writes area and the exact integral. A step that fails, a run that produced no surface, or a Cancel (on the Run or Measurements tab) ends the chain, and the first tab says at which step and why.
3. What 'surface determination' means here
There are two steps per class, and they answer different questions.
- The initial surface is built around the class's VOXELS. It can therefore only sit on voxel faces: at best it is a staircase whose step is one voxel.
- The refinement moves every vertex along its own normal to the strongest grey-level gradient within a search distance. That is what puts the surface BETWEEN voxels, and it is the step that makes the result a measurement of the specimen rather than a rendering of the segmentation.
The parameters are Dragonfly's own Surface Determination parameters, with its own defaults (sampling 1/1/1, relaxation 1.0, falloff 0.5), so a user who knows that tool gets the same first answer here.
The search distance is entered in VOXELS and the resulting LENGTH is printed beside it. The conversion goes through the SMALLEST of the three voxel spacings, because a search longer than the thinnest feature can carry a vertex past its own edge and snap it onto a different one.
4. Two ways to build the surfaces, and how to compare them
Tab 2 Meshing offers two methods, side by side. Tick either one, or BOTH.
- Surface determination: snap onto the grey levels. The surface is built around the class's voxels and then snapped onto the image's grey-level gradient, which is what puts it between voxels. This is the sub-voxel method, and every parameter below it on that tab describes it.
- Marching cubes: Dragonfly's own ROI to mesh, no snap. This is Dragonfly's own ROI-to-mesh conversion, which is already marching cubes. The surface follows the voxel boundary and no grey level is ever consulted, so it is the BASELINE. The sampling, the void filling and the smoothing apply to it; the search distance, the relaxation, the falloff and the Poisson remesh do not.
With both ticked, every class produces two surfaces and everything downstream carries one row per class per method, told apart by a Method column: the run table (with its totals stated per method), the measurements table, the .csv and the report. That is what makes the difference between the two a number rather than an impression - compare the areas, or the integrated mean curvatures, of the same class.
The measurement suffix. Each method has its own text field on tab 2, -fromSurfaceMesh and -fromMarchingMesh by default, and you can change either one. It is the suffix every measurement carries when Populate writes it onto the Multi-ROI, so a value measured on a mesh can never be mistaken for one measured on the voxels - and the two methods' values land in different slots instead of one overwriting the other.
⚠ A suffix cannot be empty, and with both methods ticked the two cannot be the same: the run is refused, with the reason, before anything is built.
Publishing with both methods produces two objects - one per method - and each carries its method in its own name, for example “Multi-Mesh of segmentation (marching cubes)”, because the object list is the only place you can tell them apart.
5. Speed: what is fast, what is not, and the one switch
Reading is cheap now. Pressing Read asks Dragonfly for each class's voxel count and bounding box directly, and reads the label volume once, without copying it. On a 256x256x256 Multi-ROI with 300 classes the whole Read takes about 0.08 s. Earlier versions copied the whole label volume twice before doing any work, which took seconds on a small specimen and minutes on a large one. The Inputs tab prints how long the Read took and where its numbers came from, so you can see which route your build used.
The run does not freeze Dragonfly. The meshing steps run on the plugin's own worker thread, so the main window stays responsive and is no longer greyed out once per class. They run one class at a time on purpose: Dragonfly's meshing calls return wrong numbers when two of them run at once, which was measured, so the plugin does not do it. The plugin's own arithmetic - the contact check and the measurements - does already use every core.
Fast initial surface (native marching cubes). This tick box on tab 2 is the one real speed lever for a long run. It starts the determined surface from Dragonfly's own ROI-to-mesh conversion instead of its Surface Determination step, and then snaps it onto the grey levels exactly as before. Measured on 40 classes of a 320x320x320 volume: 0.41 s instead of 4.53 s, eleven times faster.
It is off by default because it does not give the same surface. The vertex and face counts match on every class, but after the snap the two surfaces sit about 0.12 voxel apart on average - 1.8 voxel at the worst single vertex - and their volumes differ by about one percent. For scale, the snap itself moves Dragonfly's own surface about 0.15 voxel. So it is a real difference, not a rounding one. Use it when you need a run over hundreds of classes to finish; leave it off when the number has to match one measured with Dragonfly's own Surface Determination.
Progress lines are grouped. A run over a thousand classes reports a line per class. They are shown in batches about twelve times a second rather than one repaint each, so the log stays readable and the panel does not flicker. No line is lost.
6. Classes that touch cannot be determined from grey levels
⚠ A boundary shared by two classes is a partition somebody drew, not a grey-level edge. There is nothing there for the refinement to snap to, so the two surfaces can end up overlapping or leaving a gap.
The Inputs tab therefore MEASURES this before the run: it counts, for every pair of classes, how many voxel FACES they share (only 6-connectivity counts — two classes meeting at a corner share no surface). The pairs are listed with their contact area, so the decision is yours and it is made on a number.
- No pair touches — every surface borders the background, and all of it can be determined from the image. This is the case the plugin is for.
- Some pairs touch — the surfaces away from the shared boundary are still determined properly; the shared parts are as good as the segmentation was. Turning the refinement off makes every surface the voxel boundary, which is at least consistent.
On a very large Multi-ROI the contact count runs on a strided subsample and says so: those numbers are then a lower bound.
7. What the run reports, and what a volume means
Run meshes one class at a time and can be cancelled between classes. Per class it reports vertices, faces, area, volume, the Euler characteristic and the time taken, plus which internal route produced the surface.
⚠ A volume is only a volume for a CLOSED surface. The Euler number is 2 for one closed piece; anything else means that row's volume is not one. The number is shown rather than hidden, with the Euler column beside it to say which rows to trust.
Areas and volumes are in Dragonfly's own units — it stores world coordinates in metres, so the columns are m² and m³. Nothing is pre-scaled anywhere in this plugin.
A class that cannot be meshed does not end the run and does not disappear: it becomes a row with the reason on it.
8. Measurements, and writing them back onto the Multi-ROI
The Measurements tab measures the surfaces the run produced. PER-VERTEX values are a field over each surface; PER-LABEL values are one number for the whole class, and only those can go back onto the Multi-ROI. Measuring creates and changes nothing.
Mean curvature is Dragonfly's own: the tab calls MeshHelper.computeCurvatureAsVertexScalarValues, the same entry point as the vendor's Compute Mean Curvature, and reads the values back out of the vertex scalar slot it writes. What the vendor computes is a MEASURE, not a curvature: around each vertex it sums the length of every edge falling inside a ball of the given radius times that edge's dihedral angle, halved — so its unit is a length. The tab divides that by the area of the ball's patch to get a curvature in 1/length, and that division is the only approximation in this plugin.
Radius. The automatic radius is the bounding-box diagonal over 240, which is what Dragonfly's own dialog offers. It comes from EACH class's own box, so a big class and a small one get different radii and the table shows the radius per row. A bigger radius gives a smoother, more regional curvature that ignores small detail; a smaller one is more local and more sensitive to the noise and the voxel steps left on the surface.
⚠ The automatic radius (bounding-box diagonal / 240) is Dragonfly's WHOLE-MESH default; per class it falls below one triangle edge for any class under about 140 voxels across, and there the vendor's value is not a curvature at all (it grows with the radius — measured 6x too large at a tenth of an edge). The plugin therefore never lets the automatic radius fall below 4 median edge lengths of that surface, and says on the row when it raised it. A radius you type is used as typed, but below 2 edge lengths the row warns that its values are not a curvature.
Independent mean curvature (for comparison). Beside Dragonfly's own value the tab offers the plugin's OWN per-vertex mean curvature, on by default: every interior edge's Steiner term (edge length x dihedral angle / 2) is split onto its two end vertices and divided by the area each vertex stands for. It needs no radius, so it cannot fall into the regime described above; it integrates EXACTLY to the 'IMC (independent)' column; and it needs no trimesh, so it also works on 2025.1. It is written onto the mesh as the 'Mean Curvature (independent)' scalar slot next to Dragonfly's 'Mean Curvature', so the two can be compared vertex by vertex in the 3-D view, and both means go into the table, the Multi-ROI and the results file under their own names. On a coarse mesh it is edge-concentrated (a polyhedron's curvature lives on its edges); on a fine smooth mesh it converges to the true value (a sphere gives 1/R to under 1 percent).
Integrated mean curvature is reported TWICE, on purpose. One column is sum(H x A) over the label's vertices, from the per-vertex curvature and the barycentric area each vertex stands for. The other is the radius-free discrete value — the sum over interior edges of edge length times dihedral angle, halved — which is exact for a triangle mesh and assumes neither a radius nor a smooth surface. It is verified against closed forms: a cube of side a gives 3pia exactly, and a sphere of radius R converges to 4piR (0.04% at the tested refinement). The ratio of the two is shown beside them.
A ratio far from 1.00 means the curvature radius does not suit the features on that surface — not that either number is broken. That is why both ship instead of one.
Populate to Multi-ROI writes the per-label columns onto the SOURCE Multi-ROI as scalar value slots, so they appear in Dragonfly's own object list and can colour the labels. Every title carries the -fromMesh suffix, so a value measured on a mesh can never be mistaken for one measured on the voxels. Mesh label i goes to Multi-ROI label i by the class's own label number, never by the Multi-Mesh's 1..n renumbering, and the arrays are written the vendor's own way: labelCount + 1 long, with index 0 the background.
Measuring twice REUSES the slots of the same titles instead of piling up duplicates, and a column with no usable value for any label is reported and not written at all — an all-zeros slot cannot be told apart from a measurement of zero. Colouring the Multi-ROI by the first value written is a separate, opt-out-by-default checkbox: writing a slot adds something and can be undone by deleting it, but repainting a segmentation changes how it looks in every view.
⚠ Mean curvature needs Dragonfly 2027.1 (the computation arrived there) and it needs trimesh, which 2027.1 ships. Where trimesh cannot be imported the vendor's own code returns SILENTLY, so this plugin reads the slot back afterwards and says nothing was measured rather than showing zeros. The radius-free integral and the area need neither, and still work.
9. Publishing, and what is verified first
One Multi-Mesh puts every class into one Mesh as one label. Before it is published, three things are checked: the label count the object reports, the total vertex count against the sum of the parts, and every label's own size. A Multi-Mesh that quietly lost a class looks exactly like one that did not, so those numbers are reported rather than assumed.
Separate meshes publishes one Mesh per class instead, titled after the class and its source. That is the route to take when the classes are to be exported or measured one at a time.
⚠ Mesh labels arrived in Dragonfly 2027.1. On an older build the Mesh class has no label API at all, so the Multi-Mesh route is listed but disabled, with that as the stated reason — it is never quietly replaced by something else.
Until Publish is pressed the per-class surfaces are TEMPORARY objects: they are not in the object list, and a new run releases the previous ones. So three different search distances can be tried without leaving three sets of meshes behind.
10. Tab 6: the report
One document about the whole run: what was read, how the surfaces were built, what came out, what was written onto the Multi-ROI and what was published. The tab is three choices and one button, and it changes nothing in the session.
- What goes in. Ten sections - the objects, the classes and whether they touch, the meshing methods and their parameters, the surfaces each method produced, the measurements, what was written onto the Multi-ROI, what was published, every warning in one place, the unattended run's own log, and what this Dragonfly build could and could not do. All ticked by default; untick anything you do not want. A section you keep but have no data for still appears and says so, rather than looking like an empty result.
- The format. Word (.docx), PDF (.pdf) or Excel (.xlsx) - the same content in each. The .docx and the .xlsx are written by the plugin itself and need nothing installed; the .xlsx keeps every row of every table and puts each table on its own sheet, with numeric columns written as numbers so you can sum them. The .pdf is rendered through Dragonfly's own Qt, so the panel pauses for a moment while it is written.
- Where it goes. Type the full path or press Browse... to pick the folder and the name. The path is checked as you type: the folder has to exist and be writable, and the extension has to match the format. Changing the format rewrites the extension for you.
In an unattended run the report is the last step. Its path is checked before the chain starts - an unwritable one keeps Execute All disabled with the reason - and if no report file is set the step is simply skipped and logged. The report written that way also contains the run's own log, so a run nobody watched can be read back afterwards.
11. Requirements and limits
- No install, no internet, no GPU, no venv: in-process PyQt6 plus the numpy Dragonfly already ships.
- The optional image denoise before the snap, and Poisson remeshing, are Dragonfly 2027.1 features. Where they are missing the run says so and carries on without them.
- Poisson remeshing REPLACES the determined surface with a reconstruction of it, so the sub-voxel positions are approximated again. It is off by default.
- The image and the Multi-ROI need not share a grid: the surfaces come from the Multi-ROI's grid and the grey levels are read in WORLD coordinates. Different sizes are reported, so you can check that the two objects really are the same specimen.
- Not yet verified in a running Dragonfly. Every signature was read from the shipped type stub of both installed versions and is exercised against a fake object model that enforces those exact arities; the panel is verified offscreen. A live run is still owed.