feat/vtk-3d-view #7
|
|
@ -0,0 +1,68 @@
|
|||
# Task 9b 报告:gpr_poc CLI + 真实数据 headless 度量
|
||||
|
||||
状态:**PARTIAL / BLOCKED**
|
||||
- CLI 编译链接通过;`selftest` PASS(合成数据端到端跑通整条地基)。
|
||||
- 真实明星路数据 **BLOCKED**:前置 IO 层 `readIprb` 的 `traces=lastTrace+1` 严格校验
|
||||
与真实文件「道数=lastTrace」系统性不符,装配阶段即抛异常,无法实测建体指标。
|
||||
**未擅自修改前置/其单测**(被现有测试钉死的契约 + 跨任务边界),故真实指标暂缺,如实记录。
|
||||
|
||||
---
|
||||
|
||||
## 1. 交付物(均为本会话自有文件)
|
||||
|
||||
- `tools/gpr_poc/main.cpp` —— CLI:`build` / `load` / `selftest` 三子命令。
|
||||
- `tools/gpr_poc/Probe.hpp` —— header-only 计时(steady_clock)+ 峰值内存(Psapi `PeakWorkingSetSize`)。
|
||||
含 `NOMINMAX`/`WIN32_LEAN_AND_MEAN` 防 `<windows.h>` 宏污染 `std::numeric_limits::min/max`。
|
||||
- `tools/gpr_poc/CMakeLists.txt` —— 可执行 `gpr_poc`,链 `geopro_io_gpr/geopro_core/geopro_store/geopro_render`
|
||||
+ Windows `Psapi`;`vtk_module_autoinit` 注册 VTK 工厂。
|
||||
- 顶层 `CMakeLists.txt` —— 加 `add_subdirectory(tools/gpr_poc)`(在 `add_subdirectory(src)` 之后)。
|
||||
- `docs/superpowers/plans/poc-results-B.md` —— 实测结果(selftest PASS + 真实数据 BLOCKED 根因表)。
|
||||
|
||||
注:库目标实际名为 `geopro_store`(brief 写作 geopro_store/已对齐)与 `geopro_data`;
|
||||
本工具链 `geopro_store`(分块存储),正确。
|
||||
|
||||
## 2. 构建
|
||||
|
||||
- 配置:`cmd /c "build.bat configure"`(preset msvc-release,build/release)成功
|
||||
(cmd 被环境劫持但真实命令仍执行;以 build.ninja 出现 gpr_poc target 确认)。
|
||||
- 编译:PowerShell + vcvars64 直驱 cmake `--build build/release --target gpr_poc`。
|
||||
首次失败:`<windows.h>` min/max 宏污染 → 加 NOMINMAX 修复 → 二次链接成功。
|
||||
- 运行需 PATH 带 Qt6/VTK/vcpkg bin(headless 工具仍依赖这些 DLL)。
|
||||
|
||||
## 3. selftest 结果
|
||||
|
||||
```
|
||||
gpr_poc selftest
|
||||
[selftest] GridSpec 2x2x8 dz=0.714286
|
||||
[selftest] PASS (exit 0)
|
||||
```
|
||||
覆盖:assembleGprSurvey → buildGprVolume → write(brick=4) → buildPyramid(1) →
|
||||
WholeVolumeSource,断言维度/层数/体素非 blank 全通过。
|
||||
|
||||
## 4. 真实数据指标
|
||||
|
||||
**未实测(BLOCKED)**。根因:`readIprb`(`src/io/gpr/IprbReader.cpp:16`)
|
||||
`traces=lastTrace+1` 严格字节校验;真实明星路 14 通道每个恰含 `lastTrace` 道(少 1 道),
|
||||
逐通道实测一致(详见 poc-results-B.md §2 表)。非 OOM/超时——装配前读入即失败,
|
||||
调 `--cellXY` 无法绕过。现有 `tests/io/gpr/test_iprb_reader.cpp:30-31` 锁定该抛异常契约。
|
||||
|
||||
用的参数:`--line 001 --cellXY 0.2 --cellZ 0.05 --levels 2`(建体未到达)。
|
||||
预估几何(非实测,供核对):nx≈11118, ny≈8, nz≈1(深度尺度因土速单位为微米级,
|
||||
cellZ=0.05 压成单层——需 POC owner 复核土速/时窗单位与 cellZ)。
|
||||
|
||||
## 5. 提交前自检
|
||||
|
||||
- 仅 `git add` 自有文件:`tools/gpr_poc/*`、顶层 `CMakeLists.txt`、
|
||||
`docs/superpowers/plans/poc-results-B.md`、本报告 `.superpowers/sdd/task-9b-report.md`。
|
||||
- `git diff --cached --stat` 确认无 chart/scatter/quill/rangeslider 等并行会话行。
|
||||
- 顶层 CMakeLists 的暂存 diff 应仅含新增的 `add_subdirectory(tools/gpr_poc)` 一行块。
|
||||
|
||||
## 6. Concerns / 需 owner 决策
|
||||
|
||||
1. **真实数据 BLOCKER(高)**:`readIprb` 道数契约与真实数据不符。建议放宽为
|
||||
「道数 = 文件字节 / (samples·2)」(容忍 ±N 道),或确认 LAST TRACE 语义后去 +1,
|
||||
并同步改单测。落地后重跑两条命令即可补齐 §4 真实指标。
|
||||
2. **深度尺度(中)**:SOIL VELOCITY=100 m/s(头单位 m/µs ×1e6)→ 深度跨度微米级,
|
||||
cellZ=0.05 会把 Z 压成 1 层。影响真实体维度与 9c 渲染基准,需确认单位约定。
|
||||
3. 顶层 CMakeLists 当前 working tree 已有他会话的修改(视觉设计/chart 等);本会话只新增
|
||||
add_subdirectory 一行,暂存时务必只 stage 该文件并核对 diff,勿带入其他未暂存改动。
|
||||
|
|
@ -78,5 +78,8 @@ endif()
|
|||
|
||||
add_subdirectory(src)
|
||||
|
||||
# POC-B headless 度量 CLI(gpr_poc)。链 io_gpr/core/store/render,在真实数据上跑端到端度量。
|
||||
add_subdirectory(tools/gpr_poc)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
# POC-B 实测结果(gpr_poc headless 度量)
|
||||
|
||||
工具:`tools/gpr_poc`(CLI),构建产物 `build/release/tools/gpr_poc/gpr_poc.exe`。
|
||||
执行机:Windows 11,MSVC(VS18 Community)+ Ninja,Release(/O2)。
|
||||
日期:2026-06-23。
|
||||
|
||||
整条地基链路:
|
||||
`assembleGprSurvey → buildGprVolume → ChunkedVolumeStore::write → buildPyramid → WholeVolumeSource(load)`。
|
||||
|
||||
---
|
||||
|
||||
## 1. selftest(合成极小数据)—— PASS
|
||||
|
||||
命令:`gpr_poc selftest`
|
||||
|
||||
- 构造 2 通道合成 survey(samples=8,traces=12),写临时 `.iprb/.iprh/.ord`,
|
||||
走完整 `assembleGprSurvey → buildGprVolume → write(brick=4) → buildPyramid(1) → WholeVolumeSource`。
|
||||
- 断言:ntraces/samples/channels、channelY 升序、GridSpec `2x2x8`、建体维度、
|
||||
金字塔层数==2、整卷维度一致、(0,0,0) 非 blank。
|
||||
- 结果:**PASS**(退出码 0)。
|
||||
|
||||
结论:除真实 `.iprb` 读入外,**整条地基管线在合成数据上端到端跑通**
|
||||
(装配几何、建体量化、分块压缩落盘、金字塔降采样、整卷重组加载均正确)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 真实数据(D:\Downloads\明星路,线 001)—— **BLOCKED(前置 IO 层与真实数据契约不符)**
|
||||
|
||||
命令:
|
||||
```
|
||||
gpr_poc build "D:\Downloads\明星路" --line 001 --cellXY 0.2 --cellZ 0.05 --out %TEMP%\gpr_store_001 --levels 2
|
||||
```
|
||||
|
||||
进度:
|
||||
- 文件发现 **成功**:定位 14 通道 `明星路_001_A01..A14.iprb` + `明星路_001.ord`。
|
||||
- 装配 **失败**:`assembleGprSurvey` → `readIprb` 抛
|
||||
`文件大小与 samples*traces*2 不符: ..._A01.iprb`。
|
||||
|
||||
### 根因(off-by-one:lastTrace+1 vs 真实道数)
|
||||
|
||||
`readIprb`(前置 IO 层,`src/io/gpr/IprbReader.cpp:16`)硬编码
|
||||
`traces = h.lastTrace + 1` 并对文件字节数做**严格相等**校验。
|
||||
真实明星路每个通道文件恰好含 `lastTrace` 条道(少 1 道),逐通道实测:
|
||||
|
||||
| 通道 | 文件字节 | samples | LAST TRACE | 期望(=samples·(lastTrace+1)·2) | 实际道数(=bytes/(samples·2)) |
|
||||
|------|----------|---------|------------|--------------------------------|------------------------------|
|
||||
| A01 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A02 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
| A03 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A04 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
| A05 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A06 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
| A07 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A08 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
| A09 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A10 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
| A11 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A12 | 74392452 | 821 | 45306 | 74394094 | 45306 |
|
||||
| A13 | 74390810 | 821 | 45305 | 74392452 | 45305 |
|
||||
| A14 | 74394094 | 821 | 45307 | 74395736 | 45307 |
|
||||
|
||||
**规律一致**:所有通道「实际道数 == LAST TRACE」,即真实文件道数 = `lastTrace`,
|
||||
而 `readIprb` 期望 `lastTrace + 1`,每通道恰差 1 道(`samples·2 = 1642` 字节)。
|
||||
|
||||
这是**前置 IO 层契约与真实数据约定的系统性不符**,不是 gpr_poc CLI 的问题。
|
||||
现有单测(`tests/io/gpr/test_iprb_reader.cpp:30-31`)显式锁定
|
||||
「字节数 != samples·(lastTrace+1)·2 → 抛异常」,故 `lastTrace+1` 是被测试钉死的契约。
|
||||
|
||||
### 处置(本任务未擅自改前置/测试)
|
||||
|
||||
按任务纪律(外科手术式改动、不动他人已测契约、严禁编造指标),
|
||||
本会话**未修改** `readIprb` 或其单测,故真实数据的建体/维度/压缩比/加载/峰值内存
|
||||
**暂无法实测**,如实记录为 BLOCKED。
|
||||
|
||||
不是 OOM、不是超时——是装配前的文件读入契约不符,调粗 `--cellXY` 也无法绕过
|
||||
(在到达建体之前就抛了)。
|
||||
|
||||
### 建议解法(需 POC/IO 层 owner 决策,任一)
|
||||
|
||||
1. **放宽 `readIprb`**:以「文件字节数 / (samples·2)」反推真实道数(容忍 ±N 道),
|
||||
而非硬用 `lastTrace+1`;同步更新 `test_iprb_reader.cpp`。这与真实数据约定
|
||||
(道数 = `lastTrace`)一致,最贴近现场。
|
||||
2. **明确 LAST TRACE 语义**:若约定其为「道数」而非「末道索引」,则 `traces = lastTrace`
|
||||
(去掉 +1),同样需改实现 + 测试。
|
||||
3. 任一方案落地后,重跑:
|
||||
`gpr_poc build "D:\Downloads\明星路" --line 001 --cellXY 0.2 --cellZ 0.05 --out <store> --levels 2`
|
||||
再 `gpr_poc load <store>`,本文件 §2 即可补齐真实指标。
|
||||
|
||||
---
|
||||
|
||||
## 3. 预估几何(供解法落地后核对,非实测)
|
||||
|
||||
基于真实头:samples=821,dx≈0.049084 m,通道横偏 Y∈[-0.686, 0.686](跨度≈1.372 m),
|
||||
土速 100 m/s、时窗 160.352 ns ⇒ 深度跨度 `depthOfSample(820)≈8.0e-6 m`(量级极小)。
|
||||
|
||||
按 `--cellXY 0.2 --cellZ 0.05`、X 跨度≈`45305·0.049084≈2223 m`:
|
||||
- nx ≈ ceil(2223/0.2)+1 ≈ **11118**
|
||||
- ny ≈ ceil(1.372/0.2)+1 ≈ **8**
|
||||
- nz ≈ ceil(8.0e-6/0.05)+1 = **1**(Z 物理跨度远小于 cellZ,故仅 1 层)
|
||||
|
||||
**注意**:因 SOIL VELOCITY 存为 100 m/s(头文件单位 m/µs ×1e6 后),深度尺度为微米级,
|
||||
`--cellZ 0.05`(5 cm)会把整个深度压成 1 层。落地解法后,若要在 Z 方向有分辨率,
|
||||
需把 `--cellZ` 调到与深度跨度匹配的量级(约 1e-8 m),或复核土速/时窗单位约定。
|
||||
此项一并提请 POC owner 确认(影响真实体维度与后续渲染基准 9c)。
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# POC-B headless 度量 CLI(gpr_poc)。
|
||||
# 串起:geopro_io_gpr(解析装配)→ geopro_core(建体)→ geopro_store(分块落盘/金字塔)
|
||||
# → geopro_render(WholeVolumeSource 整卷加载)。
|
||||
# Windows 峰值内存用 Psapi(Probe.hpp)。
|
||||
|
||||
# VTK_LIBRARIES 在子作用域内由各 find_package 设定,这里显式再请求一次
|
||||
# (与 geopro_render 同组件集),确保本 target 的 vtk_module_autoinit 可用。
|
||||
find_package(VTK REQUIRED COMPONENTS
|
||||
CommonCore CommonDataModel RenderingCore RenderingOpenGL2 GUISupportQt)
|
||||
|
||||
add_executable(gpr_poc main.cpp)
|
||||
|
||||
target_include_directories(gpr_poc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(gpr_poc PRIVATE
|
||||
geopro_io_gpr
|
||||
geopro_core
|
||||
geopro_store
|
||||
geopro_render)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(gpr_poc PRIVATE Psapi)
|
||||
endif()
|
||||
|
||||
target_compile_features(gpr_poc PRIVATE cxx_std_17)
|
||||
set_target_properties(gpr_poc PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
|
||||
|
||||
# geopro_render 透传 VTK_LIBRARIES(PUBLIC),消费方需 autoinit 各 VTK 模块工厂,
|
||||
# 否则 vtkImageData/渲染对象工厂未注册。
|
||||
vtk_module_autoinit(TARGETS gpr_poc MODULES ${VTK_LIBRARIES})
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef GEOPRO_TOOLS_GPR_POC_PROBE_HPP
|
||||
#define GEOPRO_TOOLS_GPR_POC_PROBE_HPP
|
||||
|
||||
// 轻量 headless 度量探针:墙钟计时 + 进程峰值工作集内存。
|
||||
// 仅供 gpr_poc CLI 使用,header-only,零外部依赖(Windows 用 Psapi)。
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
// 防止 <windows.h> 的 min/max 宏污染 std::numeric_limits<>::min()/max()
|
||||
// (ScalarVolumeI16.hpp 等会因此编译失败)。
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#endif
|
||||
|
||||
namespace geopro::tools {
|
||||
|
||||
// 墙钟计时器:构造即起表,elapsedMs() 返回毫秒(double)。
|
||||
class Stopwatch {
|
||||
public:
|
||||
Stopwatch() : start_(std::chrono::steady_clock::now()) {}
|
||||
|
||||
void reset() { start_ = std::chrono::steady_clock::now(); }
|
||||
|
||||
double elapsedMs() const {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(now - start_).count();
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point start_;
|
||||
};
|
||||
|
||||
// 进程级度量探针。
|
||||
struct Probe {
|
||||
// 进程峰值工作集(MB)。Windows 取 GetProcessMemoryInfo::PeakWorkingSetSize;
|
||||
// 其它平台返回 0(本任务仅 Windows 实测)。
|
||||
static double peakMemMB() {
|
||||
#if defined(_WIN32)
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
return static_cast<double>(pmc.PeakWorkingSetSize) / (1024.0 * 1024.0);
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace geopro::tools
|
||||
|
||||
#endif // GEOPRO_TOOLS_GPR_POC_PROBE_HPP
|
||||
|
|
@ -0,0 +1,445 @@
|
|||
// gpr_poc —— POC-B headless 度量 CLI。
|
||||
//
|
||||
// 串起整条地基:发现 14 通道 .iprb + .ord → assembleGprSurvey → buildGprVolume
|
||||
// → ChunkedVolumeStore::write → buildPyramid → WholeVolumeSource(load),
|
||||
// 在真实/合成数据上输出可测的真实指标(耗时/维度/体积/压缩比/加载/峰值内存)。
|
||||
//
|
||||
// 子命令:
|
||||
// gpr_poc build <dir> [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out <storeDir>] [--levels 2]
|
||||
// gpr_poc load <storeDir>
|
||||
// gpr_poc selftest
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Probe.hpp"
|
||||
|
||||
#include "core/algo/GprVolumeBuilder.hpp"
|
||||
#include "core/algo/IInterpolator.hpp"
|
||||
#include "core/model/GprSurvey.hpp"
|
||||
#include "core/model/ScalarVolumeI16.hpp"
|
||||
#include "data/store/ChunkedVolumeStore.hpp"
|
||||
#include "io/gpr/GprSurveyAssembler.hpp"
|
||||
#include "render/source/WholeVolumeSource.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using geopro::tools::Probe;
|
||||
using geopro::tools::Stopwatch;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kChannels = 14;
|
||||
|
||||
// ---- 命令行参数解析(极简 --key value)----
|
||||
struct Args {
|
||||
std::map<std::string, std::string> kv;
|
||||
std::vector<std::string> positional;
|
||||
|
||||
std::string get(const std::string& key, const std::string& def) const {
|
||||
auto it = kv.find(key);
|
||||
return it != kv.end() ? it->second : def;
|
||||
}
|
||||
};
|
||||
|
||||
Args parseArgs(int argc, char** argv, int start) {
|
||||
Args a;
|
||||
for (int i = start; i < argc; ++i) {
|
||||
std::string tok = argv[i];
|
||||
if (tok.rfind("--", 0) == 0 && i + 1 < argc) {
|
||||
a.kv[tok.substr(2)] = argv[i + 1];
|
||||
++i;
|
||||
} else {
|
||||
a.positional.push_back(tok);
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// 把一行指标追加写入 last-metrics.txt(与可执行同目录的工具源目录无关,
|
||||
// 写到当前工作目录便于汇总;CSV 风格一行)。
|
||||
void writeMetricLine(const std::string& line) {
|
||||
std::ofstream f("last-metrics.txt", std::ios::app);
|
||||
if (f) f << line << "\n";
|
||||
}
|
||||
|
||||
// 发现某线 14 通道 .iprb(按通道号 A01..A14 排序)+ 该线 .ord。
|
||||
struct LineFiles {
|
||||
std::vector<std::string> iprb; // 已按通道号排序
|
||||
std::string ord;
|
||||
};
|
||||
|
||||
LineFiles discoverLine(const std::string& dir, const std::string& line) {
|
||||
LineFiles lf;
|
||||
std::map<int, std::string> byChannel; // 通道号 → 路径(自动按号排序)
|
||||
std::string ordPath;
|
||||
|
||||
for (const auto& e : fs::directory_iterator(dir)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
const std::string name = e.path().filename().string();
|
||||
const std::string ext = e.path().extension().string();
|
||||
|
||||
// .ord:优先匹配本线(含 "_<line>" 且以 .ord 结尾),否则记下工区任一 .ord 作兜底。
|
||||
if (ext == ".ord") {
|
||||
if (name.find("_" + line + ".") != std::string::npos) {
|
||||
ordPath = e.path().string();
|
||||
} else if (ordPath.empty()) {
|
||||
ordPath = e.path().string();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// .iprb:匹配 "*_<line>_A<NN>.iprb"。
|
||||
if (ext != ".iprb") continue;
|
||||
const std::string tag = "_" + line + "_A";
|
||||
const std::size_t pos = name.find(tag);
|
||||
if (pos == std::string::npos) continue;
|
||||
const std::size_t numStart = pos + tag.size();
|
||||
std::size_t numEnd = numStart;
|
||||
while (numEnd < name.size() &&
|
||||
std::isdigit(static_cast<unsigned char>(name[numEnd]))) {
|
||||
++numEnd;
|
||||
}
|
||||
if (numEnd == numStart) continue;
|
||||
const int ch = std::stoi(name.substr(numStart, numEnd - numStart));
|
||||
byChannel[ch] = e.path().string();
|
||||
}
|
||||
|
||||
for (const auto& [ch, path] : byChannel) lf.iprb.push_back(path);
|
||||
lf.ord = ordPath;
|
||||
return lf;
|
||||
}
|
||||
|
||||
// 由 survey 推 GridSpec:X 沿测线,Y 跨通道,Z 深度。
|
||||
geopro::core::GridSpec specFromSurvey(const geopro::core::GprSurvey& s,
|
||||
double cellXY, double cellZ) {
|
||||
geopro::core::GridSpec spec{};
|
||||
|
||||
const double rangeX =
|
||||
(s.ntraces > 1) ? (s.ntraces - 1) * s.dx : 0.0;
|
||||
const double y0 = s.channelY.empty() ? 0.0 : s.channelY.front();
|
||||
const double y1 = s.channelY.empty() ? 0.0 : s.channelY.back();
|
||||
const double rangeY = y1 - y0;
|
||||
const double rangeZ =
|
||||
(s.samples > 1) ? (s.samples - 1) * s.dz : 0.0;
|
||||
|
||||
auto cells = [](double range, double cell) {
|
||||
if (cell <= 0.0) return 1;
|
||||
return static_cast<int>(std::ceil(range / cell)) + 1;
|
||||
};
|
||||
|
||||
spec.ox = s.x0;
|
||||
spec.oy = y0;
|
||||
spec.oz = s.z0;
|
||||
spec.dx = cellXY;
|
||||
spec.dy = cellXY;
|
||||
spec.dz = cellZ;
|
||||
spec.nx = cells(rangeX, cellXY);
|
||||
spec.ny = cells(rangeY, cellXY);
|
||||
spec.nz = cells(rangeZ, cellZ);
|
||||
spec.power = 2.0;
|
||||
spec.maxDist = cellXY * 2.0;
|
||||
return spec;
|
||||
}
|
||||
|
||||
// 落盘 data.bin 体积(所有 data*.bin 之和,含金字塔各级)。
|
||||
std::int64_t storeDataBytes(const std::string& dir) {
|
||||
std::int64_t total = 0;
|
||||
for (const auto& e : fs::directory_iterator(dir)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
const std::string name = e.path().filename().string();
|
||||
if (name.rfind("data", 0) == 0 &&
|
||||
e.path().extension().string() == ".bin") {
|
||||
total += static_cast<std::int64_t>(e.file_size());
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int cmdBuild(int argc, char** argv) {
|
||||
const Args a = parseArgs(argc, argv, 2);
|
||||
if (a.positional.empty()) {
|
||||
std::cerr << "用法: gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
|
||||
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n";
|
||||
return 2;
|
||||
}
|
||||
const std::string dir = a.positional[0];
|
||||
const std::string line = a.get("line", "001");
|
||||
const double cellXY = std::stod(a.get("cellXY", "0.2"));
|
||||
const double cellZ = std::stod(a.get("cellZ", "0.05"));
|
||||
const int levels = std::stoi(a.get("levels", "2"));
|
||||
const std::string out =
|
||||
a.get("out", (fs::temp_directory_path() / ("gpr_store_" + line)).string());
|
||||
|
||||
std::cout << "[build] dir=" << dir << " line=" << line
|
||||
<< " cellXY=" << cellXY << " cellZ=" << cellZ
|
||||
<< " levels=" << levels << " out=" << out << "\n";
|
||||
|
||||
const LineFiles lf = discoverLine(dir, line);
|
||||
std::cout << "[build] 发现通道数=" << lf.iprb.size()
|
||||
<< " ord=" << (lf.ord.empty() ? "(无)" : lf.ord) << "\n";
|
||||
if (lf.iprb.size() != static_cast<std::size_t>(kChannels)) {
|
||||
std::cerr << "[build] 警告: 通道数 != " << kChannels
|
||||
<< "(仍按发现数继续)\n";
|
||||
}
|
||||
if (lf.iprb.empty() || lf.ord.empty()) {
|
||||
std::cerr << "[build] 错误: 未发现 .iprb 或 .ord\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1) 装配
|
||||
Stopwatch swAsm;
|
||||
geopro::core::GprSurvey survey =
|
||||
geopro::io::gpr::assembleGprSurvey(lf.iprb, lf.ord);
|
||||
const double asmMs = swAsm.elapsedMs();
|
||||
std::cout << "[build] 装配完成 ntraces=" << survey.ntraces
|
||||
<< " samples=" << survey.samples
|
||||
<< " channels=" << survey.channelY.size()
|
||||
<< " dx=" << survey.dx << " dz=" << survey.dz << "\n";
|
||||
|
||||
// 2) 建体
|
||||
const geopro::core::GridSpec spec = specFromSurvey(survey, cellXY, cellZ);
|
||||
std::cout << "[build] GridSpec nx=" << spec.nx << " ny=" << spec.ny
|
||||
<< " nz=" << spec.nz << " dx=" << spec.dx << " dy=" << spec.dy
|
||||
<< " dz=" << spec.dz << " maxDist=" << spec.maxDist << "\n";
|
||||
|
||||
Stopwatch swBuild;
|
||||
geopro::core::BuiltI16 built = geopro::core::buildGprVolume(survey, spec);
|
||||
const double buildMs = swBuild.elapsedMs();
|
||||
|
||||
const std::int64_t nx = built.vol.nx(), ny = built.vol.ny(), nz = built.vol.nz();
|
||||
const std::int64_t voxels = nx * ny * nz;
|
||||
const std::int64_t rawBytes = voxels * 2; // int16
|
||||
|
||||
// 3) 落盘 + 金字塔
|
||||
fs::create_directories(out);
|
||||
Stopwatch swWrite;
|
||||
geopro::data::ChunkedVolumeStore::write(out, built);
|
||||
const double writeMs = swWrite.elapsedMs();
|
||||
|
||||
Stopwatch swPyr;
|
||||
{
|
||||
geopro::data::ChunkedVolumeStore store(out);
|
||||
store.buildPyramid(levels);
|
||||
}
|
||||
const double pyrMs = swPyr.elapsedMs();
|
||||
|
||||
const std::int64_t dataBytes = storeDataBytes(out);
|
||||
const double ratio =
|
||||
dataBytes > 0 ? static_cast<double>(rawBytes) / dataBytes : 0.0;
|
||||
const double peak = Probe::peakMemMB();
|
||||
|
||||
std::cout << "\n=== build 指标 ===\n";
|
||||
std::cout << "装配耗时(ms) : " << asmMs << "\n";
|
||||
std::cout << "建体耗时(ms) : " << buildMs << "\n";
|
||||
std::cout << "落盘耗时(ms) : " << writeMs << "\n";
|
||||
std::cout << "金字塔耗时(ms) : " << pyrMs << "\n";
|
||||
std::cout << "体维度 : " << nx << " x " << ny << " x " << nz << "\n";
|
||||
std::cout << "体素数 : " << voxels << "\n";
|
||||
std::cout << "原始体积(B) : " << rawBytes << " ("
|
||||
<< rawBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||
std::cout << "data.bin(B) : " << dataBytes << " ("
|
||||
<< dataBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||
std::cout << "压缩比 : " << ratio << " x\n";
|
||||
std::cout << "峰值内存(MB) : " << peak << "\n";
|
||||
|
||||
writeMetricLine(
|
||||
"build,line=" + line + ",cellXY=" + std::to_string(cellXY) +
|
||||
",cellZ=" + std::to_string(cellZ) + ",nx=" + std::to_string(nx) +
|
||||
",ny=" + std::to_string(ny) + ",nz=" + std::to_string(nz) +
|
||||
",voxels=" + std::to_string(voxels) +
|
||||
",rawB=" + std::to_string(rawBytes) +
|
||||
",dataB=" + std::to_string(dataBytes) +
|
||||
",ratio=" + std::to_string(ratio) + ",asmMs=" + std::to_string(asmMs) +
|
||||
",buildMs=" + std::to_string(buildMs) +
|
||||
",writeMs=" + std::to_string(writeMs) +
|
||||
",pyrMs=" + std::to_string(pyrMs) + ",peakMB=" + std::to_string(peak));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmdLoad(int argc, char** argv) {
|
||||
const Args a = parseArgs(argc, argv, 2);
|
||||
if (a.positional.empty()) {
|
||||
std::cerr << "用法: gpr_poc load <storeDir>\n";
|
||||
return 2;
|
||||
}
|
||||
const std::string dir = a.positional[0];
|
||||
std::cout << "[load] storeDir=" << dir << "\n";
|
||||
|
||||
Stopwatch sw;
|
||||
geopro::render::WholeVolumeSource src(dir);
|
||||
const double loadMs = sw.elapsedMs();
|
||||
|
||||
const auto& m = src.meta();
|
||||
const std::int64_t voxels =
|
||||
static_cast<std::int64_t>(m.nx) * m.ny * m.nz;
|
||||
const std::int64_t wholeBytes = voxels * 2; // VTK_SHORT
|
||||
const double peak = Probe::peakMemMB();
|
||||
|
||||
std::cout << "\n=== load 指标 ===\n";
|
||||
std::cout << "加载耗时(ms) : " << loadMs << "\n";
|
||||
std::cout << "整卷维度 : " << m.nx << " x " << m.ny << " x " << m.nz
|
||||
<< "\n";
|
||||
std::cout << "整卷字节(B) : " << wholeBytes << " ("
|
||||
<< wholeBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||
std::cout << "峰值内存(MB) : " << peak << "\n";
|
||||
|
||||
writeMetricLine("load,dir=" + dir + ",nx=" + std::to_string(m.nx) +
|
||||
",ny=" + std::to_string(m.ny) +
|
||||
",nz=" + std::to_string(m.nz) +
|
||||
",wholeB=" + std::to_string(wholeBytes) +
|
||||
",loadMs=" + std::to_string(loadMs) +
|
||||
",peakMB=" + std::to_string(peak));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- selftest:合成极小数据走完整 build→load 管线 ----
|
||||
|
||||
// 写一个极小通道的 .iprb + .iprh(samples 采样、traces 道,值 = base + t + s)。
|
||||
void writeSyntheticChannel(const fs::path& iprbPath, int samples, int traces,
|
||||
std::int16_t base) {
|
||||
const fs::path iprhPath =
|
||||
fs::path(iprbPath).replace_extension(".iprh");
|
||||
std::ofstream h(iprhPath);
|
||||
h << "SAMPLES: " << samples << "\n";
|
||||
h << "LAST TRACE: " << (traces - 1) << "\n";
|
||||
h << "CHANNELS: 2\n";
|
||||
h << "TIMEWINDOW: 100.0\n";
|
||||
h << "SOIL VELOCITY: 100.0\n"; // m/µs → ×1e6 → 1e8 m/s
|
||||
h << "DISTANCE INTERVAL: 0.05\n";
|
||||
h.close();
|
||||
|
||||
std::ofstream b(iprbPath, std::ios::binary);
|
||||
// 布局 [trace*samples + s],s 最快。
|
||||
for (int t = 0; t < traces; ++t) {
|
||||
for (int s = 0; s < samples; ++s) {
|
||||
const std::int16_t v =
|
||||
static_cast<std::int16_t>(base + t + s);
|
||||
b.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cmdSelftest() {
|
||||
std::cout << "[selftest] 构造极小合成 survey(2 通道)...\n";
|
||||
const fs::path tmp =
|
||||
fs::temp_directory_path() / "gpr_poc_selftest";
|
||||
std::error_code ec;
|
||||
fs::remove_all(tmp, ec);
|
||||
fs::create_directories(tmp);
|
||||
|
||||
const int samples = 8;
|
||||
const int traces = 12;
|
||||
|
||||
// 2 通道 .iprb/.iprh + .ord(末列==1 标记有效通道,第 2 列为横偏 Y)。
|
||||
writeSyntheticChannel(tmp / "syn_001_A01.iprb", samples, traces,
|
||||
/*base=*/100);
|
||||
writeSyntheticChannel(tmp / "syn_001_A02.iprb", samples, traces,
|
||||
/*base=*/200);
|
||||
{
|
||||
std::ofstream ord(tmp / "syn_001.ord");
|
||||
ord << "0 0.000000 -1.5 1\n";
|
||||
ord << "1 1.000000 -1.5 1\n";
|
||||
}
|
||||
|
||||
const std::vector<std::string> iprb = {
|
||||
(tmp / "syn_001_A01.iprb").string(),
|
||||
(tmp / "syn_001_A02.iprb").string()};
|
||||
const std::string ord = (tmp / "syn_001.ord").string();
|
||||
|
||||
bool ok = true;
|
||||
auto check = [&](bool cond, const std::string& msg) {
|
||||
if (!cond) {
|
||||
std::cerr << "[selftest] FAIL: " << msg << "\n";
|
||||
ok = false;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// 装配
|
||||
geopro::core::GprSurvey survey =
|
||||
geopro::io::gpr::assembleGprSurvey(iprb, ord);
|
||||
check(survey.ntraces == traces, "ntraces");
|
||||
check(survey.samples == samples, "samples");
|
||||
check(survey.channelY.size() == 2, "channels");
|
||||
// channelY 升序:A01 偏移 0.0 在前,A02 偏移 1.0 在后。
|
||||
check(survey.channelY.front() < survey.channelY.back(), "channelY 升序");
|
||||
|
||||
// 建体:cellXY 取通道间距 1.0 → ny=2;cellZ 较细确保 nz>1。
|
||||
const double cellXY = 1.0;
|
||||
const double cellZ = std::max(survey.dz, 1e-12);
|
||||
const geopro::core::GridSpec spec =
|
||||
specFromSurvey(survey, cellXY, cellZ);
|
||||
std::cout << "[selftest] GridSpec " << spec.nx << "x" << spec.ny << "x"
|
||||
<< spec.nz << " dz=" << spec.dz << "\n";
|
||||
check(spec.ny == 2, "ny==2");
|
||||
|
||||
geopro::core::BuiltI16 built =
|
||||
geopro::core::buildGprVolume(survey, spec);
|
||||
check(built.vol.nx() == spec.nx, "built nx");
|
||||
check(built.vol.ny() == spec.ny, "built ny");
|
||||
check(built.vol.nz() == spec.nz, "built nz");
|
||||
|
||||
// 落盘 + 金字塔
|
||||
const std::string store = (tmp / "store").string();
|
||||
fs::create_directories(store);
|
||||
geopro::data::ChunkedVolumeStore::write(store, built, /*brick=*/4);
|
||||
{
|
||||
geopro::data::ChunkedVolumeStore s(store);
|
||||
s.buildPyramid(1);
|
||||
check(s.levels() == 2, "金字塔层数==2");
|
||||
}
|
||||
|
||||
// 加载整卷,校验维度一致
|
||||
geopro::render::WholeVolumeSource src(store);
|
||||
check(src.meta().nx == spec.nx, "load nx");
|
||||
check(src.meta().ny == spec.ny, "load ny");
|
||||
check(src.meta().nz == spec.nz, "load nz");
|
||||
|
||||
// 某体素值合理性:x0/y0 角点应有非 blank 量化值(落格命中首道首通道)。
|
||||
const std::int16_t q = built.vol.at(0, 0, 0);
|
||||
check(q != geopro::core::ScalarVolumeI16::kBlank, "(0,0,0) 非 blank");
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[selftest] 异常: " << e.what() << "\n";
|
||||
ok = false;
|
||||
}
|
||||
|
||||
fs::remove_all(tmp, ec);
|
||||
std::cout << "[selftest] " << (ok ? "PASS" : "FAIL") << "\n";
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
void usage() {
|
||||
std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n"
|
||||
" gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
|
||||
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n"
|
||||
" gpr_poc load <storeDir>\n"
|
||||
" gpr_poc selftest\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
const std::string cmd = argv[1];
|
||||
try {
|
||||
if (cmd == "build") return cmdBuild(argc, argv);
|
||||
if (cmd == "load") return cmdLoad(argc, argv);
|
||||
if (cmd == "selftest") return cmdSelftest();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "错误: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
Loading…
Reference in New Issue