From a32822f7d636d8a59b3223a3846900a979e4a676 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 24 Jun 2026 20:50:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(gpr3dv):=20=E6=A1=A5=E6=8E=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=90=8E=E7=AB=8B=E6=96=B9=E4=BD=93=E2=86=92geopro?= =?UTF-8?q?=E9=87=8F=E5=8C=96/=E5=88=86=E5=9D=97/VTK=E4=BD=93=E7=BB=98?= =?UTF-8?q?=E5=88=B6(P2=20=E6=B8=B2=E7=AC=AC=E4=B8=80=E6=9D=A1=E7=BA=BF?= =?UTF-8?q?=E7=9C=9F=E4=B8=89=E7=BB=B4=E4=BD=93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 gpr3dv(P1)处理链产出的处理后立方体桥接到 geopro 已有的量化+ChunkedVolumeStore +VTK 体绘制,渲出第一条线(明星路_001)的真三维体。算法零改动,仅复用 geopro 量化/ store/渲染。 - 新增桥接 src/io/gpr/Gpr3dvVolumeBridge.{hpp,cpp}(独立 target geopro_gpr3dv_bridge, 不污染纯 C++17 解析层):走 P1 原版 API load→buildVolumeData→runPipeline(默认链) →再 buildVolumeData(处理后),得 volumeData[通道][道][样本];轴映射 X=道/Y=通道/ Z=样本;扫值域定 Quant(offset=中点);世界 spacing(dx=道距/dy=通道横距/dz=深度采样距)。 - gpr_poc 加子命令 build-line --out [--levels N]: 桥接→ChunkedVolumeStore::write+buildPyramid,报维度/量化/spacing/内存/耗时;view 渲。 - 测试 tests/io/gpr/test_gpr3dv_volume_bridge.cpp:合成 2 通道 Impulse 测线走真链, 校验轴映射/spacing/量化(offset=中点)/稠密体无 kBlank/缺线抛异常。 真实数据验收(明星路_001):体维度 45305×14×796(道×通道×样本,样本由 821 经零时校正 裁至 796),处理后值域[-9249,9206] scale=0.288 offset=-21.5,spacing dx=0.049/dy=0.105/ dz=0.0101m,落盘 845MB 压缩比 1.14x;view --preview/--base 渲出无纹理错、整卷盖全。 全量测试通过。 --- src/io/gpr/CMakeLists.txt | 9 + src/io/gpr/Gpr3dvVolumeBridge.cpp | 185 +++++++++++++++++++++ src/io/gpr/Gpr3dvVolumeBridge.hpp | 48 ++++++ tests/CMakeLists.txt | 5 + tests/io/gpr/test_gpr3dv_volume_bridge.cpp | 130 +++++++++++++++ tools/gpr_poc/CMakeLists.txt | 1 + tools/gpr_poc/main.cpp | 107 ++++++++++++ 7 files changed, 485 insertions(+) create mode 100644 src/io/gpr/Gpr3dvVolumeBridge.cpp create mode 100644 src/io/gpr/Gpr3dvVolumeBridge.hpp create mode 100644 tests/io/gpr/test_gpr3dv_volume_bridge.cpp diff --git a/src/io/gpr/CMakeLists.txt b/src/io/gpr/CMakeLists.txt index e670c63..1d6bcd2 100644 --- a/src/io/gpr/CMakeLists.txt +++ b/src/io/gpr/CMakeLists.txt @@ -8,3 +8,12 @@ target_link_libraries(geopro_io_gpr PUBLIC geopro_core) # 纯 C++17 解析层,零 Qt/VTK。顶层全局开启了 AUTOMOC/UIC/RCC,这里显式关闭保持纯净。 set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) + +# gpr3dv 桥接层(P2):把 vendored gpr3dv(P1) 处理后立方体 → geopro 量化体(BuiltI16)。 +# 独立 target(不污染上面的纯 C++17 解析层):链 geopro_gpr3dv(Qt)+geopro_core。 +add_library(geopro_gpr3dv_bridge STATIC Gpr3dvVolumeBridge.cpp) +target_include_directories(geopro_gpr3dv_bridge PUBLIC ${CMAKE_SOURCE_DIR}/src) +target_compile_features(geopro_gpr3dv_bridge PUBLIC cxx_std_17) +target_link_libraries(geopro_gpr3dv_bridge PUBLIC geopro_core geopro_gpr3dv) +set_target_properties(geopro_gpr3dv_bridge PROPERTIES + AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) diff --git a/src/io/gpr/Gpr3dvVolumeBridge.cpp b/src/io/gpr/Gpr3dvVolumeBridge.cpp new file mode 100644 index 0000000..f78d0d3 --- /dev/null +++ b/src/io/gpr/Gpr3dvVolumeBridge.cpp @@ -0,0 +1,185 @@ +#include "io/gpr/Gpr3dvVolumeBridge.hpp" + +#include +#include +#include +#include + +#include + +#include "GPRDataModel.h" +#include "IprhParser.h" +#include "RadarProcessor.h" + +#include "core/model/ScalarVolumeI16.hpp" + +namespace geopro::io::gpr { + +namespace { + +// 全体 traces 平均绝对幅值——朴素的「处理是否生效」标量证据(与 P1 冒烟同口径)。 +double meanAbsAmplitude(const GPRDataModel& model) { + long double sum = 0.0L; + long long count = 0; + for (const RadarTrace& tr : model.traces) { + for (short v : tr.amplitudes) { + sum += static_cast(v < 0 ? -v : v); + ++count; + } + } + return count > 0 ? static_cast(sum / count) : 0.0; +} + +// 通道横向间距(米):优先 chYOffsets 跨度 /(通道数-1);取不到退路 ≈1.37/(通道数-1)。 +double channelSpacingY(const GPRDataModel::Header& h, int channels) { + if (channels <= 1) return 1.0; + const auto& off = h.chYOffsets; + if (off.size() >= 2) { + float lo = off.front(), hi = off.front(); + for (float v : off) { + lo = std::min(lo, v); + hi = std::max(hi, v); + } + const double span = static_cast(hi) - static_cast(lo); + if (span > 1e-9) return span / (channels - 1); + } + // 退路:明星路 14 通道横向跨度 ≈1.37m(-0.686~+0.686)。 + constexpr double kArrayWidthM = 1.37; + return kArrayWidthM / (channels - 1); +} + +// 深度采样间距(米):(timeWindow/samples) ns × 波速(m/ns) / 2(往返)。 +// 与 GPRDataModel::calculateDepth 同式。取不到 → 1.0(单位间距)。 +double depthSpacingZ(const GPRDataModel::Header& h) { + if (h.samplesPerTrace <= 0 || h.timeWindowNs <= 0.0) return 1.0; + const double timePerSample = h.timeWindowNs / h.samplesPerTrace; // ns + const double vel = h.waveVelocity > 0.0 ? h.waveVelocity : 0.1; // m/ns + const double dz = timePerSample * vel / 2.0; + return dz > 1e-12 ? dz : 1.0; +} + +double nowMs(std::chrono::steady_clock::time_point t0) { + const auto t1 = std::chrono::steady_clock::now(); + return std::chrono::duration(t1 - t0).count(); +} + +} // namespace + +geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, + const std::string& linePrefix, + BridgeMetrics* metricsOut) { + const QString dir = QString::fromLocal8Bit(lineDir.c_str()); + const QString base = QString::fromLocal8Bit(linePrefix.c_str()); + + // 1) 走 P1 链:load → buildVolumeData(处理前) → runPipeline → 再 buildVolumeData。 + const auto tLoad = std::chrono::steady_clock::now(); + GPRDataModel model; + if (!IprhParser::loadImpulseMultiChannel(dir, base, model)) { + throw std::runtime_error("loadImpulseMultiChannel 失败: " + lineDir + " / " + + linePrefix); + } + model.buildVolumeData(); // 处理前立方体(用于 before 统计) + const double meanBefore = meanAbsAmplitude(model); + const double loadMs = nowMs(tLoad); + + const auto tPipe = std::chrono::steady_clock::now(); + RadarProcessor::ProcPipeline pipeline; + pipeline.setDefaultFlow(); + GPRDataModel processed = RadarProcessor::runPipeline(model, pipeline); + // ★关键:runPipeline 原位变换 traces 且零时校正会改 samplesPerTrace, + // 不重建 volumeData,故必须再建一次拿处理后立方体。 + processed.buildVolumeData(); + const double meanAfter = meanAbsAmplitude(processed); + const double pipelineMs = nowMs(tPipe); + + // 2) 立方体维度:volumeData[通道][道][样本] → 轴 X=道/Y=通道/Z=样本。 + const int channels = processed.volumeData.size(); + const int traces = channels > 0 ? processed.volumeData[0].size() : 0; + const int samples = + (channels > 0 && traces > 0) ? processed.volumeData[0][0].size() : 0; + if (channels <= 0 || traces <= 0 || samples <= 0) { + throw std::runtime_error("处理后立方体为空(通道/道/样本 = " + + std::to_string(channels) + "/" + + std::to_string(traces) + "/" + + std::to_string(samples) + ")"); + } + const int nx = traces; // X=道(沿测线) + const int ny = channels; // Y=通道(横向) + const int nz = samples; // Z=样本(深度) + + // 3) 扫处理后值域 → Quant(offset=中点,防溢出)。 + const auto tFill = std::chrono::steady_clock::now(); + short rawMin = std::numeric_limits::max(); + short rawMax = std::numeric_limits::min(); + for (int c = 0; c < channels; ++c) { + const auto& chData = processed.volumeData[c]; + for (int t = 0; t < traces && t < chData.size(); ++t) { + const auto& trData = chData[t]; + for (int s = 0; s < samples && s < trData.size(); ++s) { + const short v = trData[s]; + if (v < rawMin) rawMin = v; + if (v > rawMax) rawMax = v; + } + } + } + if (rawMin > rawMax) { // 退化(理论不至):置零区间。 + rawMin = 0; + rawMax = 0; + } + const double vmin = static_cast(rawMin); + const double vmax = static_cast(rawMax); + + geopro::core::Quant quant; + // 量化到 int16 有效区间 [-32767, 32767](kBlank=-32768 保留),留两端裕度用 64000。 + quant.scale = (vmax > vmin) ? (vmax - vmin) / 64000.0 : 1.0; + quant.offset = 0.5 * (vmin + vmax); // 中点 → 防溢出 + + // 4) 逐 (ch,trace,sample) 填体。GPR 立方体为稠密体(每体素有值),无空洞 → 不置 kBlank。 + geopro::core::BuiltI16 built; + built.vol = geopro::core::ScalarVolumeI16(nx, ny, nz); + for (int c = 0; c < channels; ++c) { + const auto& chData = processed.volumeData[c]; + for (int t = 0; t < traces; ++t) { + const bool hasTrace = t < chData.size(); + for (int s = 0; s < samples; ++s) { + short v = 0; + if (hasTrace && s < chData[t].size()) v = chData[t][s]; + // X=道 t、Y=通道 c、Z=样本 s。 + built.vol.at(t, c, s) = quant.toQ(static_cast(v)); + } + } + } + const double fillMs = nowMs(tFill); + + // 5) 几何:origin=0,spacing 按 X=道距 / Y=通道横距 / Z=深度采样距。 + const GPRDataModel::Header& h = processed.header; + const double dx = h.distanceInc > 1e-9 ? h.distanceInc : 1.0; + const double dy = channelSpacingY(h, channels); + const double dz = depthSpacingZ(h); + + built.quant = quant; + built.origin = {0.0, 0.0, 0.0}; + built.spacing = {dx, dy, dz}; + built.vminPhys = vmin; + built.vmaxPhys = vmax; + + if (metricsOut) { + metricsOut->nx = nx; + metricsOut->ny = ny; + metricsOut->nz = nz; + metricsOut->meanAbsBefore = meanBefore; + metricsOut->meanAbsAfter = meanAfter; + metricsOut->vminPhys = vmin; + metricsOut->vmaxPhys = vmax; + metricsOut->dx = dx; + metricsOut->dy = dy; + metricsOut->dz = dz; + metricsOut->loadMs = loadMs; + metricsOut->pipelineMs = pipelineMs; + metricsOut->fillMs = fillMs; + } + + return built; +} + +} // namespace geopro::io::gpr diff --git a/src/io/gpr/Gpr3dvVolumeBridge.hpp b/src/io/gpr/Gpr3dvVolumeBridge.hpp new file mode 100644 index 0000000..6af49c7 --- /dev/null +++ b/src/io/gpr/Gpr3dvVolumeBridge.hpp @@ -0,0 +1,48 @@ +#ifndef GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP +#define GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP + +#include + +#include "core/algo/GprVolumeBuilder.hpp" // geopro::core::BuiltI16 + +namespace geopro::io::gpr { + +// 把 vendored gpr3dv(P1) 处理链产出的【处理后立方体】桥接成 geopro 的量化体 +// (BuiltI16),供 ChunkedVolumeStore::write + buildPyramid + VTK 体绘制消费。 +// +// 链路(走 P1 原版 API,算法零改动): +// IprhParser::loadImpulseMultiChannel(dir, base, model) +// → model.buildVolumeData() // 处理前立方体 +// → RadarProcessor::runPipeline(默认链) // 原位变换 traces,不重建 volumeData +// → model.buildVolumeData() // ★必须再建:拿处理后立方体 +// 得 volumeData[通道][道][样本]。 +// +// 轴映射(geopro 体):X=道(沿测线)、Y=通道(横向)、Z=样本(深度)。 +// 即 nx=道数、ny=通道数、nz=样本数,逐 (ch,trace,sample) 填值。 +// 量化:扫处理后值域 → Quant(offset=中点,防溢出),空值=kBlank。 +// 世界 spacing: +// X=道间距 dx(header DISTANCE INTERVAL,取不到用 1.0); +// Y=通道横向间距(chYOffsets 跨度/(通道数-1),取不到用 ~1.37/(通道数-1)); +// Z=深度采样间距(timeWindow/samples × 波速 / 2,取不到用 1.0)。origin=0。 +struct BridgeMetrics { + int nx = 0, ny = 0, nz = 0; // 体维度(道 × 通道 × 样本) + double meanAbsBefore = 0.0; // 处理前 traces 平均绝对幅值 + double meanAbsAfter = 0.0; // 处理后 traces 平均绝对幅值 + double vminPhys = 0.0, vmaxPhys = 0.0; + double dx = 0.0, dy = 0.0, dz = 0.0; // 世界 spacing + double loadMs = 0.0; // 读 + 建立方体 + double pipelineMs = 0.0; // runPipeline + 再建立方体 + double fillMs = 0.0; // 扫值域 + 量化填体 +}; + +// 走 P1 链得处理后立方体 → 量化映射成 geopro BuiltI16(轴 X=道/Y=通道/Z=样本)。 +// lineDir/linePrefix 同 gpr3dv-smoke(如 "D:/Downloads/明星路", "明星路_001")。 +// metricsOut 非空时回填维度/量化/spacing/耗时(供 CLI 报告,不编造)。 +// 失败(加载失败/立方体为空) → 抛 std::runtime_error。 +geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, + const std::string& linePrefix, + BridgeMetrics* metricsOut); + +} // namespace geopro::io::gpr + +#endif // GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 506f233..71e235f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -223,4 +223,9 @@ target_sources(geopro_tests PRIVATE io/gpr/test_gpr_survey_assembler.cpp) target_sources(geopro_tests PRIVATE io/gpr/test_gps_track.cpp) target_link_libraries(geopro_tests PRIVATE geopro_io_gpr) +# Gpr3dvVolumeBridge(P2):gpr3dv 处理后立方体 → geopro 量化体(轴 X=道/Y=通道/Z=样本)。 +# 链 geopro_gpr3dv_bridge(含 vendored gpr3dv + Qt)。 +target_sources(geopro_tests PRIVATE io/gpr/test_gpr3dv_volume_bridge.cpp) +target_link_libraries(geopro_tests PRIVATE geopro_gpr3dv_bridge) + add_subdirectory(spike) # spike S3: banded contour 渲染验证 diff --git a/tests/io/gpr/test_gpr3dv_volume_bridge.cpp b/tests/io/gpr/test_gpr3dv_volume_bridge.cpp new file mode 100644 index 0000000..c1bb45c --- /dev/null +++ b/tests/io/gpr/test_gpr3dv_volume_bridge.cpp @@ -0,0 +1,130 @@ +// Gpr3dvVolumeBridge(P2):把 gpr3dv(P1) 处理后立方体桥接成 geopro 量化体。 +// 用极小合成多通道 Impulse 测线(2 通道)走真链(load→build→runPipeline→build), +// 校验轴映射(X=道/Y=通道/Z=样本)、世界 spacing、量化(offset=中点)与处理生效性。 + +#include + +#include +#include +#include +#include + +#include "core/algo/GprVolumeBuilder.hpp" +#include "core/model/ScalarVolumeI16.hpp" +#include "io/gpr/Gpr3dvVolumeBridge.hpp" + +namespace fs = std::filesystem; + +namespace { + +// 写一个合成通道:.iprh 文本头 + .iprb 纯 int16 波形([trace*samples + s],s 最快)。 +// 值 = base + t + s,确保各通道值域不同 → 全局值域非退化。 +void writeSyntheticChannel(const fs::path& iprhPath, int samples, int traces, + std::int16_t base, double chYOffset, + double distanceInterval, double timeWindowNs, + double soilVelocity, int channels) { + std::ofstream h(iprhPath); + h << "SAMPLES: " << samples << "\n"; + h << "LAST TRACE: " << (traces - 1) << "\n"; + h << "CHANNELS: " << channels << "\n"; + h << "TIMEWINDOW: " << timeWindowNs << "\n"; + h << "SOIL VELOCITY: " << soilVelocity << "\n"; + h << "DISTANCE INTERVAL: " << distanceInterval << "\n"; + h << "CH_Y_OFFSET: " << chYOffset << "\n"; + h.close(); + + fs::path iprbPath = iprhPath; + iprbPath.replace_extension(".iprb"); + std::ofstream b(iprbPath, std::ios::binary); + for (int t = 0; t < traces; ++t) { + for (int s = 0; s < samples; ++s) { + const std::int16_t v = static_cast(base + t + s); + b.write(reinterpret_cast(&v), sizeof(v)); + } + } +} + +class Gpr3dvBridgeTest : public ::testing::Test { + protected: + void SetUp() override { + dir_ = fs::temp_directory_path() / "gpr3dv_bridge_test"; + std::error_code ec; + fs::remove_all(dir_, ec); + fs::create_directories(dir_); + } + void TearDown() override { + std::error_code ec; + fs::remove_all(dir_, ec); + } + fs::path dir_; +}; + +TEST_F(Gpr3dvBridgeTest, MapsAxesQuantAndSpacing) { + const int samples = 64; // 够大,容纳默认链零时校正搜索窗口而不致退化 + const int traces = 40; + const int channels = 2; + const double dxHeader = 0.05; // DISTANCE INTERVAL + const double timeWindowNs = 100.0; // TIMEWINDOW + const double soilVel = 0.1; // SOIL VELOCITY(m/ns) + + // 2 通道:CH_Y_OFFSET 分别 -0.5 / +0.5 → 横向跨度 1.0 → dy = 1.0/(2-1) = 1.0。 + writeSyntheticChannel(dir_ / "syn_001_A01.iprh", samples, traces, + /*base=*/100, /*chYOffset=*/-0.5, dxHeader, timeWindowNs, + soilVel, channels); + writeSyntheticChannel(dir_ / "syn_001_A02.iprh", samples, traces, + /*base=*/300, /*chYOffset=*/0.5, dxHeader, timeWindowNs, + soilVel, channels); + + geopro::io::gpr::BridgeMetrics bm; + geopro::core::BuiltI16 built; + ASSERT_NO_THROW({ + built = geopro::io::gpr::buildLineVolumeFromGpr3dv(dir_.string(), "syn_001", + &bm); + }); + + // 轴映射:Y=通道(横向)恒为通道数;X=道、Z=样本均为正。 + EXPECT_EQ(built.vol.ny(), channels); + EXPECT_EQ(built.vol.ny(), bm.ny); + EXPECT_GT(built.vol.nx(), 0); // X=道 + EXPECT_GT(built.vol.nz(), 0); // Z=样本(零时校正后可能 < 原 samples) + EXPECT_EQ(built.vol.nx(), bm.nx); + EXPECT_EQ(built.vol.nz(), bm.nz); + + // 世界 spacing:X=道距(header)、Y=通道横距(CH_Y_OFFSET 跨度/(ch-1))、Z=深度采样距。 + EXPECT_DOUBLE_EQ(built.spacing[0], dxHeader); + EXPECT_NEAR(built.spacing[1], 1.0, 1e-6); + // Z=深度采样距 = (timeWindow/samplesPerTrace) × 波速 / 2(往返)。默认链零时校正会 + // 改 samplesPerTrace(故不钉死原始 samples),仅断言为正且与 metrics 自洽、量级合理。 + EXPECT_EQ(built.spacing[2], bm.dz); + EXPECT_GT(built.spacing[2], 0.0); + // 上界:原始 samples 时 dz 最小;处理后 samplesPerTrace ≤ samples → dz ≥ 该值。 + const double dzAtRawSamples = (timeWindowNs / samples) * soilVel / 2.0; + EXPECT_GE(built.spacing[2], dzAtRawSamples - 1e-9); + + // 量化:offset = 值域中点;scale 正;vmin<=vmax。 + EXPECT_LE(built.vminPhys, built.vmaxPhys); + EXPECT_GT(built.quant.scale, 0.0); + EXPECT_DOUBLE_EQ(built.quant.offset, + 0.5 * (built.vminPhys + built.vmaxPhys)); + + // 量化 round-trip:中点物理值 → 量化 → 反量化 接近 0 偏差(offset 即中点)。 + const std::int16_t qMid = built.quant.toQ(built.quant.offset); + EXPECT_NEAR(built.quant.toPhys(qMid), built.quant.offset, + built.quant.scale + 1e-9); + + // 稠密体:不应有 kBlank(GPR 立方体每体素有值)。抽查角点。 + EXPECT_NE(built.vol.at(0, 0, 0), geopro::core::ScalarVolumeI16::kBlank); + EXPECT_NE(built.vol.at(built.vol.nx() - 1, built.vol.ny() - 1, + built.vol.nz() - 1), + geopro::core::ScalarVolumeI16::kBlank); +} + +TEST_F(Gpr3dvBridgeTest, ThrowsOnMissingLine) { + // 目录无任何 _A.iprh → loadImpulseMultiChannel 失败 → 抛异常。 + geopro::io::gpr::BridgeMetrics bm; + EXPECT_THROW( + geopro::io::gpr::buildLineVolumeFromGpr3dv(dir_.string(), "nope", &bm), + std::runtime_error); +} + +} // namespace diff --git a/tools/gpr_poc/CMakeLists.txt b/tools/gpr_poc/CMakeLists.txt index 66cd998..812b949 100644 --- a/tools/gpr_poc/CMakeLists.txt +++ b/tools/gpr_poc/CMakeLists.txt @@ -16,6 +16,7 @@ target_include_directories(gpr_poc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(gpr_poc PRIVATE geopro_io_gpr + geopro_gpr3dv_bridge geopro_core geopro_store geopro_render) diff --git a/tools/gpr_poc/main.cpp b/tools/gpr_poc/main.cpp index cd775c2..b7ef319 100644 --- a/tools/gpr_poc/main.cpp +++ b/tools/gpr_poc/main.cpp @@ -33,6 +33,7 @@ #include "core/model/ColorScale.hpp" #include "core/model/ScalarVolumeI16.hpp" #include "data/store/ChunkedVolumeStore.hpp" +#include "io/gpr/Gpr3dvVolumeBridge.hpp" #include "io/gpr/GprSurveyAssembler.hpp" #include "io/gpr/IprHeader.hpp" #include "render/actors/VoxelActor.hpp" @@ -798,6 +799,109 @@ int cmdBuildGeo(int argc, char** argv) { return 0; } +// ============================================================================ +// build-line:走 gpr3dv(P1) 处理链→处理后立方体→geopro 量化体→分块+金字塔(P2) +// ============================================================================ +// +// 与 build/build-stream/build-geo 不同:本命令不走 geopro 自家解析+采样核建体, +// 而是【复用 vendored gpr3dv 的读+处理链】(算法零改动)产出处理后立方体 +// volumeData[通道][道][样本],再桥接(Gpr3dvVolumeBridge)成 geopro int16 量化体 +// (轴 X=道/Y=通道/Z=样本),落 ChunkedVolumeStore + buildPyramid。原样渲(14 格 Y +// 薄体),不做横向插值加密。 +int cmdBuildLine(int argc, char** argv) { + const Args a = parseArgs(argc, argv, 2); + if (a.positional.size() < 2) { + std::cerr << "用法: gpr_poc build-line " + "--out [--levels 3]\n" + "例: gpr_poc build-line \"D:/Downloads/明星路\" 明星路_001 " + "--out tmp/line001_proc --levels 3\n"; + return 2; + } + const std::string lineDir = a.positional[0]; + const std::string linePrefix = a.positional[1]; + const int levels = std::stoi(a.get("levels", "3")); + const std::string out = + a.get("out", (fs::temp_directory_path() / "gpr_store_line").string()); + + std::cout << "[build-line] lineDir=" << lineDir << " linePrefix=" << linePrefix + << " levels=" << levels << " out=" << out << "\n"; + + // 1) gpr3dv 处理链 → 处理后立方体 → 桥接量化体。 + Stopwatch swBridge; + geopro::io::gpr::BridgeMetrics bm; + geopro::core::BuiltI16 built = + geopro::io::gpr::buildLineVolumeFromGpr3dv(lineDir, linePrefix, &bm); + const double bridgeMs = swBridge.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 + + std::cout << "[build-line] 处理前后平均绝对幅值: " << bm.meanAbsBefore << " → " + << bm.meanAbsAfter << " (处理" + << (std::abs(bm.meanAbsAfter - bm.meanAbsBefore) > 1e-9 ? "已生效" + : "未变化") + << ")\n"; + std::cout << "[build-line] 体维度(道×通道×样本) = " << nx << " x " << ny + << " x " << nz << "\n"; + std::cout << "[build-line] 世界 spacing dx=" << bm.dx << " dy=" << bm.dy + << " dz=" << bm.dz << " (m)\n"; + + // 2) 落盘 + 金字塔(道很长 45305>16384 → 需 levels≥2 使最粗层≤16384)。 + 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(rawBytes) / dataBytes : 0.0; + const double peak = Probe::peakMemMB(); + + std::cout << "\n=== build-line 指标(gpr3dv 处理后真三维体)===\n"; + std::cout << "桥接耗时(ms) : " << bridgeMs << " (含 读 " << bm.loadMs + << " + 处理 " << bm.pipelineMs << " + 量化填体 " << bm.fillMs + << ")\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 << "处理后值域 : [" << bm.vminPhys << ", " << bm.vmaxPhys + << "] 量化 scale=" << built.quant.scale + << " offset=" << built.quant.offset << "\n"; + std::cout << "世界 spacing : dx=" << bm.dx << " dy=" << bm.dy + << " dz=" << bm.dz << " (m)\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,prefix=" + linePrefix + ",nx=" + std::to_string(nx) + + ",ny=" + std::to_string(ny) + ",nz=" + std::to_string(nz) + + ",voxels=" + std::to_string(voxels) + + ",vmin=" + std::to_string(bm.vminPhys) + + ",vmax=" + std::to_string(bm.vmaxPhys) + + ",dx=" + std::to_string(bm.dx) + ",dy=" + std::to_string(bm.dy) + + ",dz=" + std::to_string(bm.dz) + ",rawB=" + std::to_string(rawBytes) + + ",dataB=" + std::to_string(dataBytes) + + ",ratio=" + std::to_string(ratio) + + ",bridgeMs=" + std::to_string(bridgeMs) + + ",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()) { @@ -4063,6 +4167,8 @@ void usage() { "[--maxLines N]\n" " gpr_poc build-geo [--cellXY 0.5] [--cellZ 0.1] " "[--out ] [--levels 4] [--maxLines N] [--curvilinear]\n" + " gpr_poc build-line --out " + "[--levels 3]\n" " gpr_poc load \n" " gpr_poc selftest\n" " gpr_poc offscreen-smoke\n" @@ -4097,6 +4203,7 @@ int main(int argc, char** argv) { if (cmd == "build") return cmdBuild(argc, argv); if (cmd == "build-stream") return cmdBuildStream(argc, argv); if (cmd == "build-geo") return cmdBuildGeo(argc, argv); + if (cmd == "build-line") return cmdBuildLine(argc, argv); if (cmd == "load") return cmdLoad(argc, argv); if (cmd == "selftest") return cmdSelftest(); if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();