465 lines
27 KiB
Markdown
465 lines
27 KiB
Markdown
# 雷达 B-Scan 详情页开发方案 — 2026-06-29
|
||
|
||
> 状态:方案阶段,待确认事项见第7节。确认后即可按第6节顺序开发。
|
||
> 基线分支:`radar`(基于 `fix/3d-volume-blanking-mask`)。
|
||
|
||
---
|
||
|
||
## 1. 一句话架构
|
||
|
||
**B-Scan 详情页 = 独立 ADS Dock 工作区(非底部页签)**,内部自含:
|
||
- 顶部控制栏(23+ 处理参数 + 色阶/对比度/比例/模式切换)
|
||
- 主体 B-Scan 剖面图(灰度/彩色/Wiggle 可切换,支持框选异常、滚轮缩放、悬停读值)
|
||
- 右侧内嵌 A-Scan 单道波形(点击剖面任意位置联动)
|
||
- 右侧面板对象属性(采集参数只读表)+ 对象异常(异常列表+操作)
|
||
|
||
数据流:**本地文件/后端 → `io/gpr` 解析 → `core/gpr_proc` 信号处理管线 → `BScanProfileView` 自渲染(QImage + QPainter,非 Qwt/VTK)→ 参数调整即时重绘**。
|
||
|
||
---
|
||
|
||
## 2. 关键决策与理由
|
||
|
||
| 决策 | 选择 | 理由 |
|
||
|---|---|---|
|
||
| 容器 | **独立 ADS CDockWidget**(类似 `MapViewPanel`) | 底部 `DatasetDetailPanel` 空间(~200-300px高)无法承载密集控制栏+大面积剖面交互+A-Scan波形。独立 dock 可最大化利用屏幕,且雷达分析是专业深度工作流,值得独占工作区。 |
|
||
| B-Scan 渲染 | **QImage + QPainter 自绘**(非 Qwt/VTK) | B-Scan 是二维 raster 图像(traces × samples 的像素矩阵),非曲线/等值线。Qwt 的 `QwtPlot` 面向函数曲线,不适合 raster;VTK 过重且离屏渲染延迟高。QImage 直接操作像素,处理管线输出 `std::vector<float>` → 映射色阶 → `setPixel`/`scanLine`,足够快且可控。 |
|
||
| A-Scan 渲染 | **QwtPlot 曲线**(复用现有 Qwt 基础设施) | A-Scan 是单道振幅-深度折线,Qwt 的 `QwtPlotCurve` 完全匹配,且项目已链 Qwt。 |
|
||
| 处理管线 | **前端 C++ 实时计算**(内存常驻 + 多线程) | 用户明确要求"几分钟处理完公里级数据"。全量数据(32MB/测线)驻内存,参数调整只重跑相关处理节点(脏链追踪),避免全量重算。重负载节点(滤波、偏移、反褶积)丢 `QtConcurrent::run` 后台线程,主线程保持 UI 响应。 |
|
||
| 数据入口 | **先本地文件(.iprb/.iprh/.ord),后接后端 API** | API 和 ddCode 尚未设计。M1 先走本地文件入口验证处理管线和 UI;后端契约确定后,在 `data` 层补 `loadAsync` 分发即可,视图层不动。 |
|
||
| 异常标注 | **前端本地状态(M1)+ 后端同步接口(M2)** | 前端用 `std::vector<GprAnomaly>` 驻内存,支持增删改+截图关联;后端 API 就绪后,在 `data` 层补 `saveAnomalies/loadAnomalies`,控制器层补上传逻辑。 |
|
||
|
||
---
|
||
|
||
## 3. 数据流(端到端)
|
||
|
||
```
|
||
[用户双击雷达数据集]
|
||
DatasetListPanel::itemDoubleClicked
|
||
main.cpp ──► 判断 ddCode == "dd_gpr_data"(或雷达相关 ddCode)
|
||
│
|
||
▼ 走独立路由(不走 DatasetDetailController 的页签引擎)
|
||
RadarWorkbenchController::openSurvey(dsId, ddCode, dsName, filePath)
|
||
│
|
||
▼ 如果本地文件已缓存,直接取;否则发请求/读本地文件
|
||
[io/gpr 层] readIprb / assembleGprSurvey / 未来 readRd3
|
||
│ 输出:core::GprSurvey(ntraces×samples×channels 的 double 值数组)
|
||
▼
|
||
[core/gpr_proc 层] GprProcessingPipeline
|
||
输入:原始 B-scan(某通道的 traces×samples)+ ProcessingParams
|
||
节点链:ZeroTime ──► ZeroDrift ──► BackgroundRemove ──► Gain ──► Bandpass
|
||
──► Smooth ──► TraceBalance ──► SampleBalance ──► Hilbert
|
||
──► Migration ──► TopoCorrect ──► Resample ──► PredictiveDecon
|
||
输出:processed B-scan(float 矩阵,与输入同维度)
|
||
│
|
||
▼ 参数变更时只重算脏节点下游(缓存各节点输出)
|
||
[app/panels/radar/ BScanProfileView]
|
||
- 原始/处理后数据各一份(或处理后实时生成)
|
||
- ColorMapper:float 值域 → ColorScale → RGBA 像素
|
||
- QPainter:画像素矩阵 + 网格线 + 异常框 + 十字准星
|
||
- 交互:滚轮缩放/平移(变换矩阵)、框选(QRectF 世界坐标)、悬停(mouseMove 转采样/道号)
|
||
│
|
||
▼ 点击剖面位置
|
||
[A-Scan 子视图] AScanWaveformView(QwtPlotCurve 实时更新)
|
||
│
|
||
▼ 右侧面板
|
||
[ObjectAttrPanel] 采集参数只读表(走现有 KeyValueView)
|
||
[ObjectExceptionPanel] 异常列表(复用现有异常列表面板逻辑)
|
||
```
|
||
|
||
**注意**:此路由**绕过** `DatasetDetailController` / `ChartStrategyRegistry` / `DetailViewFactory` 的页签引擎。雷达详情页是**独立专业工作区**,非通用页签容器。未来若需把雷达也纳入底部页签(如只展示属性表格),可再补一个 `Table` 页签走现有引擎。
|
||
|
||
---
|
||
|
||
## 4. 分层设计
|
||
|
||
### 4.1 `core/gpr_proc` — 信号处理管线(纯 C++17,零 Qt/VTK)
|
||
|
||
**设计目标**:可独立单元测试、可脏链追踪、SIMD/多线程友好。
|
||
|
||
**核心抽象**:
|
||
|
||
```cpp
|
||
// 处理节点接口
|
||
class IGprProcNode {
|
||
public:
|
||
virtual ~IGprProcNode() = default;
|
||
// 输入输出:行主序 float 矩阵 [trace][sample],尺寸由 meta 描述
|
||
virtual void process(const float* in, float* out, const GprTraceMeta& meta) = 0;
|
||
virtual QString name() const = 0;
|
||
};
|
||
|
||
// 管线:持有节点链,支持脏链追踪与缓存
|
||
class GprProcessingPipeline {
|
||
public:
|
||
void setNodeEnabled(const QString& name, bool on);
|
||
void setNodeParams(const QString& name, const QVariantMap& params);
|
||
// 执行管线:从原始数据到处理后数据,只重算脏节点
|
||
std::vector<float> run(const std::vector<float>& raw, const GprTraceMeta& meta);
|
||
// 获取指定节点的输出(用于调试/中间结果展示)
|
||
const std::vector<float>& nodeOutput(const QString& name) const;
|
||
private:
|
||
std::vector<std::unique_ptr<IGprProcNode>> nodes_;
|
||
std::unordered_map<QString, std::vector<float>> cache_;
|
||
std::unordered_set<QString> dirty_;
|
||
};
|
||
```
|
||
|
||
**节点清单(按用户需求的 20+ 种处理)**:
|
||
|
||
| # | 节点名 | 类名 | 复杂度 | 说明 |
|
||
|---|---|---|---|---|
|
||
| 1 | 时间零点校正 | `ZeroTimeCorrectionNode` | 低 | 自动/手动模式;自动:前 N 采样内噪声σ倍数阈值找起跳 |
|
||
| 2 | 去除零漂 | `ZeroDriftRemovalNode` | 低 | DC(整道均值) / Sliding(滑动窗口均值) |
|
||
| 3 | 背景去除 | `BackgroundRemovalNode` | 中 | MeanAverage(多道平均) / SingularityFilter(SVD) |
|
||
| 4 | 增益 | `GainNode` | 中 | AGC / SphericalDiffusion / AbsorptionCompensation 等 |
|
||
| 5 | 带通滤波 | `BandpassFilterNode` | 中 | FFT 实现(需 FFTW 或自实现 Cooley-Tukey);自动/手动频带 |
|
||
| 6 | 剖面平滑 | `SmoothingNode` | 低 | 2D 均值/高斯滤波,可分别开关横向/纵向 |
|
||
| 7 | 道间均衡 | `TraceBalanceNode` | 中 | Global(全剖面RMS) / Local(滑动窗口RMS) |
|
||
| 8 | 道内增益 | `SampleBalanceNode` | 中 | TVG-like:深度方向滑动RMS均衡 |
|
||
| 9 | 希尔伯特变换 | `HilbertTransformNode` | 中 | 包络/瞬时相位/瞬时频率(FFT-based 或时域 FIR) |
|
||
| 10 | 偏移处理 | `MigrationNode` | **高** | Kirchhoff / F-K 偏移;需速度模型 |
|
||
| 11 | 速度分析 | `VelocityAnalysisNode` | **高** | 双曲线拟合 → 速度谱;交互式拾取(后续迭代) |
|
||
| 12 | 地形校正 | `TopoCorrectionNode` | 中 | 高程→时深转换,波形整体平移/拉伸 |
|
||
| 13 | 里程归一化 | `DistanceNormalizationNode` | 低 | 打标处插桩号,固定道间距重采样 |
|
||
| 14 | 数据重采样 | `ResamplingNode` | 低 | 横向/纵向独立,线性插值 |
|
||
| 15 | 预测反褶积 | `PredictiveDeconNode` | **高** | 自相关 → Levinson-Durbin → 预测误差滤波 |
|
||
| 16 | 道编辑 | `TraceEditNode` | 低 | 废道删除/置零(前端 UI 传入废道索引列表) |
|
||
| 17 | 剖面反向 | `ReverseTraceNode` | 低 | 道序反转 |
|
||
|
||
**M1 阶段实现优先级**:
|
||
- **P0(必须)**:1, 2, 4(基础增益), 5(简化版), 6, 7, 8, 14, 16, 17
|
||
- **P1(重要)**:3, 4(球面扩散), 9, 12, 13
|
||
- **P2(后续迭代)**:10, 11, 15(计算密集,需更多测试数据验证)
|
||
|
||
**性能策略**:
|
||
- 节点输出缓存:`run()` 时比较参数 hash,未变则直接返回缓存。
|
||
- 并行化:各道独立处理(零漂、增益、均衡等)用 `tbb::parallel_for` 或 `std::execution::par`(C++17)。
|
||
- FFT:带通滤波、希尔伯特变换需 FFT。可用 `fftw3`(vcpkg 有)或自实现基2-FFT(数据量 516 采样点,很小,自实现也可接受)。推荐先自实现避免引入新依赖,性能不足再切 FFTW。
|
||
|
||
### 4.2 `app/panels/radar/` — B-Scan 视图层(QtWidgets)
|
||
|
||
**核心组件**:
|
||
|
||
```
|
||
BScanWorkbench(ADS::CDockWidget 外壳)
|
||
├── BScanProfileView(QWidget,自绘主体)
|
||
│ ├── 顶部工具栏:测线选择/通道选择/色阶/对比度/XY比例/显示异常/各处理参数按钮
|
||
│ ├── BScanCanvas(QWidget,自绘核心)
|
||
│ │ ├── 原始数据缓存(std::vector<float> raw_)
|
||
│ │ ├── 处理后数据缓存(std::vector<float> proc_,或实时从管线取)
|
||
│ │ ├── ColorMapper(float min/max → ColorScale → QRgb)
|
||
│ │ ├── ViewTransform(世界坐标 ↔ 屏幕坐标:平移/缩放矩阵)
|
||
│ │ ├── 交互状态机(Idle / Panning / Zooming / MarqueeSelect / Hover)
|
||
│ │ └── 异常标注列表(std::vector<BScanAnomaly>,世界坐标存储)
|
||
│ └── AScanWaveformView(QWidget,内嵌右侧)
|
||
│ └── QwtPlot + QwtPlotCurve(单道波形实时刷新)
|
||
├── ObjectAttrPanel(右侧面板上)— 复用现有 KeyValueView
|
||
└── ObjectExceptionPanel(右侧面板下)— 复用现有异常列表逻辑
|
||
```
|
||
|
||
**BScanCanvas 渲染管线**:
|
||
|
||
```cpp
|
||
void BScanCanvas::paintEvent(QPaintEvent*) {
|
||
QPainter p(this);
|
||
// 1. 背景
|
||
p.fillRect(rect(), Qt::black);
|
||
// 2. 根据当前标签(rawdata/proc_data_1)选择数据源
|
||
const auto& data = (currentTab_ == Raw) ? raw_ : proc_;
|
||
// 3. 可见区域裁剪:由 viewTransform_ 计算当前窗口对应的 [t0,t1)×[s0,s1)
|
||
// 4. 逐像素/逐块映射:float → ColorScale → QRgb
|
||
// - 若缩放比小(全景),聚合多采样取平均后上色(防混叠)
|
||
// - 若缩放比大(局部),单采样直接上色
|
||
// 5. 画网格线(里程/深度刻度,根据缩放动态抽稀)
|
||
// 6. 画异常框(世界坐标 → 屏幕坐标,红框+标签)
|
||
// 7. 画十字准星(鼠标悬停位置)
|
||
// 8. 画打标线(如果有打标数据)
|
||
}
|
||
```
|
||
|
||
**交互设计细节**:
|
||
|
||
| 交互 | 实现 | 坐标系 |
|
||
|---|---|---|
|
||
| 左键拖动 | 平移(修改 viewTransform 的 offset) | 屏幕 delta → 世界 delta |
|
||
| 滚轮 | 以鼠标位置为中心缩放(修改 viewTransform 的 scaleX/scaleY) | 屏幕锚点 → 世界锚点保持不动 |
|
||
| 左键框选 | 释放时生成 `BScanAnomaly`(世界坐标矩形) | 屏幕 rect → 世界 rect |
|
||
| 鼠标悬停 | 状态栏显示:道号、里程、深度、振幅值;同时更新 A-Scan | 屏幕 pos → 道号 t + 采样 s |
|
||
| 双击异常框 | 弹出异常编辑对话框(类型/深度/尺寸/备注) | — |
|
||
| 右键菜单 | 切换 X 轴显示:道号 / 距离 / 里程;切换 Y 轴:时间 / 深度 | — |
|
||
|
||
**Wiggle 模式**:点击按钮切换。
|
||
- Off:正常灰度/彩色 raster 图。
|
||
- On:每道画波形线(振幅→水平偏移,正右负左),填充正/负区域为不同颜色,背景透明/白色。参考地震勘探 wiggle trace 标准画法。
|
||
|
||
### 4.3 `controller` — 雷达工作区控制器
|
||
|
||
新增 `RadarWorkbenchController`(独立于 `DatasetDetailController`):
|
||
|
||
```cpp
|
||
class RadarWorkbenchController : public QObject {
|
||
Q_OBJECT
|
||
public:
|
||
void openSurvey(const std::string& dsId, const std::string& ddCode,
|
||
const QString& name, const QString& filePath);
|
||
void switchChannel(int channelIndex);
|
||
void setProcessingParams(const GprProcessingParams& params);
|
||
void runProcessing(); // 触发管线,完成后 emit processedReady
|
||
void saveAnomalies(); // M2:调后端 API 上传异常
|
||
signals:
|
||
void surveyLoaded(const GprSurveyInfo& info); // 采集参数、通道列表
|
||
void rawDataReady(const std::vector<float>& data, const GprTraceMeta& meta);
|
||
void processedReady(const std::vector<float>& data, const GprTraceMeta& meta);
|
||
void anomalyListChanged(const std::vector<GprAnomaly>& anomalies);
|
||
void progress(int percent, const QString& stage); // 长时间处理进度
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 数据模型新增
|
||
|
||
### 5.1 `core/model/gpr_proc/`(新建目录)
|
||
|
||
```cpp
|
||
// GprTraceMeta.hpp — 单通道剖面的元数据(纯 C++17)
|
||
struct GprTraceMeta {
|
||
int ntraces = 0;
|
||
int samples = 0;
|
||
double dx = 0; // 道距 (m)
|
||
double dz = 0; // 采样间隔 (m 或 ns)
|
||
double x0 = 0;
|
||
double z0 = 0;
|
||
bool zIsTime = false; // true=时间(ns), false=深度(m)
|
||
double velocityMPerNs = 0.12; // 雷达波速,时深转换用
|
||
};
|
||
|
||
// GprProcessingParams.hpp — 全部处理参数的结构化定义
|
||
struct GprProcessingParams {
|
||
// 零点校正
|
||
struct ZeroTime { bool autoDetect=true; int cutSamples=30; int frontSearchWindow=180; double noiseSigmaMultiple=3.0; } zeroTime;
|
||
// 零漂
|
||
enum class ZeroDriftMode { DC, Sliding };
|
||
struct ZeroDrift { ZeroDriftMode mode=ZeroDriftMode::Sliding; int slidingWindowSamples=100; } zeroDrift;
|
||
// 背景去除
|
||
enum class BgMode { MeanAverage, SingularityFilter };
|
||
struct Background { BgMode mode=BgMode::MeanAverage; int averageTraceCount=301; double singularityThreshold=1.8; } background;
|
||
// 增益(简化:先实现 SphericalDiffusion)
|
||
struct Gain { 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; } gain;
|
||
// 带通滤波
|
||
struct Bandpass { bool autoFreq=true; double lowFreqHz=1000; double highFreqHz=100; double antennaFreqMHz=200.0; } bandpass;
|
||
// 平滑
|
||
struct Smooth { int smoothWindow=2; bool verticalSmooth=true; bool horizontalSmooth=true; } smooth;
|
||
// 道间均衡
|
||
enum class TraceBalanceMode { Global, Local };
|
||
struct TraceBalance { TraceBalanceMode mode=TraceBalanceMode::Local; int horizontalWindowTraces=31; double targetRms=0.0; double maxGain=4.0; double epsilon=1.0; } traceBalance;
|
||
// 道内增益
|
||
struct SampleBalance { int windowSamples=120; double targetRms=0.0; double maxGain=6.0; double epsilon=1.0; } sampleBalance;
|
||
// 希尔伯特
|
||
struct Hilbert { bool computeEnvelope=true; } hilbert;
|
||
// 偏移
|
||
struct Migration { int sumWidth=64; double velocityMPerNs=0.12; } migration;
|
||
// 地形校正
|
||
struct Topo { bool useAverageElevation=true; double baseElevation=0.0; } topo;
|
||
// 重采样
|
||
struct Resample { bool resampleTraces=false; bool resampleSamples=false; int newTraces=0; int newSamples=0; } resample;
|
||
// 预测反褶积
|
||
struct PredictiveDecon { int predLag=1; int filterLen=10; } predictiveDecon;
|
||
};
|
||
|
||
// GprAnomaly.hpp — 雷达异常标注(前后端共用模型)
|
||
struct GprAnomaly {
|
||
QString id; // 后端返回或前端临时 UUID
|
||
QString surveyLineName;
|
||
int traceStart = 0, traceEnd = 0; // 道号范围
|
||
int sampleStart = 0, sampleEnd = 0; // 采样范围
|
||
double distanceStartM = 0, distanceEndM = 0; // 里程范围(m)
|
||
double depthStartM = 0, depthEndM = 0; // 深度范围(m)
|
||
QString typeCode; // cavity / loose / void / pipe ...
|
||
QString typeName;
|
||
double widthM = 0, heightM = 0, lengthM = 0; // 异常尺寸
|
||
double burialDepthM = 0; // 埋深
|
||
double clearanceM = 0; // 净空
|
||
double confidence = 0; // 置信度 0-1
|
||
QString remark;
|
||
QString sliceScreenshotPath; // 切片截图本地路径(M1 先本地文件)
|
||
QString profileScreenshotPath; // 剖面截图本地路径
|
||
};
|
||
```
|
||
|
||
### 5.2 `io/gpr/` — rd3 解析器(未来扩展)
|
||
|
||
在现有 `IprbReader` / `IprHeader` / `GprSurveyAssembler` 旁新增:
|
||
|
||
```cpp
|
||
// Rd3Reader.hpp — MALA .rd3 / .rd7 三维雷达格式解析
|
||
namespace geopro::io::gpr {
|
||
struct Rd3Header { /* 天线频率、采样率、道数、通道数等 */ };
|
||
Rd3Header parseRd3Header(const std::string& headerText);
|
||
BScan readRd3(const std::string& path, const Rd3Header& h);
|
||
BScan readRd3Range(const std::string& path, const Rd3Header& h, std::int64_t t0, std::int64_t t1);
|
||
} // namespace geopro::io::gpr
|
||
```
|
||
|
||
> **注意**:`.rd3` 格式细节需用户提供样例文件或格式文档。当前先以 `.iprb` 验证管线。
|
||
|
||
---
|
||
|
||
## 6. 文件清单与开发顺序
|
||
|
||
### Phase A:基础设施(无 UI,可独立测试)
|
||
|
||
| 顺序 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| A1 | `src/core/model/gpr_proc/GprTraceMeta.hpp` | 元数据模型 |
|
||
| A2 | `src/core/model/gpr_proc/GprProcessingParams.hpp` | 参数结构体(对应用户需求的 20+ 参数) |
|
||
| A3 | `src/core/model/gpr_proc/GprAnomaly.hpp` | 异常标注模型 |
|
||
| A4 | `src/core/gpr_proc/IGprProcNode.hpp` | 节点接口 |
|
||
| A5 | `src/core/gpr_proc/GprProcessingPipeline.hpp/cpp` | 管线编排+脏链追踪 |
|
||
| A6 | `src/core/gpr_proc/ZeroTimeCorrectionNode.cpp` | 零点校正 |
|
||
| A7 | `src/core/gpr_proc/ZeroDriftRemovalNode.cpp` | 去零漂 |
|
||
| A8 | `src/core/gpr_proc/GainNode.cpp` | 基础增益(球面扩散+吸收补偿) |
|
||
| A9 | `src/core/gpr_proc/BandpassFilterNode.cpp` | 带通滤波(自实现 FFT) |
|
||
| A10 | `src/core/gpr_proc/SmoothingNode.cpp` | 剖面平滑 |
|
||
| A11 | `src/core/gpr_proc/TraceBalanceNode.cpp` | 道间均衡 |
|
||
| A12 | `src/core/gpr_proc/SampleBalanceNode.cpp` | 道内增益 |
|
||
| A13 | `src/core/gpr_proc/ResamplingNode.cpp` | 数据重采样 |
|
||
| A14 | `tests/core/gpr_proc/test_gpr_pipeline.cpp` | 管线集成测试(用 fixture 数据断言输出) |
|
||
| A15 | `tests/core/gpr_proc/test_*.cpp` | 各节点单元测试 |
|
||
|
||
### Phase B:B-Scan 视图(UI 核心)
|
||
|
||
| 顺序 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| B1 | `src/app/panels/radar/BScanCanvas.hpp/cpp` | 自绘核心(QImage raster + 交互状态机) |
|
||
| B2 | `src/app/panels/radar/ColorMapper.hpp/cpp` | float → ColorScale → QRgb(复用 core::ColorScale) |
|
||
| B3 | `src/app/panels/radar/ViewTransform.hpp/cpp` | 世界↔屏幕坐标变换(纯几何,可单测) |
|
||
| B4 | `src/app/panels/radar/AScanWaveformView.hpp/cpp` | A-Scan 波形(QwtPlotCurve) |
|
||
| B5 | `src/app/panels/radar/BScanProfileView.hpp/cpp` | 总装:工具栏 + BScanCanvas + AScanWaveformView |
|
||
| B6 | `src/app/panels/radar/BScanToolbar.hpp/cpp` | 顶部控制栏(参数按钮+滑块+下拉) |
|
||
| B7 | `src/app/panels/radar/GprColorScaleDialog.hpp/cpp` | 色阶选择对话框(可复用 ColorScaleConfigDialog) |
|
||
| B8 | `src/app/panels/radar/GprParamsDialog.hpp/cpp` | 参数配置对话框(各处理参数的表单) |
|
||
| B9 | `src/app/panels/radar/BScanAnomalyDialog.hpp/cpp` | 异常编辑对话框(类型/深度/尺寸/备注) |
|
||
| B10 | `src/app/panels/radar/BScanWorkbench.hpp/cpp` | ADS DockWidget 外壳 + 右侧面板布局 |
|
||
|
||
### Phase C:控制器与集成
|
||
|
||
| 顺序 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| C1 | `src/controller/RadarWorkbenchController.hpp/cpp` | 控制器:数据加载/处理调度/异常管理 |
|
||
| C2 | `src/app/main.cpp` | 修改:新增雷达工作区路由、ADS dock 注册、信号接线 |
|
||
| C3 | `src/app/CMakeLists.txt` | 新增 Phase B 所有 .cpp 文件 |
|
||
| C4 | `src/core/CMakeLists.txt` | 新增 Phase A 所有 .cpp 文件(gpr_proc 子目录) |
|
||
| C5 | `tests/...` | 补 UI 单元测试(ViewTransform、ColorMapper)和控制器测试 |
|
||
|
||
### Phase D:后端对接(M2,API 就绪后)
|
||
|
||
| 顺序 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| D1 | `src/data/api/ApiDatasetRepository.cpp` | 新增 loaderKey 分发:`gpr.profile`、`gpr.anomalies` 等 |
|
||
| D2 | `src/data/dto/GprDto.hpp/cpp` | 雷达采集参数、异常列表的 JSON DTO |
|
||
| D3 | `src/controller/RadarWorkbenchController.cpp` | 补 `saveAnomalies` / `loadAnomalies` 后端调用 |
|
||
|
||
---
|
||
|
||
## 7. 待确认事项(阻塞开发或影响架构)
|
||
|
||
### 🔴 高优先级(阻塞 Phase C 及之后)
|
||
|
||
1. **ddCode 命名**
|
||
- 雷达数据集在后端的 `ddCode` 是什么?例如 `dd_gpr_data`、`dd_radar_profile`、`dd_gpr_bscan`?
|
||
- 是否按"三维雷达"vs"二维雷达"分不同 ddCode?(`.rd3` 是三维阵列雷达,`.iprb` 是二维单通道)
|
||
|
||
2. **数据集入口方式**
|
||
- 用户双击数据集后,前端如何获取雷达文件的本地路径?
|
||
- 方案A:后端 `/business/dataset/detail` 返回 `filePath` 字段,前端直接读本地文件。
|
||
- 方案B:后端提供 `/business/dd/gpr/download` 接口,前端先下载到临时目录再读。
|
||
- 方案C:后端直接提供 `/business/dd/gpr/profile` 返回二进制 B-scan 数据,前端不碰文件系统。
|
||
- **推荐 A 或 B**(前端实时处理需要本地文件随机访问/seek)。
|
||
|
||
3. **雷达采集参数的数据来源**
|
||
- 用户的"对象属性"表格(Date/Time/Antenna/Frequency/Traces/Channels 等)来自哪里?
|
||
- 方案A:全从 `.iprh` / `.rd3` 头文件解析(已有 `IprHeader` 部分字段,需扩充)。
|
||
- 方案B:后端 `/business/dd/gpr/info` 返回结构化 JSON,前端解析填表。
|
||
- **推荐 A(M1)+ B(M2 补充后端管理字段)**。
|
||
|
||
4. **多通道数据模型**
|
||
- 阵列雷达(16通道)在界面上是"一次全载 16 通道到内存"还是"按需切换通道懒加载"?
|
||
- 16 通道 × 3778 道 × 516 采样 × 4字节(float) ≈ **120MB**,全载可接受。
|
||
- 但处理管线是"每个通道独立跑"还是"跨通道联合处理"?(背景去除的 MeanAverage 是跨道,但这里用户描述是按通道的"同通道道数量"平均)
|
||
- **建议 M1 先全载,切换通道时即时换数据,处理管线按单通道跑**。
|
||
|
||
### 🟡 中优先级(影响 UI 细节,不阻塞架构)
|
||
|
||
5. **异常标注的数据模型确认**
|
||
- 异常的世界坐标系:用 **道号+采样点**(原始索引)还是 **里程+深度**(物理坐标)?
|
||
- 用户说"上传点坐标,对应测线名称,距离和深度等"——建议**双存**:前端用道号/采样点(抗重采样不变性),显示和上传时转里程/深度。
|
||
- 异常类型枚举:用户提到"空洞/疏松/脱空/管线"——是否还有其他?需要完整列表。
|
||
- 截图要求:"切片截图和剖面截图"——M1 阶段用 `QWidget::grab()` 生成 `QPixmap` 存本地临时文件,M2 上传时作为 multipart/form-data 附件?
|
||
|
||
6. **色阶方案**
|
||
- 用户提到"灰度图、彩虹色阶、冷暖色阶、振幅色阶"——这些是否已有定义?
|
||
- 复用现有 `core::ColorScale`(阶梯色阶)+ 预置几套默认 stops(Gray/Rainbow/Seismic/Amplitude)。
|
||
- 是否支持与 2D/3D 视图色阶联动?(跨视图色阶真源 `DatasetViewState`)——建议 **M1 先独立,M2 视需求接入联动**。
|
||
|
||
7. **处理参数默认值**
|
||
- 用户给出了大量默认值(如 `cutSamples=30`、`slidingWindowSamples=100` 等)。
|
||
- 这些默认值是"写死在前端代码"还是"后端 `/business/dd/gpr/defaultParams` 返回"?
|
||
- **建议 M1 写死,M2 后端可覆盖**。
|
||
|
||
8. **Wiggle 模式细节**
|
||
- Wiggle 画的波形线:正半周填充什么颜色?负半周填充什么颜色?线宽?背景色?
|
||
- 参考标准:地震勘探通常正半周填黑/红,负半周填白/蓝,背景白色。
|
||
|
||
9. **打标(Marker)数据格式**
|
||
- 用户说"第一列是道号,第二列是采样点,第三列是类型,第四列是可以插入的里程号"。
|
||
- 这个数据存在哪里?`.mrk` 文件?后端 API?需要样例。
|
||
|
||
10. **rd3 格式文档/样例**
|
||
- `.rd3` 是 MALA 三维探地雷达格式,需要头文件结构说明或样例文件,才能写解析器。
|
||
- 是否与 `.iprb` 布局类似(int16 道序存储)?
|
||
|
||
### 🟢 低优先级(可开发中迭代)
|
||
|
||
11. **独立工作区 vs 底部页签**
|
||
- 当前方案是独立 ADS Dock。如果产品坚持要走底部 `DatasetDetailPanel` 页签,需要大幅压缩 UI(控制栏收进折叠面板,A-Scan 弹窗或取消)。**请确认**。
|
||
|
||
12. **性能基准**
|
||
- "几分钟处理完公里级三维探地雷达数据"——具体数据量?
|
||
- 如:10km 测线 × 20道/米 × 516 采样 × 16 通道 ≈ 3.2GB 原始数据(int16)。这个量级全内存+实时处理是否可行?是否需要逐段流式处理(Slab)?
|
||
- **建议先以单测线(~2MB/通道)验证管线性能,再评估大数据量策略**。
|
||
|
||
---
|
||
|
||
## 8. 与现有架构的衔接点
|
||
|
||
| 现有组件 | 衔接方式 |
|
||
|---|---|
|
||
| `io/gpr/IprbReader` | 直接复用 `readIprb` / `readIprbRange` 读原始 B-scan |
|
||
| `io/gpr/IprHeader` | 解析采集参数(frequency、samples、traces、timeWindow、distanceInterval);需扩展字段(date、time、antenna 型号等,如果头文件里有) |
|
||
| `io/gpr/GprSurveyAssembler` | 多通道时复用 `assembleGprSurvey` 装配 `core::GprSurvey` |
|
||
| `core::ColorScale` | B-Scan 上色直接复用,支持全局透明度和 under/over/nan |
|
||
| `core::GeoLocalFrame` / `GpsTrack` | 里程归一化、地形校正需要 GPS 轨迹 → 复用 `GpsTrack` 解析 `.gps` 文件 |
|
||
| `app/panels/KeyValueView` | 对象属性面板直接复用 |
|
||
| `app/panels/ObjectExceptionPanel` / `AnomalyTablePanel` | 异常列表 UI 复用,数据模型从 `core::Anomaly` 扩展为 `core::GprAnomaly` |
|
||
| `ads::CDockWidget` | BScanWorkbench 继承或封装 ADS 停靠 |
|
||
| `DatasetViewState` | M2 可选接入跨视图色阶联动 |
|
||
| `net::ApiClient` / `ApiBatch` | M2 后端对接复用现有网络基础设施 |
|
||
|
||
---
|
||
|
||
## 9. 风险评估
|
||
|
||
| 风险 | 等级 | 缓解措施 |
|
||
|---|---|---|
|
||
| FFT 实现性能不足(带通/希尔伯特) | 中 | 先自实现基2-FFT(516点很小),实测不足再引入 FFTW |
|
||
| 偏移/反褶积算法复杂,M1 难以正确实现 | 高 | M1 不实现(P2),先留接口占位;用简化版本或后端预处理过渡 |
|
||
| 大数据量(公里级)内存/性能不达标 | 中 | 单测线验证通过后,评估是否需要:① 分块加载(Slab)② 处理管线也分块 ③ 降采样预览 + 全精度导出 |
|
||
| rd3 格式无文档,解析器开发受阻 | 中 | 向用户/厂商索要格式说明或样例;M1 先用 iprb 验证全链路 |
|
||
| UI 复杂度高,开发周期长 | 中 | 按 Phase A→B→C 分阶段交付,每阶段可独立验证;BScanCanvas 自绘虽然工作量大但可控 |
|
||
| API 未设计,后端对接延期 | 低 | M1 全走本地文件,视图和控制器与后端解耦;API 就绪后只改 data 层 |
|
||
|
||
---
|
||
|
||
_本文档为雷达 B-Scan 详情页的完整开发方案。待第 7 节高优先级事项确认后,按第 6 节 Phase A→B→C 顺序进入开发。_
|