656 lines
25 KiB
Markdown
656 lines
25 KiB
Markdown
# 雷达 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<float> run(const std::vector<float>& raw,
|
||
const GprTraceMeta& meta);
|
||
// 标记某节点参数已变更(下次 run 时从该节点开始重算)
|
||
void markDirty(const QString& nodeName);
|
||
// 标记全部重算
|
||
void markAllDirty();
|
||
private:
|
||
std::vector<ProcNodeDesc> nodes_;
|
||
std::unordered_map<QString, std::vector<float>> cache_;
|
||
std::unordered_set<QString> 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<const geopro::core::ZeroTimeParams*>(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<float> rawData_; // 当前通道原始数据
|
||
std::vector<float> 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<GprAnomaly> 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<float> data; // traces × samples,行主序
|
||
};
|
||
|
||
struct GprProfilePayload {
|
||
GprTraceMeta meta; // 几何参数
|
||
std::vector<GprChannelData> 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<controller::TabSpec> 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<IDetailView>(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 接入**
|
||
|
||
是否按此顺序推进?还是先确认完所有事项再开始编码?
|