# GPR 三维体 POC(B & C 双方案)实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 用真实 13G 雷达数据为 B(整卷上 GPU)与 C(分块+金字塔+核外)两套对等方案做 POC,验证技术可行性并挖出 spec 未预见的阻塞;POC 代码即生产地基与接口实现,不返工。 **Architecture:** 共用地基(解析/几何/结构化建体/int16 量化体/分块存储)+ `IVolumeRenderSource` 渲染接缝;`WholeVolumeSource`(B) 与 `OutOfCoreSource`(C) 是接口下两个永久并存实现,用户运行时按数据规模切换。落盘从第一天就分块,B 的裸分块格式是 C 金字塔/核外的基座。 **Tech Stack:** C++17, Qt6, VTK 9.6(`RenderingVolumeOpenGL2` GPU ray cast,自带 `vtkzlib`),GoogleTest,nlohmann-json(sidecar),现有 `src/{core,data,render,app}` 分层。 ## Global Constraints - **dtype**:雷达体走 **int16**(`vtkShortArray`),不污染反演剖面的 double 主路径(`ScalarVolume` 保持 `std::vector`,`src/core/model/Field.hpp:8-26`)。 - **量化**:物理值 ↔ int16 经 `scale/offset`,**必须贯穿**传递函数采样、色阶 LUT(`src/render/interact/SliceTool.cpp:37`)、取值/详情反量化(见 spec B §3.5)。 - **落盘**:**不用 `vtkHDFWriter`**(VTK 9.6 写不了 `vtkImageData`,记忆 `vtk96-hdfwriter-no-imagedata`)。用裸 int16 分块 + sidecar(json) + 逐块 `vtkzlib`。 - **渲染接缝**:上层(场景/切片)只面向 `IVolumeRenderSource`;B/C 是其两个实现。 - **结构化建体**:X(沿线)/Z(深度) 规则落格,仅 Y(14 通道) 向 1D 插值;**不**对雷达用全 3D 散点 IDW(现有 `IdwInterpolator` 无空间索引暴力,`src/core/algo/IdwInterpolator.cpp:15-33`)。 - **真实数据判定**:POC 用 `D:\Downloads\明星路`(450MHz/14通道/821采样/~45306道/线/20线/int16/13.6GB)。POC 过 = 在该真实数据上跑通并达标,不许避重就轻。 - **测试数据头实证**:`.iprh` 文本键值(`SAMPLES/LAST TRACE/CHANNELS/TIMEWINDOW/SOIL VELOCITY/DISTANCE INTERVAL`);`.iprb` = `int16[samples × traces]`,`samples×traces×2 == 文件大小`;`.ord` = 通道横向偏移(14 有效)。 --- ## 文件结构(决定分解与复用) ``` src/io/gpr/ ← 新增:雷达 IO(共用地基) IprHeader.{hpp,cpp} 解析 .iprh → 结构体 IprbReader.{hpp,cpp} 读 .iprb int16 B-scan(mmap/分块读) GprGeometry.{hpp,cpp} .ord 通道偏移 + .gps/.cor 逐道经纬 + 深度轴 GprSurvey.{hpp,cpp} 一个工区 = 线[]×通道[] + 几何(建体输入) src/core/model/ ScalarVolumeI16.hpp int16 体 + Quant{scale,offset} (新增,与 ScalarVolume 并列) src/core/algo/ GprVolumeBuilder.{hpp,cpp} 结构化建体:X/Z 落格 + Y 向 1D 插值 → ScalarVolumeI16 src/data/store/ ChunkedVolumeStore.{hpp,cpp} 分块 int16 + zlib + sidecar;读/写/按块取(B/C 共用,C 加金字塔) src/render/source/ IVolumeRenderSource.hpp 渲染接缝(接口) WholeVolumeSource.{hpp,cpp} B:读全块 → 1 个 vtkImageData OutOfCoreSource.{hpp,cpp} C:金字塔 + brick 分页 → 工作集 src/render/actors/ VoxelActor.cpp Modify:buildVoxel 增 int16 重载 + 量化域传函 tests/io/gpr/ tests/core/ tests/data/store/ ← 对应测试 tools/gpr_poc/ ← POC 度量台(建体/加载/显存/fps 探针 + CLI) ``` POC 度量统一进 `tools/gpr_poc`(建体耗时、输出维度、落盘体积/压缩比、加载耗时、显存、切片/体绘制 fps),B/C 用同一套指标对照。 > **POC vs 生产**:Task 1–6(地基)+ 7–8、10–11(接口/存储)是**生产代码,走 TDD、有完整代码**。Task 9、12–13 是**可行性探针**:给出明确实验、被测未知、通过/失败判据与度量,不预先杜撰我们正要验证的 VTK 核外内部实现——这是 POC 的本质,强行写"完整代码"等于造假。 --- ## Phase 0 — 地基(共用,生产级 TDD) ### Task 1: .iprh 头解析 **Files:** - Create: `src/io/gpr/IprHeader.hpp`, `src/io/gpr/IprHeader.cpp` - Test: `tests/io/gpr/test_ipr_header.cpp` **Interfaces:** - Produces: `struct IprHeader { int samples; long lastTrace; int channels; double timeWindowNs; double soilVelocity; double distanceInterval; };` + `IprHeader parseIprHeader(const std::string& text);` - [ ] **Step 1: 写失败测试** ```cpp #include "io/gpr/IprHeader.hpp" #include using geopro::io::gpr::parseIprHeader; TEST(IprHeader, ParsesKeyFieldsFromRealSample) { const std::string t = "SAMPLES: 821\nLAST TRACE: 45305\nCHANNELS: 14\n" "TIMEWINDOW: 160.352\nSOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.049084\n"; auto h = parseIprHeader(t); EXPECT_EQ(h.samples, 821); EXPECT_EQ(h.lastTrace, 45305); EXPECT_EQ(h.channels, 14); EXPECT_DOUBLE_EQ(h.timeWindowNs, 160.352); EXPECT_DOUBLE_EQ(h.soilVelocity, 100.0); EXPECT_NEAR(h.distanceInterval, 0.049084, 1e-9); } ``` - [ ] **Step 2: 运行确认失败** — `ctest -R IprHeader`,预期 编译/链接失败(未定义)。 - [ ] **Step 3: 最小实现** — `parseIprHeader` 逐行 `key: value` 拆分,按字段名填结构体;缺字段抛 `std::runtime_error`。 - [ ] **Step 4: 运行确认通过** — `ctest -R IprHeader`,预期 PASS。 - [ ] **Step 5: 提交** — `git commit -m "feat(gpr): parse .iprh header fields"` ### Task 2: .iprb B-scan 读取 **Files:** - Create: `src/io/gpr/IprbReader.{hpp,cpp}` - Test: `tests/io/gpr/test_iprb_reader.cpp` **Interfaces:** - Consumes: `IprHeader` - Produces: `struct BScan { int samples; long traces; std::vector data; /* [trace*samples + s] */ };` + `BScan readIprb(const std::string& path, const IprHeader& h);`(校验 `samples*traces*2 == fileSize`) - [ ] **Step 1: 写失败测试**(用临时文件造 4 道×3 采样 int16) ```cpp TEST(IprbReader, ReadsInt16AndValidatesSize) { // 写 tmp:samples=3, traces=4 → 24 bytes std::vector raw{0,1,2, 10,11,12, 20,21,22, 30,31,32}; auto path = writeTmp(raw); // helper geopro::io::gpr::IprHeader h{}; h.samples=3; h.lastTrace=3; // traces=lastTrace+1=4 auto b = geopro::io::gpr::readIprb(path, h); EXPECT_EQ(b.samples, 3); EXPECT_EQ(b.traces, 4); EXPECT_EQ(b.data[1*3 + 2], 12); // 第1道第2采样 } ``` - [ ] **Step 2: 运行确认失败**。 - [ ] **Step 3: 最小实现** — `traces = lastTrace+1`;读全文件为 int16;不匹配大小抛错。 - [ ] **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(gpr): read .iprb int16 b-scan with size check"` ### Task 3: 几何(通道偏移 + 逐道经纬 + 深度轴) **Files:** - Create: `src/io/gpr/GprGeometry.{hpp,cpp}` - Test: `tests/io/gpr/test_gpr_geometry.cpp` **Interfaces:** - Produces: - `std::vector parseChannelXOffsets(const std::string& ordText);`(取第 4 列==1 的有效通道横偏,明星路应得 14 个 -0.686..+0.686) - `double depthOfSample(int s, const IprHeader& h);`(`= s * (timeWindowNs/(samples-1)) * soilVelocity*1e-9/2`,单位米;soilVelocity 100 m/µs = 1e8 m/s) - [ ] **Step 1: 写失败测试** ```cpp TEST(GprGeometry, ParsesActiveChannelOffsets) { const std::string ord = "0 -0.686000 -1.5 1\n1 -0.581000 -1.5 1\n14 0 -1.5 0\n"; auto xs = geopro::io::gpr::parseChannelXOffsets(ord); EXPECT_EQ(xs.size(), 2u); // 仅 2 个有效(末列=1) EXPECT_NEAR(xs[0], -0.686, 1e-6); } TEST(GprGeometry, DepthOfLastSampleMatchesPhysics) { geopro::io::gpr::IprHeader h{}; h.samples=821; h.timeWindowNs=160.352; h.soilVelocity=1e8; EXPECT_NEAR(geopro::io::gpr::depthOfSample(820, h), 8.0, 0.05); // ~8m } ``` > 注:`soilVelocity` 单位换算在 Task 1 读入时统一成 m/s(100 m/µs = 1e8 m/s),在此基础上测试。 - [ ] **Step 2: 失败**。 - [ ] **Step 3: 实现** — `.ord` 按空白拆列、末列=="1" 收集第 2 列;`depthOfSample` 按公式。 - [ ] **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(gpr): channel offsets + depth axis geometry"` ### Task 4: int16 量化体类型 **Files:** - Create: `src/core/model/ScalarVolumeI16.hpp` - Test: `tests/core/test_scalar_volume_i16.cpp` **Interfaces:** - Produces: ```cpp struct Quant { double scale = 1.0; double offset = 0.0; int16_t toQ(double v) const; // round((v-offset)/scale),钳到[INT16_MIN+1,INT16_MAX] double toPhys(int16_t q) const; }; // q*scale+offset class ScalarVolumeI16 { // 行优先 idx=((k*ny+j)*nx+i),与 vtkImageData 一致 ScalarVolumeI16(int nx,int ny,int nz); int16_t& at(int i,int j,int k); int nx()const; ...; std::vector& data(); static constexpr int16_t kBlank = INT16_MIN; }; // 空值哨兵→透明 ``` - [ ] **Step 1: 写失败测试**(量化往返 + 索引布局 + blank) ```cpp TEST(ScalarVolumeI16, QuantRoundTripAndLayout) { geopro::core::Quant q{0.5, -10.0}; EXPECT_EQ(q.toQ(-10.0), 0); EXPECT_NEAR(q.toPhys(q.toQ(3.0)), 3.0, 0.25); geopro::core::ScalarVolumeI16 v(2,2,2); v.at(1,0,1) = 7; EXPECT_EQ(v.data()[(1*2+0)*2+1], 7); } ``` - [ ] **Step 2: 失败** → **Step 3: 实现** → **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(core): int16 scalar volume + quantization"` ### Task 5: 结构化建体 GprVolumeBuilder **Files:** - Create: `src/core/algo/GprVolumeBuilder.{hpp,cpp}` - Test: `tests/core/test_gpr_volume_builder.cpp` **Interfaces:** - Consumes: `GprSurvey`(线×通道 BScan + 几何)、`GridSpec`(复用 `src/core/algo/IInterpolator.hpp:7-13`) - Produces: `struct BuiltI16 { ScalarVolumeI16 vol; Quant quant; std::array origin, spacing; double vminPhys, vmaxPhys; };` `BuiltI16 buildGprVolume(const GprSurvey& s, const GridSpec& spec);` - **算法**:X(沿线)/Z(深度) 最近邻或线性落格(道已规则);**Y 向**对落在该 (x,z) 的 14 通道值做 1D 线性插值填充横向网格;maxDist/无覆盖 → `kBlank`。量化 scale/offset 由全体 min/max 定。 - [ ] **Step 1: 写失败测试**(2 通道、各 1 道×2 采样的人造 survey,验横向中点插值 + 维度) ```cpp TEST(GprVolumeBuilder, InterpolatesAcrossChannelsOnly) { auto s = makeTwoChannelSurvey(/*ch0 val=0, ch1 val=100, 横偏 0 和 1m*/); geopro::core::GridSpec spec{/*nx=*/3,/*ny=*/1,/*nz=*/1, 0,0,0, 0.5,1,1, 2.0, 9.9}; auto b = geopro::core::buildGprVolume(s, spec); EXPECT_NEAR(b.quant.toPhys(b.vol.at(1,0,0)), 50.0, 1.0); // 横向中点≈50 } ``` - [ ] **Step 2: 失败** → **Step 3: 实现**(先单线程,循环结构留可并行)→ **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(core): structured GPR volume builder (Y-only interp)"` ### Task 6: 分块存储 ChunkedVolumeStore(B/C 共用基座) **Files:** - Create: `src/data/store/ChunkedVolumeStore.{hpp,cpp}` - Test: `tests/data/store/test_chunked_volume_store.cpp` **Interfaces:** - Produces: ```cpp struct StoreMeta { int nx,ny,nz; int brick; // e.g. 64 std::array origin, spacing; Quant quant; double vminPhys,vmaxPhys; }; class ChunkedVolumeStore { static void write(const std::string& dir, const BuiltI16& b, int brick=64); // 分块+zlib+sidecar.json static StoreMeta readMeta(const std::string& dir); std::vector readBrick(int bx,int by,int bz) const; // 解压单块 // Task 10 追加:pyramid 层;Task 11 用 readBrick 做工作集 }; ``` - **格式**:`meta.json`(StoreMeta + 分块索引/偏移/压缩长度) + `data.bin`(逐块 zlib 压缩流)。zlib 用 VTK 自带 `vtkzlib` 或直接 zlib C API。 - [ ] **Step 1: 写失败测试**(write→readMeta→readBrick 往返 + 压缩后小于原始) ```cpp TEST(ChunkedVolumeStore, RoundTripBrickAndCompresses) { auto b = makeBuilt(128,128,128, /*可压缩模式*/); geopro::data::ChunkedVolumeStore::write(tmpDir, b, 64); auto m = geopro::data::ChunkedVolumeStore::readMeta(tmpDir); EXPECT_EQ(m.nx, 128); EXPECT_EQ(m.brick, 64); geopro::data::ChunkedVolumeStore s(tmpDir); auto blk = s.readBrick(0,0,0); EXPECT_EQ(blk.size(), 64u*64*64); EXPECT_LT(fileSize(tmpDir+"/data.bin"), 128u*128*128*2); // 压缩生效 } ``` - [ ] **Step 2: 失败** → **Step 3: 实现** → **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(data): chunked int16 volume store (zlib + sidecar)"` ### Task 7: VoxelActor int16 重载 + 量化域传递函数 **Files:** - Modify: `src/render/actors/VoxelActor.cpp`(增 int16 重载,参照现 double 版 `:41-79`) - Test: `tests/render/test_voxel_i16_smoke.cpp`(无窗渲染冒烟 / 或断言 image 标量类型与传函控制点在量化域) **Interfaces:** - Produces: `vtkSmartPointer buildVoxelI16(const ScalarVolumeI16& vol, const Quant& q, const ColorScale& cs, double ox,..,dz, vtkSmartPointer& outImage);` - **要点**:`vtkShortArray` 填值;传函/不透明度在**量化域 qmin/qmax** 加点(`q.toQ(vminPhys)`..`q.toQ(vmaxPhys)`),`kBlank→0` 不透明;`vtkSmartVolumeMapper`。 - [ ] **Step 1: 写失败测试**(构造小 int16 体,断言 `outImage->GetScalarType()==VTK_SHORT` 且传函在量化域采样不崩)。 - [ ] **Step 2: 失败** → **Step 3: 实现** → **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(render): int16 voxel actor with quantized transfer fn"` --- ## Phase 1 — POC-B(整卷上 GPU) ### Task 8: IVolumeRenderSource + WholeVolumeSource(B) **Files:** - Create: `src/render/source/IVolumeRenderSource.hpp`, `src/render/source/WholeVolumeSource.{hpp,cpp}` - Test: `tests/render/test_whole_volume_source.cpp` **Interfaces:** - Produces: ```cpp class IVolumeRenderSource { public: virtual ~IVolumeRenderSource()=default; virtual StoreMeta meta() const = 0; virtual void update(const Camera& cam) = 0; // B:首次载全量;C:按相机换块 virtual std::vector> currentProps() const = 0; // B:1 个;C:工作集 virtual vtkImageData* sliceSource() const = 0; }; // 供 SliceTool reslice class WholeVolumeSource : public IVolumeRenderSource { // 读全块拼 1 个 int16 vtkImageData explicit WholeVolumeSource(const std::string& storeDir); }; ``` - [ ] **Step 1: 写失败测试**(从 Task 6 写出的 store 构造 WholeVolumeSource,`currentProps().size()==1`,维度==meta)。 - [ ] **Step 2: 失败** → **Step 3: 实现**(遍历所有 brick 填进整卷 image)→ **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(render): IVolumeRenderSource + whole-volume source (B)"` ### Task 9: POC-B 真实数据度量(探针,含通过判据) **Files:** - Create: `tools/gpr_poc/main.cpp`(CLI:`gpr_poc build|renderB <明星路目录>`)、`tools/gpr_poc/Probe.{hpp,cpp}`(计时/显存/fps) - 复用:Task 1–8 全部。 **被验证的未知 / 阻塞点:** ①int16 GPU ray cast 在真机正常出图;②真实建体耗时与输出体积;③整卷 5~10GB 加载耗时、显存峰值;④切片拖动 fps、体绘制 fps;⑤超显存时 `vtkSmartVolumeMapper` 的 `LowResResample`(`MaxMemoryInBytes`) 自动降质观感。 - [ ] **Step 1:** `gpr_poc build 明星路` → 跑 Task 1–6,输出:建体耗时、`nx×ny×nz`、落盘体积、压缩比。记录。 - [ ] **Step 2:** `gpr_poc renderB` → WholeVolumeSource 上 `vtkSmartVolumeMapper`,量化传函着色,真窗口显示。 - [ ] **Step 3:** Probe 采集:加载耗时、显存峰值、切片拖动 fps(脚本化相机/切片移动)、体绘制旋转 fps。 - [ ] **Step 4:** 设 `MaxMemoryInBytes` 低于体大小,验证 `LowResResample` 自动降质路径出图。 - [ ] **Step 5:** 写 `docs/superpowers/plans/poc-results-B.md`:指标表 + 结论。 - **B 通过判据**:真实数据建体可完成且体积/耗时可接受;整卷在目标显存内出图;**切片拖动 ≥ 可用帧率(目标 ≥30fps)**;超显存时 LowRes 兜底可用。任一硬阻塞(如 GPU 不吃 short、显存必爆且 LowRes 不可接受)→ 记为 B 的落地风险并反馈 spec。 - [ ] **Step 6: 提交** — `git commit -m "test(gpr): POC-B real-data metrics harness + results"` --- ## Phase 2 — POC-C(分块+金字塔+核外,含最小真实分页器) ### Task 10: 金字塔生成(ChunkedVolumeStore 增量) **Files:** - Modify: `src/data/store/ChunkedVolumeStore.{hpp,cpp}`(加 LOD 层 + 每块 min/max) - Test: `tests/data/store/test_pyramid.cpp` **Interfaces:** - Produces: `void buildPyramid(int levels);`(逐级 2× 降采样,每层独立分块 + 每块 `int16 min,max` 存 meta);`std::vector readBrick(int level,int bx,int by,int bz);`;`std::pair brickRange(int level,int bx,int by,int bz);` - [ ] **Step 1: 写失败测试**(建 1 层金字塔,断言 level1 维度≈半、brickRange 命中真实 min/max)。 - [ ] **Step 2: 失败** → **Step 3: 实现** → **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(data): volume pyramid + per-brick min/max"` ### Task 11: brick 分页器(LRU 工作集,生产级 TDD) **Files:** - Create: `src/render/source/BrickPager.{hpp,cpp}` - Test: `tests/render/test_brick_pager.cpp` **Interfaces:** - Produces: ```cpp class BrickPager { // 内存恒定:驻留 ≤ budgetBricks 个解压块 BrickPager(const ChunkedVolumeStore& store, size_t budgetBricks); void requestVisible(const std::vector& visible, int level); // 载入缺失、LRU 淘汰 const std::vector* get(BrickId id, int level) const; // 命中返回,未命中 nullptr size_t residentCount() const; }; ``` - [ ] **Step 1: 写失败测试**(budget=4,请求 6 块 → residentCount==4,最早的被淘汰,命中/未命中正确)。 - [ ] **Step 2: 失败** → **Step 3: 实现**(LRU + 从 store 解压载入)→ **Step 4: 通过**。 - [ ] **Step 5: 提交** — `git commit -m "feat(render): bounded-memory brick pager (LRU)"` ### Task 12: OutOfCoreSource(C)— 最高风险探针:核外体绘制 **Files:** - Create: `src/render/source/OutOfCoreSource.{hpp,cpp}`(实现 `IVolumeRenderSource`) - 复用:Task 10/11。 **被验证的未知 / 阻塞点(C 的命门,必须正面撞):** 1. **VTK 能否渲染"动态换入换出的块工作集"为体**——把 BrickPager 的工作集作为多个 `vtkImageData` 喂给 `vtkMultiBlockVolumeMapper`(注意其"试图全量加载"语义,须只喂视野块)或多个 `vtkVolume` 叠加;验证可行性与正确性。 2. **块边接缝**:相邻 brick 渲染交界是否可见;试 `vtkMultiBlockVolumeMapper` 的抖动是否压得住。 3. **LOD 切换**:相机拉远用粗层、拉近换细层,切换是否闪烁/可接受。 4. **热路径解压**:拖动每帧换块时 zlib 解压是否拖垮帧率(CPU 瓶颈)。 > 本任务**不预写完整实现**——它就是要发现上面四点的真实结论。步骤是受控实验,产物是"能跑的最小版本 + 实测结论"。 - [ ] **Step 1:** `update(cam)` 内:算视野相交 brick + 选 LOD → `BrickPager::requestVisible` → `currentProps()` 返回工作集 image。 - [ ] **Step 2:** 用 `vtkMultiBlockVolumeMapper`(或 N×`vtkVolume`)渲染工作集,量化传函复用 Task 7。先静态相机出图,确认正确。 - [ ] **Step 3:** 接相机移动 → 动态换块;Probe 测 residentCount/内存恒定、换块 fps。 - [ ] **Step 4:** 逐项记录未知 1–4 的实测结论(接缝截图、LOD 切换录屏指标、解压占帧时间)。 - [ ] **Step 5:** 写 `poc-results-C.md`:四个未知的结论 + 是否构成阻塞 + 缓解手段。 - **C 通过判据**:工作集体绘制能正确出图且**内存恒定**;接缝/闪烁/解压三项**各自有可接受方案或明确缓解**(不可接受则记为阻塞并反馈 spec C,这正是 POC 的目的)。 - [ ] **Step 6: 提交** — `git commit -m "test(gpr): POC-C out-of-core volume render probe + results"` ### Task 13: 切片核外 + B/C 切换贯通 **Files:** - Modify: `src/render/source/OutOfCoreSource.cpp`(`sliceSource()`:只读切面相交块拼子体供 reslice) - 复用现有 `SliceTool`(`src/render/interact/SliceTool.cpp`)对 source 给出的 image 切片。 **被验证的未知:** 切片只读相交块时的内存/fps;同一 `IVolumeRenderSource` 下 B↔C 运行时切换无缝。 - [ ] **Step 1:** `OutOfCoreSource::sliceSource()` 按当前切面算相交 brick → 拼最小子体 image。 - [ ] **Step 2:** `SliceTool` 对该 image reslice,拖动切片测 fps、内存。 - [ ] **Step 3:** POC 台加 `renderC` 与 `--source whole|ooc` 开关,验证同一上层代码切两实现。 - [ ] **Step 4:** 补 `poc-results-C.md`:切片核外指标 + 切换验证。 - [ ] **Step 5: 提交** — `git commit -m "feat(render): slice out-of-core + runtime B/C source switch"` --- ## Self-Review(对照 spec 检查) **1. spec 覆盖** - spec B:int16 路径(Task 4/7)、结构化建体(Task 5)、裸分块落盘(Task 6)、量化贯穿(Task 4/7/Global)、整卷渲染(Task 8/9)、LowResResample(Task 9) ✓ - spec C:分块(Task 6)、金字塔+min/max(Task 10)、brick 分页(Task 11)、核外体绘制(Task 12)、切片核外(Task 13)、B/C 切换(Task 13) ✓ - 共有地基:.iprb/.iprh 解析(Task 1/2)、几何/配准(Task 3)、GPR→体(Task 5) ✓ - **缺口(POC 不覆盖,明确记录)**:ddCode 接入数据集树/UI、后端持久化对接、异常/色阶编辑器接线——POC 阶段不做(spec A §2 / B §6 列为成品阶段),不影响复用。 **2. 占位符扫描**:地基任务(1–8,10,11)均有完整测试代码与实现描述;POC 探针(9,12,13)按设计**有意不写杜撰实现**,代之以受控实验 + 通过判据(已在任务内注明本质)。无 "TODO/TBD/适当处理" 类空话。 **3. 类型一致性**:`ScalarVolumeI16`/`Quant`/`BuiltI16`/`StoreMeta`/`IVolumeRenderSource`/`BrickPager` 在定义任务与消费任务间签名一致;`buildGprVolume`/`buildVoxelI16`/`ChunkedVolumeStore::{write,readMeta,readBrick}`/`requestVisible` 全程同名。 --- ## 关键风险(开工即知) - **Task 12 是月级生产工程的"最小探针"**:POC 只需证可行 + 撞出阻塞,不追求生产质量分页器;但若未知 1(VTK 渲染动态工作集)撞墙,是 C 的根本阻塞,须立刻反馈 spec C 并评估替代(OpenVDS / 自建 GL)。 - **真实 13G 在合理分辨率下可能装得进显存** → B 顺过、C 的核外价值要靠"细分辨率/拼全路段大体"的真实配置才能压出(Task 9/12 的网格参数留 CLI 可调,用同一份真实数据调出超显存体来考验 C)。 - **量化贯穿**漏一处即色阶/读数错(Global 约束 + Task 4/7),Self-Review 已盯。