Measurements & AnalysisChinese & English

Generate Measurement Boxes For Teeth

Input is one MultiROI whose every label is one tooth. For each tooth this plugin builds a standardised measurement box oriented by that tooth's own anatomy, splits it into a buccal/labial and a lingual/palatal half, and

Updated 2026-09-10User manual

Generate Measurement Boxes For Teeth(为牙齿生成标准化测量盒)

Generate Measurement Boxes For Teeth - User Manual

Dragonfly Prototype Apps · Generate Measurement Boxes For Teeth...

版本 Version 1.0 · 2026-09-08


第一部分 中文手册

目录

1. 简介

2. 坐标系:哪一部分来自牙齿,哪一部分来自牙弓

3. 测量盒不是牙齿的外接盒

4. CEJ 有意不做自动识别

5. 唇舌侧分割与分区

6. 体积:盒子决定在哪里统计,掩膜决定统计什么

7. 如何看牙弓预览图

8. 命名

9. 已在运行中的 Dragonfly、用真实患者数据验证

10. 环境需求与已知边界

1. 简介

输入是一个 MultiROI,其中每个 label 就是一颗牙。本插件对每颗牙按其自身解剖结构建立局部坐标系,生成一个标准化测量盒,再把它分成唇/颊侧与舌/腭侧两半;若给出牙槽骨掩膜,还会统计每一半以及每个分区小盒内的骨量。

五个页签就是工作流本身,每一步只有在上一步产出结果后才会开启:1 数据源 → 2 坐标轴 → 3 测量盒 → 4 分区 → 5 发布。

在点「发布」之前,本插件不会创建或修改会话中的任何对象。数据源只读取,坐标轴只拟合,测量盒与分区只计算;你可以反复调整外扩量而完全不动数据。

本插件不做牙齿分割。单牙实例分割、粘连拆分与牙位编号属于你现有的流程,MultiROI 是本插件的输入。

「数据源」页的一键执行全部步骤(按当前设置)会无人值守地跑完整个流程:读取牙齿 MultiROI → 拟合坐标轴 → 生成测量盒与分区 → 发布,每一步都使用你按下按钮那一刻各自页签上显示的设置。其中只有「发布」会向会话写入对象,它被特意包含在内,这样回来时测量盒与体积表就已经在会话里了。所有页签始终可以打开和修改参数,以便在运行之前检查各方向的规则与命名;某一步所需的输入尚不存在时,按下它自己的按钮会给出提示,说明缺的是哪一步。

2. 坐标系:哪一部分来自牙齿,哪一部分来自牙弓

测量盒由三个方向确定,而它们并非都来自同一处:

  • Z,牙体长轴 —— 由该牙自身的体素做主成分分析得到。根尖在哪一端,由两端最外侧 1 mm 薄层的体素数判定:在固定薄层厚度下体素数正比于该处横截面积,根尖约 1 mm²,冠尖约 9 mm²。实测 14/14 正确,余量 4.4–8.4 倍。
  • Y,唇舌方向 —— 该牙处牙弓的外向法线。+Y 为唇/颊侧。
  • X,近远中方向 —— 牙弓切线,即 Z × Y。

⚠ 一颗牙自身的形状无法区分近远中与唇舌,而且这种错误毫无征兆。它的两个横截面尺寸往往只相差百分之几,此时它自身的第二主轴实际上是由噪声在二者之间做选择。体模实测:一颗 7.0 × 7.1 mm 的牙(相差 1.4%,完全落在真实解剖与分割误差范围内),仅凭自身坐标系在 40 次随机倾斜中40 次全错,偏差 73°。由此生成的盒子绕长轴转了约 3/4 直角,「唇侧半盒」大半其实是近中半盒:体积数字是错的,而且看起来完全正常。

这也是本插件不使用 Dragonfly 自带 OBB(有向外接盒)的原因。它以体积最小为目标,根本无从判断三个轴中哪一个是近远中;而在 Dragonfly 2025.1 上,这段代码以及它依赖的 trimesh 库都不存在。

已用医生手工绘制的测量盒验证:在 6 颗下颌前牙上,本插件算出的近远中与唇舌方向与医生自己选定的方向中位数相差 2.5°。医生的长轴与世界 Z 轴夹角为 19.6°–28.1°,所以轴对齐的盒子从一开始就不可行。

全牙列按牙根朝向自动分成两个牙弓 —— 上下颌牙根方向相差约 180°,而同一牙弓内任意两颗牙都远达不到这个角度。归属则由位置决定;若某颗牙自身的牙根方向与所属牙弓不一致,会被标记出来,而不是被挪走。

3. 测量盒不是牙齿的外接盒

从医生自己的会话中读出(下颌 6 颗前牙,CBCT 体素 0.30 mm),测量盒实际是:

  • 近远中方向比牙齿更窄 —— 3.0–4.6 mm,而下颌中切牙约 5.3 mm:它取的是中央一层;
  • 唇舌方向约为牙齿的两倍 —— 总宽 10.2–14.8 mm,因为要覆盖唇舌两侧骨板;
  • 高度短于牙根 —— 5.3–8.5 mm,而牙根约 13 mm。

因此每个方向各由一条具名规则决定,并且每个盒子都会记录是哪条规则生成了哪个面,任何一个数字都能追溯到一次明确的选择。近远中与唇舌方向可选:牙齿自身宽度的比例、牙齿宽度加外扩量、绝对宽度,或恰好等于牙齿宽度。长轴方向可选:CEJ 平面到根尖、CEJ 到根尖长度的比例、CEJ 根方固定距离、绝对高度,或整颗牙。

唇舌方向的外侧面可以分侧设定。医生自己的两半盒就是不等宽的 —— 某颗牙唇侧 6.45 mm、舌侧 3.73 mm —— 而它们的近远中与长轴尺寸完全相同,所以这两个方向始终共用。

面板会在每个控件旁显示换算出的毫米值,并同时给出医生实测的尺寸范围供对比。

4. CEJ 有意不做自动识别

专利中盒高的规则是「CEJ 平面 → 根尖」,而 CEJ 是釉质与牙骨质的交界,本质上是灰度边界。若只有牙齿掩膜,唯一可用的线索是牙颈部最细截面,而它经过实测后被否决。

⚠ 在体模上,横截面积从牙冠最丰满处经 CEJ 一直到牙根是单调下降的,根本不存在可供寻找的极小值。「牙冠最大截面之后的第一个局部极小」给出的平面与真实 CEJ 相差 7.75 mm;120 次运行中误差是恒定的 2.1–8.3 mm 系统偏差,而非随机噪声。2 mm 相当于 5.7 mm 盒高的约 35%。

因此 CEJ 平面由三种可核查的方式给出:冠尖到根尖长度的比例(默认,创建任何对象之前即可预览)、距冠尖或距根尖的绝对距离,或你在会话中放置的标注点 —— 即专利中的医生校核点。

5. 唇舌侧分割与分区

分界面是通过牙体长轴、以唇舌方向为法线的平面。医生自己画的两个盒子恰恰就是这样:方向矩阵完全一致,近远中与长轴尺寸相同,两个中心的距离恰好等于两半宽度之和的一半 —— 这正是「两个盒子共用一个面」的代数特征。因此本插件生成一个母盒再分割,绝不分别拟合两个盒子。

可选的进一步分区,默认全部关闭,且全部在盒子自身坐标系内进行:长轴方向可分两段(冠方/根方)、三段,或按 CEJ 根方固定距离(专利中的 4 / 6 / 8 mm);近远中方向可分近中/中央/远中;唇舌方向还可再分层。

每个分区小盒成为已发布 MultiROI 的一个 label,并按解剖位置命名 —— 于是专利中的四个核心区域(唇侧冠方、唇侧根方、舌侧冠方、舌侧根方)就是一个对象里的四个 label,而不是十六个各自独立的 ROI。

6. 体积:盒子决定在哪里统计,掩膜决定统计什么

测量盒本身不测量任何东西。给出牙槽骨 ROI,或分割 MultiROI 的某个 label 后,每个分区的体积为 V = N × Δx × Δy × Δz,N 为其中的骨体素数;牙体、牙髓、空气、邻牙以及一切非目标结构,都由你指定的掩膜排除。

⚠ 没有骨掩膜时,本插件只报告测量盒体积,并明确说明这一点。它不会用自己臆造的阈值来替代:那样的静默回退会在每一份数据上测出不同的量。

按分区与按牙分别报告:体素数、体积,以及骨在该分区中所占的比例。CSV 每个分区一行,包含坐标轴、尺寸、每个面的生成规则与体素尺寸 —— 论文里的一个数字可以由它自己那一行完整复现,第二个时间点也可以复用同一个盒子。

实际发布的栅格化由 Dragonfly 自身在源网格上完成,因此体素计数与图像通道、与你以往的数据是可比的;随后它会与本插件自己的栅格化逐盒比对,并把任何差异显示出来 —— 测试所验证的算法与你实际得到的结果是同一套。

7. 如何看牙弓预览图

右侧面板从咬合面方向绘制牙弓:每颗牙的质心、穿过它们的牙弓曲线、每个盒子的投影轮廓,以及每颗牙的两个方向箭头。

暖色箭头是唇/颊侧方向,每一个都必须指向牙弓外侧。这是这些盒子里唯一无法靠读数字来核对的东西 —— 一个绕牙体长轴转了 90° 的盒子,看起来仍然像个合理的盒子。冷色线段是近远中方向。

点击盒子投影可以看到该牙坐标系的来源、盒子尺寸,以及与它相关的所有警告。本插件没有把握的牙(唇舌方向未确定、两端形态相近、长轴与两侧邻牙都相差过大)会用警告色画出。

勾选在视图中绘制可以把盒子放进 Dragonfly 自己的 2D/3D 视图。它们是真实对象,不是预览:取消勾选即可删除。

8. 命名

默认模板重现了医生本来就在手工输入的命名:{tooth}{side}{stamp} 得到 41b0 —— FDI 牙位号、b 或 l 表示唇/颊侧或舌/腭侧、再加时间点后缀。ROI 名称还可以带上唇舌侧文字,于是 ROI 就是 41b0 颊侧,与会话中已有的命名一致。

FDI 牙位号取自 MultiROI 的 label 名称(若有)。若没有一个 label 名称像 FDI 牙位号,则所有牙齿使用同一套规则、测量盒按 label 编号命名,并且面板会明确说明这一点,而不是去猜。

按牙位类型的预设只有在 label 带 FDI 号时才可用,而这确实有意义:尖牙牙根比中切牙约长 3 mm,医生自己的盒子也正好反映了这一点 —— 33 号牙 8.5 mm,41 号牙 5.3 mm。

9. 已在运行中的 Dragonfly、用真实患者数据验证

以上内容都已经在运行中的 Dragonfly 里、用医生自己的会话与他们手工绘制的测量盒逐项核对过 —— 不只是体模验证。

  • 上下颌被正确分开并排序。一份 24 颗牙的分割被分成两个各 12 颗的牙弓,每个牙弓首尾顺序完全正确,24 颗牙的唇/颊侧方向全部指向牙弓外侧。
  • 坐标轴与医生自己选定的方向一致:长轴中位数相差 1.8°,近远中 4.1°,唇舌 3.8°。
  • 两套栅格化结果完全一致。用医生自己的盒子,本插件与 Dragonfly 各自栅格化:3625 / 3625、3247 / 3247、3862 / 3862、10605 / 10605 个体素,差异为 0。
  • 医生自己的骨 ROI 有 100.00% 落在本插件对其同一个盒子的栅格化范围内,所检查的 4 个全部如此。

默认规则给出的尺寸也很接近医生手工画的结果(这并非刻意设计):6 颗牙中有 5 颗的高度相差在 0.4 mm 以内。默认值仍然只是默认值 —— 测量盒不是牙齿的外接盒,规则由你决定 —— 但你的起点已经接近自己的习惯。

⚠ live 验证发现了体模发现不了的问题:上下颌分离的阈值原本是按「上下颌相距 30–40 mm」设定的。实际并非如此 —— 上下牙列相互咬合,真正起作用的是牙齿质心,二者只相距约 16 mm,实测间隙只有 6–8 mm。旧阈值把上下颌当成了一个牙弓、把两颌的牙交替排列,结果某颗前牙的近远中方向偏差 57°,而长轴却依然完全正确。该问题已修复,并且体模现在会复现真实的 6–8 mm 间隙,因此不会再悄悄回归。

10. 环境需求与已知边界

无需安装、联网、GPU 或虚拟环境:进程内 PyQt6 加 Dragonfly 自带的 numpy。Dragonfly 2025.1 与 2027.1 均支持。

  • 读不到体素尺寸时拒绝运行;若本插件与 Dragonfly 对某个体素在世界坐标中的位置判断相差超过千分之一个体素,同样拒绝运行,并同时给出两个坐标 —— 否则每个盒子都会放错位置。
  • 骨掩膜若与牙齿 MultiROI 不在同一网格上,会被拒绝而不会被重采样:重采样会悄悄改变本插件所报告的体素计数。
  • 任一方向薄于 3 个体素的盒子会被连同原因一起拒绝,因为低于这个厚度分区平面已无法区分任何内容。无论是否拒绝,请求尺寸与实际实现尺寸都会同时以毫米和体素数报告 —— 在 0.30 mm 网格上,3.03 mm 就是 10.1 个体素。
  • 孤立的单颗牙仍然会生成盒子,但它的唇/颊侧方向是任意的,面板与警告都会明确说明。

未包含:彩色热图与多时间点的 ΔV / 变化率 —— 它们消费本插件的 CSV,属于下游环节。


Part II English Manual

Contents

1. Introduction

2. The frame: what comes from the tooth, and what from the arch

3. The box is not a fit to the tooth

4. The CEJ is not detected automatically, on purpose

5. The buccal/lingual split, and the partition

6. Volumes: the box says where, the mask says what

7. Reading the arch preview

8. Naming

9. Verified on a real patient, in a running Dragonfly

10. Requirements and limits

1. Introduction

Input is one MultiROI whose every label is one tooth. For each tooth this plugin builds a standardised measurement box oriented by that tooth's own anatomy, splits it into a buccal/labial and a lingual/palatal half, and — given an alveolar-bone mask — counts the bone inside each half and each sub-partition.

The five tabs are the workflow, and each opens only once the step before it produced something: 1 Source → 2 Axes → 3 Box → 4 Partition → 5 Publish.

Nothing in the session is created or modified until you press Publish. Source reads, Axes fits, Box and Partition compute; you can hunt for a margin all day without touching the data.

This plugin does not segment teeth. Single-tooth instance segmentation, splitting adhesions and FDI numbering belong to your own pipeline; the MultiROI is the input.

Execute All Steps with Current Settings on the Source tab runs the whole workflow unattended - read the tooth Multi-ROI, fit the axes, build the boxes and their cells, publish - each step with whatever its own tab shows at the moment you press it. Publishing is the only step that writes to the session and it is included on purpose, so the boxes and the volume table are there when you come back. Every tab stays open at all times so the rules and the naming can be read and changed BEFORE the run; a step whose input does not exist yet refuses when you press ITS button and says which step is missing.

2. The frame: what comes from the tooth, and what from the arch

Three directions define the box, and they do not all come from the same place:

  • Z, the long axis — from the tooth's own voxels, by principal component analysis. Which end is the root apex is decided by the voxel count in the extreme 1 mm slab at each end, which at a fixed slab thickness is the cross-sectional area there: about 1 mm² at an apex against 9 mm² at a crown tip. Measured 14/14 correct with a 4.4×–8.4× margin.
  • Y, labiolingual — the dental arch's outward normal at that tooth. +Y is buccal/labial.
  • X, mesiodistal — the arch tangent, i.e. Z × Y.

⚠ A tooth's own shape cannot tell mesiodistal from labiolingual, and the failure is silent. Its two cross-sectional widths differ by a few percent, so its second principal axis chooses between them by noise. Measured on a phantom tooth 7.0 × 7.1 mm — a 1.4% difference, well inside real anatomic and segmentation variation — the tooth-only axis was wrong in 40 of 40 random tilts, 73° off. A box built on it is rotated about three quarters of a right angle about the long axis, so the "buccal half" is mostly the mesial half: the volume is wrong and completely plausible.

This is also why Dragonfly's own oriented-bounding-box helper is not used. It minimises volume and has no idea which of its three axes is mesiodistal — and on Dragonfly 2025.1 it does not exist at all, nor does the trimesh library it needs.

Validated against a clinician's own hand-built boxes: the mesiodistal and labiolingual axes this plugin computes agree with the ones they chose themselves to a median of 2.5° across six mandibular anterior teeth. Their long axes are tilted 19.6°–28.1° from the world Z axis, so an axis-aligned box was never an option.

A full dentition is split into its two arches by the direction the roots point — opposing jaws are about 180° apart, which no two teeth in one jaw come close to. Membership then comes from position, and a tooth whose own root direction disagrees with its arch is flagged, not moved.

3. The box is not a fit to the tooth

Read out of a clinician's own session (six mandibular anterior teeth, CBCT voxel 0.30 mm), the box is:

  • narrower than the tooth mesiodistally — 3.0–4.6 mm, against a ~5.3 mm mandibular central incisor: it samples a central slab;
  • about twice as wide labiolingually — 10.2–14.8 mm total, because it has to reach across both bone plates;
  • shorter than the root — 5.3–8.5 mm against a ~13 mm root.

So each direction takes one named rule, and every box records which rule produced each face, so a number can always be traced back to a decision. Mesiodistally and labiolingually: a fraction of the tooth's own width, the tooth's width plus a margin, an absolute width, or exactly the tooth's width. Along the long axis: the CEJ plane down to the apex, a fraction of the CEJ-to-apex length, a fixed distance below the CEJ, an absolute height, or the whole tooth.

The labiolingual outer face can be set per side. A clinician's own two halves are unequal — 6.45 mm buccal against 3.73 mm lingual on one tooth — while their mesiodistal and long-axis sizes are identical, so those two are always shared.

The panel prints the resulting millimetres beside every control, plus the clinician's own measured range for comparison.

4. The CEJ is not detected automatically, on purpose

The patent's rule for the box height is CEJ plane → apex, and the CEJ is where enamel meets cementum: a grey-value boundary. From a tooth mask the only handle is the cervical constriction, and it was measured and rejected.

⚠ On the phantom the cross-sectional area falls monotonically from the crown bulge through the CEJ into the root, so there is no minimum to find. "The first local minimum after the crown maximum" returned a plane 7.75 mm from the truth, and across 120 runs the error was a constant 2.1–8.3 mm bias, not noise. A 2 mm error is about 35% of a 5.7 mm box height.

So the CEJ plane is set one of three checkable ways: a fraction of the crown-to-apex length (the default, previewed before anything is created), an absolute distance from the crown tip or the apex, or an annotation you placed in the session — the patent's doctor-confirmed point.

5. The buccal/lingual split, and the partition

The split plane is the plane through the tooth's long axis with the labiolingual normal. That is exactly what a clinician's own two boxes turn out to be: bit-identical direction matrices, identical mesiodistal and long-axis sizes, and centres separated by exactly half the sum of their widths — the algebraic signature of two boxes sharing a face. So one parent box is built and split; two independent boxes are never fitted.

Optional further partition, all off by default and all in the box's own frame: along the long axis into halves (coronal / apical), thirds, or fixed distances below the CEJ (the patent's 4 / 6 / 8 mm levels); mesiodistally into mesial / central / distal; and further sub-shells labiolingually.

Every cell becomes one label of a published MultiROI, named anatomically — so the patent's four core regions (buccal-coronal, buccal-apical, lingual-coronal, lingual-apical) come out as four labels of one object rather than sixteen separate ROIs.

6. Volumes: the box says where, the mask says what

The box on its own measures nothing. With an alveolar-bone ROI, or one label of a segmentation MultiROI, the volume of each cell is V = N × Δx × Δy × Δz over the bone voxels inside it, with teeth, pulp, air, neighbouring teeth and every non-target structure excluded by the mask you name.

⚠ Without a bone mask the plugin reports box volumes only and says so. It will not substitute a threshold of its own: a silent fallback there would measure a different quantity on every dataset.

Reported per cell and per tooth: the voxel count, the volume, and the fraction of the cell the bone occupies. The CSV carries one row per cell with the frame axes, the extents, the rule behind every face and the voxel size, so a number in a paper can be reproduced from its own row — and a second timepoint can reuse the same box.

The rasterisation that gets published is Dragonfly's own, on the source grid, so a voxel count is comparable with the channel and with your historical numbers. It is then compared box by box against this plugin's own rasteriser and any disagreement is shown — the arithmetic the tests check is the arithmetic you got.

7. Reading the arch preview

The right-hand panel draws the arch from the occlusal side: each tooth's centroid, the arch through them, each box's footprint, and two arrows per tooth.

The warm arrow is BUCCAL, and every one of them must point out of the arch. That is the one thing about these boxes which cannot be checked by reading a number, because a box rotated 90° about the tooth's long axis still looks like a reasonable box. The cool line is the mesiodistal direction.

Click a box footprint to see where that tooth's frame came from, its box size, and any warning about it. A tooth the plugin is unsure of — buccal unresolved, both ends alike, an axis far from both neighbours — is drawn in the warning colour.

Tick Draw in the views to put the boxes into Dragonfly's own 2D and 3D views. Those are real objects, not a preview: untick to remove them again.

8. Naming

The default template reproduces what a clinician already types by hand: {tooth}{side}{stamp} gives 41b0 — the FDI number, b or l for buccal or lingual, then a timepoint stamp. The ROI titles can additionally carry a side word, so an ROI comes out as 41b0 颊侧, matching what is already in the session.

The FDI number is taken from the MultiROI's label name when it has one. When no label name looks like an FDI number, one rule applies to every tooth, the boxes are named after the label, and the panel says so rather than guessing.

Per-tooth-type presets are only available when the labels carry FDI numbers, and they matter: a canine root is about 3 mm longer than a central incisor's, and the clinician's own boxes track that — 8.5 mm for tooth 33 against 5.3 mm for tooth 41.

9. Verified on a real patient, in a running Dragonfly

Everything above was checked live, through a running Dragonfly, against a clinician's own session and their own hand-built boxes — not only against a phantom.

  • The arches split and ordered correctly. A 24-tooth segmentation came out as two arches of 12, each in perfect anatomic order end to end, and buccal pointed out of the arch on 24 of 24 teeth.
  • The axes agree with the clinician's own to a median of 1.8° on the long axis, 4.1° mesiodistally and 3.8° labiolingually.
  • The two rasterisers agree exactly. Their own boxes, rasterised by this plugin and by Dragonfly: 3625 / 3625, 3247 / 3247, 3862 / 3862, 10605 / 10605 voxels — zero difference.
  • Their own bone ROIs sit 100.00% inside this plugin's rasterisation of their own boxes, on all four checked.

The default rules also landed close to what they drew by hand, which was not designed for: heights within 0.4 mm on five of six teeth. The defaults are still defaults — the box is not a fit to the tooth and the rules are yours to set — but you are starting near your own practice.

⚠ What the live run caught, and the phantom could not: the jaw-splitting threshold was set from "the jaws are 30-40 mm apart". They are not — the arches interdigitate, so the tooth CENTROIDS are only ~16 mm apart and the measured gap is 6-8 mm. The old threshold put both jaws in ONE arch, interleaved them, and put 57° of error into a front tooth's mesiodistal axis while the long axis stayed perfect. It is fixed, and the phantom now reproduces the real 6-8 mm gap so it cannot come back.

10. Requirements and limits

No install, no internet, no GPU, no venv: in-process PyQt6 and the numpy Dragonfly already ships. Works on Dragonfly 2025.1 and 2027.1.

  • The run is refused when the voxel size cannot be read, and also when this plugin and Dragonfly disagree by more than a thousandth of a voxel about where a given voxel lies in world coordinates — both positions are printed, because otherwise every box would be placed wrongly.
  • A bone mask on a different grid from the tooth MultiROI is refused, not resampled: resampling would quietly change the voxel count the whole plugin reports.
  • A box thinner than 3 voxels in any direction is refused with the reason, because below that the partition planes cannot separate anything. The requested and realised extents are reported in millimetres and in voxels either way — at a 0.30 mm grid a 3.03 mm extent is 10.1 voxels.
  • A single isolated tooth still gets a box, but its buccal direction is arbitrary and both the panel and the warnings say so.

Not built: the colour heat map and the multi-timepoint ΔV / ΔV%, which consume this plugin's CSV and belong downstream.

You’ve reached the end of this manual.Explore the library →