Vessel Morphometry(血管形态测量)
Vessel Morphometry - User Manual
Dragonfly Prototype Apps · Vessel morphometry along the centerline...
版本 Version 1.0 · 2026-08-08
第一部分 中文手册
目录
1. 简介
2. 两个半径定义,以及为什么两个都要报告
3. 单位:网格自身的单位,绝不人工预缩放
4. 扭率是实验性指标,且不报告最大值
5. 不封闭的网格没有体积
6. 安装与启用
7. 使用方法
8. 自动体素尺寸,以及它为什么重要
9. 报告里有什么
10. 限制
1. 简介
从会话中已发布的封闭表面网格(Mesh)出发,沿一根血管自身的中心线,逐个横截面测量管腔形态。
插件先把网格内部体素化、骨架化,取骨架中最长的测地路径作为中心线,然后在每个采样点上用两种互相独立的方式测量管腔,并计算曲线的微分几何量(曲率与扭率)。输出的是各量沿血管的分布,而不是一个平均数。
不创建、不修改任何对象。 插件只读取已发布的网格,并且只写出你明确导出的文件。
六个步骤:输入 / 标定与预处理 / 参数 / 预览与质检 / 运行 / 结果与导出,另有一个不编号的「分享报告」标签页。预览与运行都不发布任何内容;所有导出都是第 6 步里的显式操作。
2. 两个半径定义,以及为什么两个都要报告
每次结果都以两个独立的行给出两个半径,绝不会出现一个叫“半径”的行:
- 内切球半径 —— 在该处管腔内能放入的最大球的半径。这就是 vmtk / 3D Slicer 中心线里 “Radius” 那一列的含义,它受横截面短轴限制。
- 等效半径 sqrt(A/π) —— 与横截面面积相同的圆的半径。
两者只在横截面为圆时才相等。管腔越扁,二者差得越多,而且差得并不小:参考流程给出的“半径”约为其 sqrt(A/π) 的 54%,即约 1.9 倍之差 —— 这正是管腔被压扁约 3.5:1 时这两个定义的差别。把其中之一称作“那个半径”,正是历史数据无法互相核对的根源。
因此插件同时给出两者以及它们的比值。请针对你自己的标本读这个比值,不要沿用别处的数字:在本插件所针对的两根血管上,实测比值为 1.15 与 1.09 —— 这两个管腔接近圆形,对它们引用 1.9 就是错的。
对应的直径(各半径的两倍)也各自单列,因为多数既有表格是以直径书写的。
3. 单位:网格自身的单位,绝不人工预缩放
⚠ 不要事先缩放模型。 本插件所替代的流程在测量前把网格人工放大 10 倍,之后又不一致地换算回去。结果是长度大了 10 倍、面积大了 100 倍、体积又额外差了 10 倍 —— 一个“图方便”的步骤造成三种不同的错误,而且三者看上去都很合理。
在这里,换算只发生一次,而且插件会说明它做了什么。Dragonfly 的世界坐标以米为单位;顶点在读取时一次性换算到工作单位(默认取 Dragonfly 自己的长度显示单位,这样数值与界面里的标尺一致),此后所有长度都以该单位表示,面积为单位²,体积为单位³,曲率与扭率为 1/单位。
工作单位会在第 2 步显示,并同时以该单位给出包围盒尺寸,便于在测量之前核对量级;每一份导出文件里也都记录了它。如果 Dragonfly 的长度单位是本插件无法标注的单位(例如英寸),它会退回毫米并明确说明,而不是用一个没有标注的单位去测量。
4. 扭率是实验性指标,且不报告最大值
⚠ 扭率标注为“实验性”,并且一概不报告扭率的最大值。
扭率需要中心线的三阶导数,而它的分母在曲线局部接近直线处趋于零,因此在每个拐点处都无定义,任何极值都会被个别采样点主宰。在同一标本上用四种体素尺寸实测,扭率最大值分别为 54.9 / 17.6 / 15.3 / 33.4 1/mm,而平均值变化在 8% 以内,曲率在 0.4% 以内。一个会随用户根本没有选择过的设置变化 3 倍的数字不是测量值;把它印在可复现的数字旁边,等于暗示它也是测量值。
所以只报告平均值,标注为实验性,并写明产生它的拟合窗口;最小值与最大值处打印“不报告”并附上原因。曲率低于阈值的采样点会从扭率统计中排除,并在报告里计数。
曲率没有这个问题:它在不同体素尺寸下可复现(同一标本四种体素尺寸实测 0.1812 / 0.1708 / 0.1702 / 0.1708 1/mm),因此与其它量一样正常给出最小值与最大值。
5. 不封闭的网格没有体积
⚠ 如果表面不封闭,报告在体积处打印「未定义:网格不封闭」而不是一个数字。它本来就没有体积可算,并且其体素化的内部也可能是错的。
这种情况下「运行」不会被禁用:中心线、两个半径与曲率仍然有意义,因此插件会给出警告并继续。请不要把带有该警告的报告当成“只是缺了体积”。
分析前会先合并重合顶点。STL 会为每个三角面各存一份顶点,因此在合并之前,一个完全封闭的 1160 面血管会被报告为 1160 个连通分量、watertight = 否。在真实标本上实测:合并前 1160 个分量,合并后 1 个。
第 1 步会在你走到「运行」之前显示面数、顶点数、是否封闭、连通分量数、体积与表面积。对于超过 30 万面的网格,该检查不会自动开始 —— 请点「检查几何」。
6. 安装与启用
1. 打开 Prototype Apps ▸ App Store,在 Measurements & Analysis 分组里找到 Vessel Morphometry 并启用。
2. 重启 Dragonfly。插件在启动时被发现,因此必须重启。
3. 菜单项为 Prototype Apps ▸ Vessel morphometry along the centerline...
4. 无需其它准备:不需要虚拟环境、不需要 GPU、不需要管理员权限,测量本身也不访问网络。唯一的例外是可选的「分享报告」标签页,它不做任何计算:发布报告会开启一条临时 Cloudflare 快速隧道,且首次使用会在你确认后下载 Cloudflare 的 cloudflared.exe(约 52 MB)。计算在 Dragonfly 进程内完成,使用 Dragonfly 自带的 numpy、scipy 与 scikit-image。还需要 trimesh:Dragonfly 2026.1 与 2027.1 自带它,2025.1 没有,因此本插件内置了一份(MIT、纯 Python),仅在 Dragonfly 本身缺失时使用。因此所有版本用的是同一个库,也仍然不需要下载任何东西。
7. 使用方法
1. 第 1 步 输入 —— 选择一根血管的已发布网格(若在打开面板之后才创建,请点「刷新」),并阅读几何信息行:面数、顶点数、是否封闭、连通分量数、体积、表面积。
2. 第 2 步 标定与预处理 —— 核对工作单位,以及以该单位给出的包围盒尺寸。除非你确有目的,请把单位保持在「自动」。
3. 第 3 步 参数 —— 首次运行请把体素尺寸与采样间距都保持自动。两个求导窗口是局部血管半径的倍数,而不是采样点个数。
4. 第 4 步 预览与质检 —— 点「运行预览(使用更粗的体素)」,用很短的时间核对设置。预览数值只用于检查设置,绝不可用于报告;质检表会报告实际得到的「每半径体素数」。
5. 第 5 步 运行 —— 点「运行完整测量」。小血管约需 20 秒;各阶段实时输出到日志,进度条旁有秒表。「取消」在下一阶段边界处生效;取消之后仍然跑完的运行,其结果会被丢弃。
6. 第 6 步 结果与导出 —— 阅读汇总表,切换分布图所显示的量,然后导出:逐采样点 CSV、汇总 CSV、JSON,或 HTML / Word / PDF 报告。
修改任何输入或参数都会丢弃已有结果,因此屏幕上的数字始终对应显示中的设置。在完整测量结束之前,导出按钮是禁用的。
8. 自动体素尺寸,以及它为什么重要
体素尺寸由血管半径决定,而不是由包围盒决定:自动 = 局部血管半径 / 每半径 8 个体素,其中半径按 2V/A 估计(对长管 V = πr²L、A = 2πrL,故 2V/A 即半径,无需中心线)。
这一点是关键。当每半径不足约两个体素时,体素化后的管道会把自己的弯曲“抄近路”截断,骨架便会穿过网格中并不存在的实体。在仅口径不同的两个真实标本上实测:每半径 1.39 个体素时,175 个中心线采样点中有 45 个落到了网格之外;3.02 个体素时无一落在外面。体素越细越慢,但丢失的采样点越少,面板上也这样写明。
落在网格之外的采样点会被报告为缺失,绝不会给出一个偏小的正半径 —— 到表面的距离是无符号的,一个漂移到外面的点否则会返回一个完全合理的半径。若丢失的是一整段连续采样点,插件会在其两端已测得的采样点之间做直线桥接,再重新居中到横截面上;报告会给出桥接的采样点数、仍在网格外的采样点数,以及被排除的横截面数。
参数 | 作用 | 默认 |
体素尺寸 | 填充网格内部所用体素的边长。越细则保留的采样点越多,但越慢。 | 自动:半径 / 8 |
采样间距 | 沿弧长方向相邻中心线采样点之间的距离。 | 自动 |
中心线平滑半宽 | 对中心线本身的平滑。受几何约束:位移超过约一个管腔半径,中心线就会跑出血管,因此必要时测量会自行收缩该值,并报告实际使用值。 | 6 个采样点 |
曲率拟合窗口 | 曲率多项式拟合的半宽,按局部半径的倍数给出。若按采样点个数固定,结果就会依赖体素尺寸。 | 3 × 局部半径 |
扭率拟合窗口 | 扭率的同类参数;它需要三阶导数,因此窗口宽得多。加宽求导窗口不会移动中心线,只会降低估计的分辨率。 | 12 × 局部半径 |
预览放粗倍数 | 仅在预览运行时把体素边长乘上该倍数。 | 3 |
9. 报告里有什么
文档报告(HTML、Word 或 PDF —— 三者出自同一个文档模型,因此内容完全一致)包含五节:
- 汇总 —— 体积、表面积、中心线长度、首尾弦长、两种约定的迂曲度(指数 L/D − 1 与比值 L/D)、两个半径的平均值(各自单列)以及二者之比。
- 分布 —— 两个半径、两个直径、横截面积、曲率与扭率的平均值/中位数/最小值/最大值/标准差/有效数/缺失数;两个半径沿中心线的剖面曲线;以及每个量的直方图。
- 警告与排除 —— 测量过程中产生的每一条警告,各自成块。
- 逐采样点数据 —— 每个采样点的弧长、两个半径、面积、曲率与扭率。Word 与 PDF 只显示前 200 行并注明总行数;随报告一并写出的 CSV 始终包含全部采样点。
- 设置与溯源 —— 实际使用的体素尺寸及其是否自动、实际得到的每半径体素数、采样间距、两个求导窗口的物理长度、实际使用的平滑半宽,以及桥接 / 仍在网格外 / 被排除的横截面 / 被排除出扭率统计的采样点数。这些设置会改变结果,因此它们属于结果的一部分。
在 HTML 中图形保持矢量,整份报告是一个自包含文件,不发起任何外部请求;Word 与 PDF 使用同样图形的位图化版本。
「分享报告」标签页可以通过 Cloudflare 快速隧道把这份单文件 HTML 临时发布到互联网上:无需账号、无需上传,直接由本机提供服务,窗口关闭后该地址即永久失效。它没有密码 —— 在分享期间,拿到链接的任何人都能阅读 —— 且首次使用会在你确认后下载 Cloudflare 的 cloudflared.exe(约 52 MB)。
10. 限制
一次一根血管。 中心线取骨架中最长的测地路径,即主干。若网格中包含多根血管,或是带长侧支的树状结构,测量只沿该主干进行;第 1 步的连通分量数可以提示你是否属于这种情况。
只有当横截面的某个闭合环环绕中心线点时,该截面才被计入。垂直于中心线的平面同样会切到相邻分支以及 U 形回弯的另一支,把这些面积相加会悄悄把管腔算成两倍;没有环绕环的采样点(通常正好位于分叉处)会被报告为排除,而不是猜一个值。
面板中的逐采样点表最多显示 2000 行,因为绘制更多会导致界面卡顿。CSV 导出始终包含全部采样点。
手工输入的体素尺寸不会事先与网格尺寸做合理性核对。数值离谱时会以「体素化得到空实体」的形式暴露出来,或在触及网格上限时被自动放粗,两者都会写入日志。
Part II English Manual
Contents
1. Introduction
2. The two radius definitions, and why both exist
3. Units: the mesh's own, with no manual pre-scaling
4. Torsion is experimental, and has no maximum
5. A mesh that is not watertight has no volume
6. Installation & enabling
7. Using it
8. The automatic voxel size, and why it matters
9. What the report contains
10. Limitations
1. Introduction
Measures the lumen of one vessel, cross-section by cross-section, along its own centerline, starting from a closed surface Mesh published in the session.
The plugin voxelises the mesh interior, skeletonises it, takes the longest geodesic path through the skeleton as the centerline, and then at every sample measures the lumen two independent ways plus the differential geometry of the curve (curvature and torsion). It reports the distribution of each quantity along the vessel, not a single average.
Nothing is created or modified. The plugin reads a published Mesh and writes only the files you explicitly export.
Six steps: Inputs / Calibration & Preprocessing / Parameters / Preview & QC / Run / Results & Export, plus an unnumbered Share Report tab. Preview and Run publish nothing; every export is an explicit action in step 6.
2. The two radius definitions, and why both exist
Every result carries two radii as separate rows, never one row called "the radius":
- Inscribed sphere radius — the largest sphere that fits inside the lumen at that point. This is what the centerline "Radius" column of vmtk / 3D Slicer is, and it is bounded by the SHORT axis of the cross-section.
- Equivalent radius sqrt(A/pi) — the radius of a circle with the same cross-sectional area.
The two agree only for a round section. They diverge as the lumen flattens, and the divergence is not small: the reference pipeline's own "radius" is about 54% of its sqrt(A/pi), a factor of about 1.9, which is what a lumen flattened around 3.5:1 does to these definitions. Reporting one of them as "the radius" is exactly what made the historical numbers impossible to reconcile.
So the plugin prints both and the ratio between them. Read that ratio for your own specimen instead of carrying a figure over from elsewhere: on the two vessels this plugin was built for it measured 1.15 and 1.09 — those lumens are close to round, and quoting 1.9 for them would have been wrong.
The corresponding diameters (twice each radius) are reported as their own rows too, because that is the form most existing tables are written in.
3. Units: the mesh's own, with no manual pre-scaling
⚠ Never pre-scale the model. The workflow this plugin replaces scaled the mesh 10x by hand before measuring and then converted back inconsistently. The result was that lengths came out 10x too large, areas 100x too large, and the volume off by a further factor of 10 — three different errors from one "convenience" step, and all three looked plausible.
Here the conversion happens exactly once, and the plugin says what it did. Dragonfly stores world coordinates in metres; the vertices are scaled once when they are read into a working unit (by default Dragonfly's own display unit for lengths, so the numbers match the GUI's ruler), and from then on every length is in that unit, areas in unit^2, volumes in unit^3, and curvature and torsion in 1/unit.
The working unit is shown in step 2 together with the bounding box in that unit, so the scale can be sanity-checked before anything is measured, and it is recorded in every export. If Dragonfly's length unit is one this plugin cannot label (inches, for example) it falls back to millimetres and says so rather than measuring in an unlabelled unit.
4. Torsion is experimental, and has no maximum
⚠ Torsion is reported as EXPERIMENTAL, and no torsion MAXIMUM is reported at all.
Torsion needs the third derivative of the centerline, and its denominator vanishes wherever the curve is locally straight, so it is undefined at every inflection. Isolated samples therefore dominate any extremum. Measured on one specimen across four voxel sizes, the torsion maximum read 54.9 / 17.6 / 15.3 / 33.4 1/mm while the mean stayed within 8% and curvature within 0.4%. A number that moves by 3x with a setting the user never chose is not a measurement, and printing it beside reproducible figures implies that it is one.
So the mean is reported, labelled experimental, with the fitting window that produced it stated; the minimum and maximum print "not reported" together with the reason. Samples whose curvature is below a floor are excluded from the torsion statistics and counted in the report.
Curvature does not have this problem: it is reproducible across voxel sizes (measured 0.1812 / 0.1708 / 0.1702 / 0.1708 1/mm on one specimen at four voxel sizes) and is reported with a minimum and maximum like any other quantity.
5. A mesh that is not watertight has no volume
⚠ If the surface is not closed, the report prints "not defined: the mesh is not watertight" for the volume instead of a number. There is no volume to compute, and the voxelised interior may be wrong as well.
The Run button is not blocked in that case: the centerline, both radii and the curvature are still meaningful, so the plugin warns and continues. Do not read a report that carries that warning as if the volume were merely missing.
Coincident vertices are merged before anything else. An STL stores every triangle with its own three vertices, so without merging a perfectly closed 1160-triangle vessel reports 1160 components and watertight = no. Measured on a real specimen: 1160 components before merging, 1 after.
Step 1 shows the face count, vertex count, watertight flag, component count, volume and surface area before you reach Run. For a mesh above 300000 faces that check is not started automatically — press Check geometry.
6. Installation & enabling
1. Open Prototype Apps ▸ App Store, find Vessel Morphometry in the Measurements & Analysis group, and enable it.
2. Restart Dragonfly. Plugins are discovered at startup, so a restart is required.
3. The item appears as Prototype Apps ▸ Vessel morphometry along the centerline...
4. No further setup: no venv, no GPU, no administrator rights, and the measurement never touches the network. The one exception is the optional Share Report tab, which performs no computation: publishing opens a temporary Cloudflare quick tunnel, and the first use downloads Cloudflare's cloudflared.exe (about 52 MB) after you confirm it. It runs in Dragonfly's own process on the numpy, scipy and scikit-image that Dragonfly already ships. trimesh is also needed: Dragonfly 2026.1 and 2027.1 bundle it, Dragonfly 2025.1 does not, so a copy (MIT, pure Python) ships INSIDE this plugin and is used only where Dragonfly has none. Every version therefore uses the same library, and still nothing is downloaded.
7. Using it
1. Step 1 Inputs — pick the published Mesh of one vessel (press Refresh if you created it after opening the panel) and read the geometry line: faces, vertices, watertight, components, volume, surface area.
2. Step 2 Calibration & Preprocessing — check the working unit and the bounding box printed in it. Leave the unit on Automatic unless you deliberately want another one.
3. Step 3 Parameters — leave the voxel size and sample spacing automatic for a first run. The two derivative windows are multiples of the local vessel radius, not sample counts.
4. Step 4 Preview & QC — press Run preview (coarser voxel) to check the setup in a fraction of the time. Preview numbers are for checking the setup, never for reporting, and the QC table reports the voxels-per-radius that actually came out.
5. Step 5 Run — press Run the full measurement. About 20 seconds for a small vessel; the stages stream into the log and a clock ticks next to the progress bar. Cancel takes effect at the next stage boundary, and a run that finishes after Cancel has its result discarded.
6. Step 6 Results & Export — read the summary table, switch the distribution plot between quantities, then export: per-sample CSV, summary CSV, JSON, or a report as HTML / Word / PDF.
Changing any input or parameter drops the result, so the numbers on screen always belong to the settings shown. The exports are disabled until a full measurement has finished.
8. The automatic voxel size, and why it matters
The voxel size is chosen from the vessel radius, not from the bounding box: automatic = local vessel radius / 8 voxels per radius, with the radius estimated as 2V/A (for a long tube V = pi r^2 L and A = 2 pi r L, so 2V/A is the radius and no centerline is needed).
This is load-bearing. Below about two voxels per radius the voxelised tube short-circuits its own bends and the skeleton runs through solid that does not exist in the mesh. Measured on two real specimens that differ only in calibre: at 1.39 voxels per radius, 45 of 175 centerline samples ended up outside the mesh; at 3.02, none did. Finer is slower but loses fewer samples, and the panel says so.
A sample that is outside the mesh is reported as missing, never as a small positive radius — the distance to a surface is unsigned, so a point that has drifted outside would otherwise return a perfectly plausible radius. Where a contiguous stretch is lost, it is bridged by interpolating between the two measured samples that bound it and then recentred onto the cross-section; the report states how many samples were bridged, how many are still outside, and how many cross-sections were excluded.
Setting | What it does | Default |
Voxel size | Edge length of the voxel used to fill the mesh interior. Finer = more samples kept, slower. | automatic: radius / 8 |
Sample spacing | Distance between centerline samples along the arc. | automatic |
Centerline smoothing half-width | Smoothing of the centerline itself. Bounded by geometry: moving it more than about one lumen radius takes it out of the vessel, so the measurement reduces it when it has to and reports the value used. | 6 samples |
Curvature fitting window | Half-width of the polynomial fit for curvature, as a multiple of the LOCAL RADIUS. A window fixed in samples would make the answer depend on the voxel size. | 3 x local radius |
Torsion fitting window | The same for torsion, which needs the third derivative and therefore a much wider window. Widening a derivative window never moves the centerline; it only costs resolution in the estimate. | 12 x local radius |
Preview coarsening factor | Multiplies the voxel edge for a preview run only. | 3 |
9. What the report contains
The document report (HTML, Word or PDF — one shared model, so all three carry the same content) has five sections:
- Summary — volume, surface area, centerline length, end-to-end chord, tortuosity in both conventions (index L/D − 1 and ratio L/D), both radius means as separate rows and the ratio between them.
- Distributions — mean / median / min / max / std / N / missing for both radii, both diameters, cross-sectional area, curvature and torsion; a profile of both radii along the centerline; and a histogram of each quantity.
- Warnings and exclusions — every warning the measurement produced, as its own callout.
- Per-sample measurements — arc length, both radii, area, curvature and torsion for every sample. Word and PDF show the first 200 rows and say how many there are; the CSV written beside the report always contains every sample.
- Settings and provenance — the voxel size actually used and whether it was automatic, the voxels per radius that came out, the sample spacing, both derivative windows as physical lengths, the smoothing half-width used, and the counts of samples bridged / still outside / cross-sections excluded / samples excluded from torsion. These settings change the answer, so they are part of it.
In HTML the plots stay vector and the whole report is one self-contained file with no external requests. Word and PDF get the same plots rasterised.
The Share Report tab can serve that one-file HTML temporarily over the internet through a Cloudflare quick tunnel: no account and no upload, served from this machine, and the address stops working permanently when the window is closed. It has no password — anyone with the link can read it while it is live — and the first use downloads Cloudflare's cloudflared.exe (about 52 MB) with your confirmation.
10. Limitations
One vessel per run. The centerline is the longest geodesic path through the skeleton, i.e. the main trunk. A mesh containing several vessels, or a tree with long side branches, is measured along that trunk only, and the component count in step 1 tells you when that is what you have.
A cross-section is counted only when a closed loop of the section encircles the centerline point. A plane perpendicular to the centerline also cuts neighbouring branches and the far limb of a U-turn, and summing those would silently double the lumen; a sample with no enclosing loop (typically right at a bifurcation) is reported as excluded rather than guessed.
The per-sample table in the panel shows at most 2000 rows, because painting more freezes the GUI. The CSV export always contains every sample.
A manually entered voxel size is not sanity-checked against the mesh extents beforehand. A wildly wrong value surfaces as "voxelisation produced an empty solid", or as an automatic coarsening when the grid cap is hit, both of which reach the log.