# 雷达 B-Scan 详情页开发方案(修订版)— 2026-06-29 > 基于用户确认的 4 项关键决策修订:① 底部页签容器 ② dd_radar_2d/dd_radar_3d > ③ 本地文件加载 ④ 单通道处理管线 + 用户提供算法代码。 > 基线分支:`radar`。上一版见同目录 `2026-06-29-radar-bscan-development-plan.md`。 --- ## 1. 一句话架构 **雷达 B-Scan = 现有底部页签引擎(`DatasetDetailController` + `ChartStrategyRegistry`)内的一个全新 `ViewKind::BScanProfile` 视图**。`BScanProfileView` 内部自含紧凑工具条 + 左右分割(B-Scan 自绘 canvas + A-Scan Qwt 曲线)+ 底部状态栏。数据从前端本地 `.iprb/.iprh` 文件读取,处理管线在 `core/gpr_proc` 纯 C++ 层跑,用户提供各算法实现填入管线节点。 --- ## 2. 相比上一版的关键调整 | 项 | 上一版 | 修订版 | |---|---|---| | 容器 | 独立 ADS Dock | **底部 `DatasetDetailPanel` 页签**(走 `DatasetDetailController`) | | ddCode | 待定 | **`dd_radar_2d`**(单通道)/**`dd_radar_3d`**(多通道阵列) | | 数据入口 | 后端 API 或本地文件 | **纯本地文件**(`ApiDatasetRepository` 对 radar loaderKey 本地处理) | | 采集参数 | 后端 API 或头文件 | **全从 `.iprh` 头文件解析**(复用/扩展现有 `IprHeader`) | | 处理算法 | 我实现 | **用户提供代码**,我只定义接口 + 管线框架 | | 多通道 | 单通道独立跑 | **全载内存,顶部工具条通道切换,处理管线按当前选中单通道跑** | --- ## 3. 数据流(底部页签路线,端到端) ``` [DatasetListPanel 双击雷达数据集 item] main.cpp:1501 读 kDsIdRole + kDsDdCodeRole="dd_radar_2d"(或 dd_radar_3d) main.cpp:1509 detailCtrl.openDataset(dsId, "dd_radar_2d", dsName, tmObjectId) │ ▼ [DatasetDetailController::openDataset] registry_.find("dd_radar_2d") → Radar2dStrategy strategy->tabs() → [ { "B-Scan剖面", ViewKind::BScanProfile, "radar.profile", lazy=false }, { "采集参数", ViewKind::Table, "radar.info", lazy=false }, { "异常列表", ViewKind::Table, "radar.anomalies", lazy=false } ] emit datasetOpened(...) → DatasetDetailPanel 建页 loadTab(dsId, "dd_radar_2d", 0) → loadTabImpl("radar.profile") │ ▼ [ApiDatasetRepository::loadAsync("radar.profile", dsId)] // 不走网络!本地文件直接读取: 1. 根据 dsId 推断本地路径(约定见 §4) 2. io::gpr::parseIprHeader(iprhPath) → 采集参数 3. io::gpr::readIprb(iprbPath, header) → 单通道 B-scan(2d) 或 assembleGprSurvey(channelPaths, ordPath) → 多通道 GprSurvey(3d) 4. 组装 GprProfilePayload{原始数据, 头信息, 通道列表} 5. return new LocalDetailLoad(payload) // 同步完成,立即 emit done │ ▼ [DatasetDetailController::tabReady] emit tabReady(dsId, 0, payload) │ ▼ [DatasetDetailPage::setTabPayload] views_[0]->setPayload(payload) // BScanProfileView 自解包 │ ▼ [BScanProfileView] 1. 解包 GprProfilePayload → 填充通道选择下拉 2. 默认显示通道 0 的原始数据 3. 若用户切换到 proc_data_1 标签: → 用当前 ProcessingParams 跑 GprProcessingPipeline → 管线输出 proc_data → ColorMapper → 重绘 canvas 4. 用户调整参数 → 只重算脏节点 → 即时刷新 ``` **懒加载/分页**:三个页签均 `lazy=false`(开页即载),`paginated=false`(B-Scan 全量,采集参数/异常列表量小一次性返回)。异常列表量若大,后续可改 `paginated=true` + `DataTableView`。 --- ## 4. 本地文件加载方案 ### 4.1 约定式本地路径 雷达数据集在前端本地项目目录下的固定结构: ``` {projectDir}/datasets/{dsId}/ ├── data.iprh # 头文件(采集参数) ├── data.iprb # B-scan 二进制(dd_radar_2d:单通道) ├── data.ord # 通道偏移(dd_radar_3d 时需要) └── data.gps # GPS 轨迹(可选,地形校正/里程归一化用) ``` `dd_radar_3d` 多通道扩展(若一个数据集含多线或多文件组): ``` {projectDir}/datasets/{dsId}/ ├── data.iprh ├── ch01.iprb ├── ch02.iprb ... ├── ch16.iprb └── channel.ord ``` ### 4.2 `ApiDatasetRepository` 雷达分支(零架构改动) 在现有 `ApiDatasetRepository::loadAsync` 中,对 `"radar.*"` loaderKey 直接本地处理,**不发网络请求**。 ```cpp // src/data/api/ApiDatasetRepository.cpp DetailLoad* ApiDatasetRepository::loadAsync(const std::string& key, const std::string& dsId, ...) { if (key == "radar.profile") return makeRadarProfile(dsId); if (key == "radar.info") return makeRadarInfo(dsId); if (key == "radar.anomalies") return makeRadarAnomalies(dsId); // ... 现有网络请求逻辑不变 } ``` `makeRadarProfile` 内部: 1. 根据 `dsId` 组装本地目录路径(`LocalProjectPathResolver::datasetDir(dsId)`) 2. 读 `.iprh` → `IprHeader` 3. `dd_radar_2d`:读单个 `.iprb` → `BScan` → 转 `float` 4. `dd_radar_3d`:读多通道 `.iprb` + `.ord` → `assembleGprSurvey` → `GprSurvey` 5. 返回 `LocalDetailLoad`(同步完成,`QTimer::singleShot(0, ...)` emit done) **优势**:零改动 `DatasetDetailController`、`IAsyncDatasetRepository` 接口、页签引擎。雷达数据只是 `ApiDatasetRepository` 内部的一个本地分支。 ### 4.3 `IprHeader` 扩展(采集参数) 现有 `IprHeader` 只有 `samples/lastTrace/channels/timeWindowNs/soilVelocity/distanceInterval`。需要从 `.iprh` 中解析更多字段: ```cpp // src/io/gpr/IprHeader.hpp 扩展 struct IprHeader { // 已有字段 int samples = 0; long lastTrace = 0; int channels = 0; double timeWindowNs = 0; double soilVelocity = 0; // m/s double distanceInterval = 0; // m // 新增字段(从 .iprh 文本解析) QString date; // 采集日期,如 "2022-03-10" QString time; // 采集时刻,如 "10:46" QString antennaModel; // 雷达硬件型号,如 "MALA MIRA" double antennaFreqMHz = 0; // 天线中心频率 MHz // ... 其他字段按需追加 }; ``` **注意**:`.iprh` 是文本头,字段名可能不固定。解析器用关键词模糊匹配(如 `"DATE"`、`"ANTENNA"`、`"FREQUENCY"`),缺失字段留空/0 不抛错。 --- ## 5. 算法接口契约(用户代码接入点) 用户提供处理算法代码,我只负责**管线框架**(节点编排、参数传递、脏链追踪、缓存、线程调度)。 ### 5.1 算法函数签名(用户需实现) ```cpp // 所有算法统一签名:输入输出为行主序 float 矩阵 [trace][sample] // meta 提供几何参数(dx, dz, velocity 等) // params 为各算法专属参数结构(见 §5.3) // 算法就地修改 out(out 已由调用方分配,大小 = in.size()) using GprAlgoFunc = void(*)(const float* in, float* out, const GprTraceMeta& meta, const void* params); ``` **内存布局约定**(必须与用户提供代码一致): - `in/out` 大小 = `meta.ntraces * meta.samples` - 索引方式:`in[t * meta.samples + s]`,其中 `t=0..ntraces-1`(道号),`s=0..samples-1`(采样点) - `t=0` 为测线起点,`s=0` 为地表(时间零点) ### 5.2 管线框架(我实现) ```cpp // src/core/gpr_proc/GprProcessingPipeline.hpp namespace geopro::core { struct GprTraceMeta { int ntraces = 0; int samples = 0; double dx = 0; // 道距 (m) double dz = 0; // 深度采样间隔 (m 或 ns,看 zIsTime) double x0 = 0; double z0 = 0; bool zIsTime = true; // true=ns, false=m double velocityMPerNs = 0.12; }; // 节点描述(纯数据,无虚函数,零开销) struct ProcNodeDesc { QString name; // 唯一标识,如 "zero_time", "gain" GprAlgoFunc algo; // 用户提供的算法函数指针 const void* params; // 指向参数结构体的指针(由调用方保证生命周期) bool enabled = true; // 是否启用 }; class GprProcessingPipeline { public: // 注册节点(按顺序)。params 指针必须指向稳定内存(如 BScanProfileView 成员)。 void registerNode(const ProcNodeDesc& desc); // 执行管线:从 raw 输入开始,按注册顺序逐节点处理,返回最终输出 // 内部缓存各节点输出,参数未变时直接返回缓存(脏链追踪) std::vector run(const std::vector& raw, const GprTraceMeta& meta); // 标记某节点参数已变更(下次 run 时从该节点开始重算) void markDirty(const QString& nodeName); // 标记全部重算 void markAllDirty(); private: std::vector nodes_; std::unordered_map> cache_; std::unordered_set dirty_; }; } // namespace geopro::core ``` ### 5.3 参数结构体(用户算法使用) 每个算法一个参数结构体,定义在 `core/model/gpr_proc/` 下。用户按这些结构体传参: ```cpp // src/core/model/gpr_proc/GprAlgoParams.hpp #pragma once namespace geopro::core { struct ZeroTimeParams { bool autoDetect = true; int cutSamples = 30; int frontSearchWindow = 180; double noiseSigmaMultiple = 3.0; }; enum class ZeroDriftMode { DC, Sliding }; struct ZeroDriftParams { ZeroDriftMode mode = ZeroDriftMode::Sliding; int slidingWindowSamples = 100; }; enum class BgMode { MeanAverage, SingularityFilter }; struct BackgroundParams { BgMode mode = BgMode::MeanAverage; int averageTraceCount = 301; double singularityThreshold = 1.8; }; struct GainParams { bool enableSpherical = true; bool enableAbsorption = true; double velocityMPerNs = 0.12; double referenceDepthM = 0.01; double exponent = 1.5; double absorptionBeta = 1.0; double maxGain = 30.0; }; struct BandpassParams { bool autoFreq = true; double lowFreqHz = 1000; double highFreqHz = 100; double antennaFreqMHz = 200.0; }; struct SmoothParams { int smoothWindow = 2; bool verticalSmooth = true; bool horizontalSmooth = true; }; enum class TraceBalanceMode { Global, Local }; struct TraceBalanceParams { TraceBalanceMode mode = TraceBalanceMode::Local; int horizontalWindowTraces = 31; double targetRms = 0.0; double maxGain = 4.0; double epsilon = 1.0; }; struct SampleBalanceParams { int windowSamples = 120; double targetRms = 0.0; double maxGain = 6.0; double epsilon = 1.0; }; struct HilbertParams { bool computeEnvelope = true; }; struct MigrationParams { int sumWidth = 64; double velocityMPerNs = 0.12; }; struct TopoParams { bool useAverageElevation = true; double baseElevation = 0.0; }; struct ResampleParams { bool resampleTraces = false; bool resampleSamples = false; int newTraces = 0; int newSamples = 0; }; struct PredictiveDeconParams { int predLag = 1; int filterLen = 10; }; // 总参数:BScanProfileView 持有一个实例,各处理对话框修改子字段 struct GprProcessingParams { ZeroTimeParams zeroTime; ZeroDriftParams zeroDrift; BackgroundParams background; GainParams gain; BandpassParams bandpass; SmoothParams smooth; TraceBalanceParams traceBalance; SampleBalanceParams sampleBalance; HilbertParams hilbert; MigrationParams migration; TopoParams topo; ResampleParams resample; PredictiveDeconParams predictiveDecon; }; } // namespace geopro::core ``` ### 5.4 用户提供代码的接入方式 **方式 A(推荐):函数指针** 用户把算法实现为 C 风格函数,放在 `src/core/gpr_proc/algo/` 下: ```cpp // src/core/gpr_proc/algo/gpr_zero_time.cpp(用户文件) #include "gpr_proc/GprAlgoParams.hpp" void gprZeroTime(const float* in, float* out, const geopro::core::GprTraceMeta& meta, const void* params) { const auto& p = *static_cast(params); // ... 用户算法实现 ... } ``` 管线注册: ```cpp pipeline.registerNode({"zero_time", gprZeroTime, ¶ms_.zeroTime}); ``` **方式 B:类接口(若用户偏好 OOP)** ```cpp class IGprAlgorithm { public: virtual ~IGprAlgorithm() = default; virtual void process(const float* in, float* out, const GprTraceMeta& meta) = 0; virtual QString name() const = 0; }; ``` **请用户确认偏好 A 还是 B**。A 更轻量(C 风格函数指针),B 更利于封装状态。 --- ## 6. UI 组件架构(适配底部页签空间) 底部页签高度有限(默认 ~250px,用户可拖拽拉大)。BScanProfileView 内部必须紧凑。 ``` BScanProfileView(QWidget,实现 IDetailView) ├── 顶部工具条(单行,QHBoxLayout) │ ├── [通道选择] QComboBox # dd_radar_3d 显示,2d 隐藏 │ ├── [rawdata|proc_data_1] QButtonGroup # 切换原始/处理后 │ ├── [色阶] QComboBox # Gray / Rainbow / Seismic / ColdWarm │ ├── [对比度] QSlider(0-200%) + QLabel │ ├── [比例] QLabel("1:2") + QToolButton(调) │ ├── [显示异常] QCheckBox │ ├── [Wiggle] QToolButton(toggle) │ ├── [道编辑] QToolButton │ ├── [反向] QToolButton │ └── [处理参数⚙] QToolButton # 点击弹出 GprProcessingDialog ├── 主体(QSplitter,水平) │ ├── BScanCanvas(QWidget,自绘,占 80%) │ │ ├── 原始/处理后 float 数据缓存 │ │ ├── ColorMapper(float→ColorScale→QImage) │ │ ├── ViewTransform(世界↔屏幕) │ │ ├── 交互:滚轮缩放/左键平移/左键框选异常/悬停读值/右键菜单 │ │ └── 异常标注列表(屏幕坐标实时投影) │ └── AScanWaveformView(QWidget,占 20%,最小 120px) │ └── QwtPlot + QwtPlotCurve(单道波形) └── 底部状态栏(QHBoxLayout) ├── 道号: 1234 | 里程: 123.45m | 深度: 2.34m | 振幅: -1234.5 └── 当前通道: CH01 | 当前数据: rawdata ``` ### 6.1 空间压缩策略 | 原设计 | 底部页签适配 | |---|---| | 顶部多行控制栏(23+参数) | **单行快捷栏** + "⚙处理参数"按钮弹出对话框 | | 大面积剖面图 | BScanCanvas 占满可用空间,默认纵向压缩;用户拉大底部 panel 后自动扩展 | | 右侧 A-Scan | QSplitter 水平分割,可拖拽调宽;默认窄条(~120px) | | 鼠标悬停信息面板 | 收进**底部状态栏**(单行文本),不盖剖面 | | 异常编辑弹窗 | 模态对话框(`BScanAnomalyDialog`),不常驻 | | 参数配置区 | `GprProcessingDialog`(模态/非模态对话框),分组折叠面板 | ### 6.2 BScanCanvas 自绘核心 ```cpp class BScanCanvas : public QWidget { // 数据 std::vector rawData_; // 当前通道原始数据 std::vector procData_; // 处理后数据(由管线输出) GprTraceMeta meta_; // 渲染 QImage image_; // 当前显示的 RGB 图像(缓存) ColorMapper mapper_; // float→QRgb(复用 core::ColorScale) // 视图变换 ViewTransform view_; // 世界坐标(m,m) ↔ 屏幕坐标(px) // 交互状态 enum class Mode { Idle, Panning, Zooming, Marquee, Hover } mode_ = Mode::Idle; QPoint lastMousePos_; QRectF marqueeRect_; // 框选异常(世界坐标) // 异常 std::vector anomalies_; // A-Scan 联动 int hoverTrace_ = -1; // 当前悬停道号 signals: void traceSelected(int traceIdx, const float* traceData, int samples); void anomalySelected(const GprAnomaly& anomaly); void hoverInfoChanged(const QString& info); }; ``` **paintEvent 管线**: 1. 若数据/参数/视口变化 → 重新生成 `QImage`(`ColorMapper` float→QRgb) 2. `QPainter::drawImage` 贴图 3. 画网格线(里程/深度刻度,根据缩放动态抽稀) 4. 画异常框(红框 + 标签) 5. 画十字准星(鼠标悬停位置) 6. 画打标线(如果有 Marker 数据) **缩放策略**: - 滚轮:以鼠标位置为中心,缩放 `view_.scaleX` 和 `view_.scaleY`(可独立锁比例) - XY 比例按钮:固定 `scaleY / scaleX = ratio`(如 1:2 表示纵向放大 2 倍) - 全景:一键重置 view 到 fit **Wiggle 模式**: - 关闭:`drawImage` 正常 raster 图 - 开启:每道画波形线(`QPainter::drawPolyline`),正半周填充色(如黑/红),负半周填充另一色(白/蓝),背景白 ### 6.3 A-Scan 联动 ```cpp // BScanCanvas 悬停/点击某道时 emit traceSelected connect(canvas_, &BScanCanvas::traceSelected, this, [this](int t, const float* data, int n) { aScanCurve_->setSamples(data, n); // QwtPlotCurve 更新 aScanPlot_->replot(); }); ``` --- ## 7. Payload、ViewKind 与策略 ### 7.1 新增 ViewKind ```cpp // src/controller/DatasetDetailTab.hpp enum class ViewKind { Scatter, FilledContour, Bar, LineProfile, PolylineMap, Table, WebMap, BScanProfile // ← 新增 }; ``` ### 7.2 新增 Payload ```cpp // src/core/model/detail/DetailPayloads.hpp // 单通道剖面数据(B-Scan 核心载荷) struct GprChannelData { QString channelName; // 如 "CH01" std::vector data; // traces × samples,行主序 }; struct GprProfilePayload { GprTraceMeta meta; // 几何参数 std::vector channels; // 多通道数据(2d 时 size=1) int currentChannel = 0; // 默认显示通道 // 采集参数(从头文件解析,供采集参数页签展示) QString date, time, antennaModel; double antennaFreqMHz = 0; // 色阶(默认 Gray) ColorScale defaultScale; }; // 雷达异常标注(前后端共用,M1 先本地) struct GprAnomaly { QString id; QString typeCode; // cavity / loose / void / pipe QString typeName; int traceStart = 0, traceEnd = 0; int sampleStart = 0, sampleEnd = 0; double distStartM = 0, distEndM = 0; double depthStartM = 0, depthEndM = 0; double widthM = 0, heightM = 0, lengthM = 0; double burialDepthM = 0, clearanceM = 0; double confidence = 0; QString remark; }; Q_DECLARE_METATYPE(geopro::core::GprProfilePayload) ``` ### 7.3 策略 ```cpp // src/app/panels/chart/Radar2dStrategy.hpp struct Radar2dStrategy : controller::IDatasetChartStrategy { std::string ddCode() const override { return "dd_radar_2d"; } std::vector tabs() const override { return { {QStringLiteral("B-Scan剖面"), controller::ViewKind::BScanProfile, QStringLiteral("radar.profile"), false, false}, {QStringLiteral("采集参数"), controller::ViewKind::Table, QStringLiteral("radar.info"), false, false}, {QStringLiteral("异常列表"), controller::ViewKind::Table, QStringLiteral("radar.anomalies"), false, false}, }; } }; // Radar3dStrategy.hpp(同结构,ddCode="dd_radar_3d") ``` ### 7.4 工厂分支 ```cpp // src/app/panels/chart/DetailViewFactory.cpp case controller::ViewKind::BScanProfile: { auto* bscan = new BScanProfileView(parent); // 注入所需仓储/回调(如需) return std::unique_ptr(bscan); } ``` --- ## 8. 文件清单与开发顺序 ### Phase 1:数据模型 + 管线框架(无算法,无 UI) | # | 文件 | 说明 | |---|---|---| | 1.1 | `src/core/model/gpr_proc/GprTraceMeta.hpp` | 几何元数据 | | 1.2 | `src/core/model/gpr_proc/GprAlgoParams.hpp` | 全部算法参数结构体 | | 1.3 | `src/core/model/gpr_proc/GprAnomaly.hpp` | 异常标注模型 | | 1.4 | `src/core/model/detail/DetailPayloads.hpp` | 追加 `GprProfilePayload` + `Q_DECLARE_METATYPE` | | 1.5 | `src/core/gpr_proc/GprProcessingPipeline.hpp/cpp` | 管线框架(注册/执行/缓存/脏链) | | 1.6 | `src/io/gpr/IprHeader.hpp/cpp` | 扩展头文件字段(date/time/antenna/freq) | | 1.7 | `tests/core/gpr_proc/test_pipeline.cpp` | 管线框架测试(用 stub 算法) | ### Phase 2:页签引擎接入(策略 + 本地加载 + 工厂) | # | 文件 | 说明 | |---|---|---| | 2.1 | `src/controller/DatasetDetailTab.hpp` | 追加 `BScanProfile` ViewKind | | 2.2 | `src/app/panels/chart/Radar2dStrategy.hpp` | 2d 策略 | | 2.3 | `src/app/panels/chart/Radar3dStrategy.hpp` | 3d 策略 | | 2.4 | `src/app/panels/chart/BScanProfileView.hpp/cpp` | 壳子(先空白 QWidget) | | 2.5 | `src/app/panels/chart/DetailViewFactory.cpp` | 追加 `BScanProfile` 分支 | | 2.6 | `src/data/api/ApiDatasetRepository.hpp/cpp` | 追加 `makeRadarProfile/Info/Anomalies` | | 2.7 | `src/app/main.cpp` | 注册 Radar2d/3d 策略 | | 2.8 | `src/app/CMakeLists.txt` | 追加新 .cpp | | 2.9 | `src/core/CMakeLists.txt` | 追加 gpr_proc 子目录 | ### Phase 3:B-Scan 自绘核心 | # | 文件 | 说明 | |---|---|---| | 3.1 | `src/app/panels/radar/ViewTransform.hpp/cpp` | 世界↔屏幕坐标变换(纯几何,可单测) | | 3.2 | `src/app/panels/radar/ColorMapper.hpp/cpp` | float→ColorScale→QRgb(复用 core::ColorScale) | | 3.3 | `src/app/panels/radar/BScanCanvas.hpp/cpp` | 自绘核心(paintEvent + 数据缓存) | | 3.4 | `src/app/panels/radar/AScanWaveformView.hpp/cpp` | QwtPlot + QwtPlotCurve | | 3.5 | `src/app/panels/radar/BScanToolbar.hpp/cpp` | 顶部单行工具条 | | 3.6 | `tests/render/test_bscan_canvas.cpp` | 自绘单元测试(用 fixture 数据画参考图) | ### Phase 4:交互 + 处理对话框 | # | 文件 | 说明 | |---|---|---| | 4.1 | `src/app/panels/radar/BScanCanvas.cpp` | 补交互:滚轮/平移/框选/悬停/右键菜单 | | 4.2 | `src/app/panels/radar/GprProcessingDialog.hpp/cpp` | 处理参数对话框(分组折叠面板) | | 4.3 | `src/app/panels/radar/BScanAnomalyDialog.hpp/cpp` | 异常编辑对话框 | | 4.4 | `src/app/panels/radar/BScanProfileView.cpp` | 总装:工具条 + Canvas + A-Scan + 状态栏 + 管线集成 | ### Phase 5:算法接入(用户提供代码后) | # | 文件 | 说明 | |---|---|---| | 5.1 | `src/core/gpr_proc/algo/` | 用户算法文件目录(函数实现) | | 5.2 | `src/app/panels/radar/BScanProfileView.cpp` | 管线注册各节点,绑定参数 | | 5.3 | `tests/core/gpr_proc/test_algo_*.cpp` | 各算法单元测试 | --- ## 9. 仍需确认的事项 ### 🔴 接入方式确认(1 项,阻塞 Phase 5) **算法接入方式**:用户提供处理代码时,偏好哪种接口? - **A(推荐)**:C 风格函数指针 `void algo(const float* in, float* out, const GprTraceMeta& meta, const void* params)`,放在 `src/core/gpr_proc/algo/*.cpp` - **B**:C++ 抽象类 `IGprAlgorithm`,用户继承实现 `process()` 方法 ### 🟡 中优先级(影响细节,不阻塞框架开发) 2. **`.iprh` 头文件完整字段列表**:现有头文件只有 samples/lastTrace/channels/timeWindow/soilVelocity/distanceInterval。用户的"采集参数"还需要 Date/Time/Antenna/Frequency。`.iprh` 文本中这些字段的**精确关键词**是什么?(如 `"DATE"`、`"TIME"`、`"ANTENNA"`、`"FREQUENCY"`?)缺失字段是否允许留空? 3. **色阶预置方案**:用户提到"灰度图、彩虹色阶、冷暖色阶、振幅色阶"。这些是否已有标准定义?我可以先预置几套 `core::ColorScale` stops: - Gray: 黑(0) → 白(max) - Rainbow: 紫→蓝→青→绿→黄→红 - Seismic/ColdWarm: 蓝(负) → 白(0) → 红(正) 请确认或提供具体 RGB 断点。 4. **Wiggle 模式配色**:正半周填什么色?负半周填什么色?线色?背景色?(标准地震勘探:正=黑/红,负=白/蓝,背景白) 5. **异常类型完整枚举**:用户提到"空洞/疏松/脱空/管线",是否还有其他类型?需要完整列表用于下拉选择。 6. **本地文件路径约定**:`{projectDir}/datasets/{dsId}/data.iprb` 这个约定是否 OK?还是需要从项目配置中读某个字段来确定根目录? --- ## 10. 下一步行动建议 1. **你确认算法接入方式(A 函数指针 / B 抽象类)** → 我锁定管线接口 2. **我启动 Phase 1 + Phase 2**(数据模型 + 管线框架 + 页签引擎接入) 3. **Phase 1/2 完成后,我给你一个可运行的空白 B-Scan 页签**(能双击雷达数据集打开页签、加载本地文件、显示空白画布) 4. **你同步准备算法代码**(按我定义的接口签名实现) 5. **我接 Phase 3/4**(自绘核心 + 交互 + 对话框) 6. **你提供算法代码后,Phase 5 接入** 是否按此顺序推进?还是先确认完所有事项再开始编码?