2026-05-20-02-55-11 修复头部STL实体导出

This commit is contained in:
2026-05-20 03:02:30 +08:00
parent 68fb0cb564
commit b9c0f17313
5 changed files with 332 additions and 13 deletions

View File

@@ -0,0 +1,51 @@
# 实现方案:完整 STL 网格导出与实体填充增强
实现方案文档路径:`工程分析/实现方案-2026-05-20-02-55-11.md`
## 修改目标
修复大面数 STL 构件导出后在 ITK-SNAP 中呈散点的问题,使分割 NIfTI 的 Label Map 基于完整二进制 STL 三角面求交,并在每层生成连续实体区域。
## 涉及路径
- `WebSite/server.ts`
- `工程分析/需求分析-2026-05-20-02-55-11.md`
- `工程分析/实现方案-2026-05-20-02-55-11.md`
- `工程分析/测试方案-2026-05-20-02-55-11.md`
- `工程分析/经验记录.md`
## 技术路线
1. 保留 `createStlPreview` 作为网页预览和 bounds 统计来源。
2. 新增导出专用 STL 迭代逻辑,直接读取完整二进制 STL buffer 中的全部三角面,不再使用 `sampleLimit=200000` 的预览顶点数组作为 NIfTI 生成输入。
3. 对每个三角面执行当前位姿变换、DICOM 物理坐标映射、Mesh-Plane Intersection 和扫描线光栅化。
4. 将每个构件、每个 slice 的 rows 光栅化到临时 mask再做四边 flood fill填补闭合区域内部孔洞后写入最终 Label Map。
5. 保持 `visible/all` 分割范围、labels JSON、导出包结构不变。
## 执行步骤
1. 阅读当前导出生成链路,确认抽样来源和填充缺口。
2. 编写完整 STL 三角面读取辅助函数,避免长期缓存完整顶点数组。
3. 替换 `createSegmentationData` 中遍历 `payload.vertices` 的逻辑。
4. 增强后端 slice mask 的内部孔洞填补。
5. 运行类型检查、构建、导出接口验证。
6. 用脚本检查导出的 Label Map 体素分布,重点确认 `头部` label 不再只集中于少量散点。
7. 追加经验记录、提交、推送并重新部署。
## 兼容性与回滚方案
- API 参数和前端调用不变,外部使用者无需调整。
- 若完整 STL 导出耗时不可接受,可在后续引入后台任务或空间索引;本次先保证导出结果正确。
- 回滚时可恢复到上一个提交,但会重新暴露大面数构件抽样导致的散点问题。
## 预计文件变更
- `WebSite/server.ts`:新增完整 STL 迭代与 slice mask 填充逻辑。
- `工程分析/*-2026-05-20-02-55-11.md`:记录本次工作流。
- `工程分析/经验记录.md`:追加 A/B/C/D 经验。
## 提交与部署策略
- 只暂存本次相关代码和工程分析文档。
- commit message 包含 `2026-05-20-02-55-11` 和简要描述。
- 推送到 Gitea 后使用 `tmux` 会话 `revoxelseg-dicom` 重新部署。

View File

@@ -0,0 +1,65 @@
# 测试方案:头部 STL 实体导出验证
测试方案文档路径:`工程分析/测试方案-2026-05-20-02-55-11.md`
## 静态检查
- 运行 `npm run lint`,确认 TypeScript 类型检查通过。
- 运行 `git diff --check`,确认无空白错误。
结果:已通过。
## 构建检查
-`WebSite/` 执行 `npm run build`,确认生产构建通过。
结果已通过。Vite 仍提示单个 bundle 超过 500 kB此为既有体积提示不影响本次导出修复。
## 关键业务场景验证
- 启动临时服务或复用部署服务。
- 请求分割导出包:
- `targets=segmentation,pose`
- `format=nii.gz`
- `segmentationScope=visible`
- 解包并检查包含:
- `*-segmentation-label.nii.gz`
- `*-segmentation-labels.json`
- `*-pose-data.json`
结果:已通过。临时服务 `127.0.0.1:4100` 下:
- `segmentationScope=visible`HTTP 200约 431802 bytes用时约 6.56 s。
- `segmentationScope=all`HTTP 200约 667690 bytes用时约 8.51 s。
- 压缩包内包含分割 NIfTI、labels JSON 与 pose JSON。
## 医学影像数据相关边界验证
- 读取导出的 NIfTI header确认维度仍为 DICOM 同维度。
- 统计 label 体素分布,重点检查 `头部` 对应 label 的体素数量和 slice 分布不再表现为极少量散点。
- 检查导出包内 labels JSON 中 `头部.stl` 的 label 对应关系。
结果:已通过。
- NIfTI 维度:`512 x 512 x 300``vox_offset=352`
- `visible` 范围下 `头部.stl` 对应 label 7体素数 `11742597`,覆盖 slice `13-287`,共 `275` 层,最大单层 `83903` 体素。
- `all` 范围下 label 7 体素数 `9845171`label 8 `头颅.stl` 体素数 `1930290`,二者均覆盖 `275` 层。
- labels JSON 中 `头部.stl` 的 label/partId 为 `7`
## 部署验证
- 重新部署后验证:
- `http://127.0.0.1:4000/api/health`
- `http://127.0.0.1:4000/`
- 对部署后的导出包端点再做一次小样本验证。
## Git/Gitea 备份验证
- 提交信息包含 `2026-05-20-02-55-11` 和本次修复摘要。
- 推送到 Gitea 成功。
## 风险与回归关注点
- 完整 STL 导出比预览抽样更耗时,需要观察请求耗时。
- 多构件重叠时仍按现有顺序写入 label后写构件会覆盖先写构件。
- 旧的未暂存历史删除状态不应混入提交。

View File

@@ -1117,3 +1117,21 @@ C. 解决问题方案
D. 后续如何避免问题
任何分割影像导出都应同时考虑语义侧车文件,并保证侧车元数据与实际 mask 标签来自同一批样式和筛选条件。多文件导出优先做成一个后端归档包,避免浏览器多下载顺序、丢文件或元数据错配。
## 2026-05-20-02-55-11 大面数 STL 导出不能复用预览抽样网格
A. 具体问题
用户在 ITK-SNAP 中查看导出的分割 NIfTI 时,`头部` 类别仍呈散点或点云状,而原始 `头部.stl` 是完整实体表面。
B. 产生问题原因
后端 NIfTI 分割生成复用了 `createStlPreview(file, 200000)` 的预览顶点数组。`头部.stl` 约有 257 万个三角面,但导出时最多只保留 20 万个抽样面闭合轮廓被抽稀后Mesh-Plane Intersection 生成的扫描线交点不连续,实体填充会退化成稀疏点/线。
C. 解决问题方案
保留预览抽样数据仅用于 bounds 和网页显示NIfTI 导出路径改为逐三角读取完整二进制 STL buffer对全部网格面执行位姿变换、平面求交和扫描线光栅化。同时后端每个构件、每个 slice 先写入临时二值 mask再通过四边 flood fill 标记外部区域,将内部未连通孔洞补齐后写入最终 Label Map。
D. 后续如何避免问题
预览数据、抽样数据和医学导出数据必须明确分层。凡是 NIfTI、Mask、Label Map、体素化这类可用于医学工具复核的导出功能都不得复用用于前端性能优化的抽样网格验证时必须检查大面数构件的 label 体素数量、slice 覆盖范围和侧视/冠状重建是否连续。

View File

@@ -0,0 +1,51 @@
# 需求分析:修复头部 STL 导出后仍呈散点
开始时间2026-05-20-02-55-11
## 原始需求摘要
用户反馈在 ITK-SNAP 中打开导出的 `head-ct-demo-segmentation-label.nii` 后,“头部”类别仍呈散点形式;用户指出 STL 本体不是散点,头部 STL 在三维查看器中是完整实体表面,因此导出的分割影像也不应呈点云。
## 业务目标
- 分割影像导出必须基于真实 STL 网格生成实体 Label Map。
- 大面数构件(尤其 `头部.stl``头颅.stl`)不能因预览抽样导致闭合轮廓断裂。
- ITK-SNAP 中轴向、冠状、矢状视图应显示连续实体分割,而不是稀疏点云。
## 输入与输出
- 输入:
- 默认项目 `head-ct-demo`
- DICOM 原始序列。
- `Head_CT_ReConstruct/头部.stl` 等二进制 STL。
- 当前模型位姿、构件显隐与导出范围。
- 输出:
- 修复后的分割 NIfTI / NIfTI.GZ / 导出包。
- 工程分析文档与经验记录。
## 影响范围
- 后端 `WebSite/server.ts` 中 STL 到 NIfTI Label Map 的生成逻辑。
- 导出接口:
- `/api/projects/:projectId/export-mask`
- `/api/projects/:projectId/export-nifti`
- `/api/projects/:projectId/export-bundle`
- 前端下载入口原则上不需要改变,除非发现参数传递问题。
## 关键约束
- 保持 DICOM 维度、spacing、slice 数量与当前导出逻辑一致。
- 保持 labels JSON、位姿 JSON、压缩包结构兼容。
- 不将大型 DICOM/STL 数据或运行态导出文件纳入 Git 提交。
- 最终仍需重新部署并验证服务。
## 风险点
- 完整 STL 面数较大,不能把所有顶点长期存为大型 JS 数组造成内存膨胀。
- 需要避免继续复用预览抽样数据做医学导出。
- 若 STL 存在非闭合切口,仅靠扫描线偶奇填充可能仍出现孔洞,需要补充连通域填补。
## 默认假设
- 用户希望本次直接修复导出的 NIfTI 分割质量,而不是只解释原因。
- 头部散点的主要原因是后端导出使用了预览抽样网格,导致实体轮廓无法闭合。