From abd3027610c2cda18d45bf5501f3ae7827a721dc Mon Sep 17 00:00:00 2001 From: gaozheng Date: Mon, 29 Jun 2026 12:14:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor(gpr):=20=E6=8A=BD=E5=87=BA=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=20assembleRadarVolume=EF=BC=8CImpulse=20=E8=B7=AF?= =?UTF-8?q?=E6=94=B9=E8=B0=83(=E6=B6=88=E5=A1=AB=E4=BD=93=20DRY)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/gpr/CMakeLists.txt | 1 + src/io/gpr/Gpr3dvVolumeBridge.cpp | 123 +++++-------------- src/io/gpr/RadarVolumeAssembler.cpp | 68 ++++++++++ src/io/gpr/RadarVolumeAssembler.hpp | 17 +++ tests/CMakeLists.txt | 2 + tests/io/gpr/test_radar_volume_assembler.cpp | 39 ++++++ 6 files changed, 159 insertions(+), 91 deletions(-) create mode 100644 src/io/gpr/RadarVolumeAssembler.cpp create mode 100644 src/io/gpr/RadarVolumeAssembler.hpp create mode 100644 tests/io/gpr/test_radar_volume_assembler.cpp diff --git a/src/io/gpr/CMakeLists.txt b/src/io/gpr/CMakeLists.txt index 4afd503..75bed4e 100644 --- a/src/io/gpr/CMakeLists.txt +++ b/src/io/gpr/CMakeLists.txt @@ -16,6 +16,7 @@ set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC O # 额外用 GpsTrack(geopro_io_gpr) 解析 .gps。 add_library(geopro_gpr3dv_bridge STATIC Gpr3dvVolumeBridge.cpp + RadarVolumeAssembler.cpp Gpr3dvSurveyVolumeBridge.cpp) target_include_directories(geopro_gpr3dv_bridge PUBLIC ${CMAKE_SOURCE_DIR}/src) target_compile_features(geopro_gpr3dv_bridge PUBLIC cxx_std_17) diff --git a/src/io/gpr/Gpr3dvVolumeBridge.cpp b/src/io/gpr/Gpr3dvVolumeBridge.cpp index e3b7bcf..a6dceaf 100644 --- a/src/io/gpr/Gpr3dvVolumeBridge.cpp +++ b/src/io/gpr/Gpr3dvVolumeBridge.cpp @@ -1,8 +1,8 @@ #include "io/gpr/Gpr3dvVolumeBridge.hpp" +#include #include #include -#include #include #include @@ -13,7 +13,7 @@ #include "RadarProcessor.h" #include "core/model/ScalarVolumeI16.hpp" -#include "io/gpr/GprGeometry.hpp" // planChannelInterpolation +#include "io/gpr/RadarVolumeAssembler.hpp" // assembleRadarVolume(共享建体) namespace geopro::io::gpr { @@ -71,7 +71,6 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, const std::string& linePrefix, BridgeMetrics* metricsOut, int coarse, double targetDy) { - const int stride = coarse > 1 ? coarse : 1; // 沿测线下采样步长(≥1) const QString dir = QString::fromLocal8Bit(lineDir.c_str()); const QString base = QString::fromLocal8Bit(linePrefix.c_str()); @@ -107,106 +106,48 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, std::to_string(traces) + "/" + std::to_string(samples) + ")"); } - // 下采样后输出道数(向上取整保留末道附近):nxOut = ceil(traces/stride)。 - const int nxOut = (traces + stride - 1) / stride; - const int nx = nxOut; // X=道(沿测线,已按 stride 下采样) - const int nz = samples; // Z=样本(深度) - - // §1 线内通道插值:读各通道真实横向偏移(header.chXOffsets) → 规则网格化 Y 到 targetDy。 - // 绝不跨线;间距/通道数从数据来,不假设。退路(无偏移/未启用)= 逐通道 identity。 + // §1 线内通道插值偏移:读各通道真实横向偏移(header.chXOffsets)。绝不跨线;间距/ + // 通道数从数据来,不假设。空/不等长 → 不启用通道插值(helper 内退路=逐通道 identity)。 std::vector latOff; const auto& chx = processed.header.chXOffsets; if (chx.size() == channels) for (int c = 0; c < channels; ++c) latOff.push_back(static_cast(chx[c])); - std::vector rows; - bool interpolated = false; - if (static_cast(latOff.size()) == channels && targetDy > 0.0) { - rows = planChannelInterpolation(latOff, targetDy); - interpolated = (static_cast(rows.size()) != channels); - } - if (rows.empty()) - for (int c = 0; c < channels; ++c) rows.push_back({c, c, 0.0}); - const int ny = static_cast(rows.size()); // Y=通道(横向,可能已插值加密) - // 3) 扫处理后值域 → Quant(offset=中点,防溢出)。 + // 3) 组立方体描述 + 采样器(volumeData[通道][道][样本],越界取 0),调共享建体 helper。 + // 扫值域→Quant(中点 offset)→通道插值(规则化 Y 到 targetDy)→逐体素填→spacing 均在 + // helper 内完成(与后续规范化 .head/.data 路共用同一逻辑,零漂移)。 + const GPRDataModel::Header& h = processed.header; + RadarCubeDesc desc; + desc.channels = channels; + desc.traces = traces; + desc.samples = samples; + desc.chXOffsets = latOff; + desc.dxBase = h.distanceInc > 1e-9 ? h.distanceInc : 1.0; // 道距(米) + desc.dyWhenNotInterpolated = channelSpacingY(h, channels); // 原通道横距(米) + desc.dz = depthSpacingZ(h); // 深度采样距(米) + CubeSampler sample = [&processed](int c, int t, int s) -> double { + const auto& ch = processed.volumeData[c]; + if (t >= static_cast(ch.size())) return 0.0; + const auto& tr = ch[t]; + return s < static_cast(tr.size()) ? static_cast(tr[s]) : 0.0; + }; + 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) 逐 (输出行 j, trace, sample) 填体。每个输出行 = 两侧最近真实通道线性插值 - // (a==b 时即原通道)。GPR 立方体稠密(每体素有值),无空洞 → 不置 kBlank。 - // 沿测线按 stride 下采样:输出道 to → 源道 t = to*stride。 - geopro::core::BuiltI16 built; - built.vol = geopro::core::ScalarVolumeI16(nx, ny, nz); - for (int j = 0; j < ny; ++j) { - const auto& chA = processed.volumeData[rows[j].a]; - const auto& chB = processed.volumeData[rows[j].b]; - const double wb = rows[j].wb, wa = 1.0 - wb; - for (int to = 0; to < nxOut; ++to) { - const int t = to * stride; - const bool hasA = t < static_cast(chA.size()); - const bool hasB = t < static_cast(chB.size()); - for (int s = 0; s < samples; ++s) { - const double va = - (hasA && s < static_cast(chA[t].size())) ? chA[t][s] : 0.0; - const double vb = - (hasB && s < static_cast(chB[t].size())) ? chB[t][s] : 0.0; - // X=输出道 to、Y=输出行 j、Z=样本 s。 - built.vol.at(to, j, s) = quant.toQ(wa * va + wb * vb); - } - } - } + geopro::core::BuiltI16 built = assembleRadarVolume(desc, sample, coarse, targetDy); const double fillMs = nowMs(tFill); - // 5) 几何:origin=0,spacing 按 X=道距 / Y=通道横距 / Z=深度采样距。 - const GPRDataModel::Header& h = processed.header; - // 下采样后相邻输出道在世界中跨 stride 个原始道距 → dx ×stride 保持真实尺度。 - const double dxBase = h.distanceInc > 1e-9 ? h.distanceInc : 1.0; - const double dx = dxBase * stride; - // 插值后 Y 已规则化到 targetDy 网格;否则用原通道横距。 - const double dy = interpolated ? targetDy : 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->nx = built.vol.nx(); + metricsOut->ny = built.vol.ny(); + metricsOut->nz = built.vol.nz(); metricsOut->meanAbsBefore = meanBefore; metricsOut->meanAbsAfter = meanAfter; - metricsOut->vminPhys = vmin; - metricsOut->vmaxPhys = vmax; - metricsOut->dx = dx; - metricsOut->dy = dy; - metricsOut->dz = dz; + metricsOut->vminPhys = built.vminPhys; + metricsOut->vmaxPhys = built.vmaxPhys; + metricsOut->dx = built.spacing[0]; + metricsOut->dy = built.spacing[1]; + metricsOut->dz = built.spacing[2]; metricsOut->loadMs = loadMs; metricsOut->pipelineMs = pipelineMs; metricsOut->fillMs = fillMs; diff --git a/src/io/gpr/RadarVolumeAssembler.cpp b/src/io/gpr/RadarVolumeAssembler.cpp new file mode 100644 index 0000000..9ccc1c6 --- /dev/null +++ b/src/io/gpr/RadarVolumeAssembler.cpp @@ -0,0 +1,68 @@ +#include "io/gpr/RadarVolumeAssembler.hpp" +#include +#include +#include +#include "core/model/ScalarVolumeI16.hpp" +#include "io/gpr/GprGeometry.hpp" // planChannelInterpolation, ChannelInterpRow +namespace geopro::io::gpr { + +geopro::core::BuiltI16 assembleRadarVolume(const RadarCubeDesc& d, + const CubeSampler& sample, + int coarse, double targetDy) { + if (d.channels <= 0 || d.traces <= 0 || d.samples <= 0) + throw std::runtime_error("assembleRadarVolume: 维度为空"); + const int stride = coarse > 1 ? coarse : 1; + const int nxOut = (d.traces + stride - 1) / stride; + const int nz = d.samples; + + // 通道插值方案(读 chXOffsets 规则化到 targetDy);退路=逐通道 identity。 + std::vector rows; + bool interpolated = false; + if (static_cast(d.chXOffsets.size()) == d.channels && targetDy > 0.0) { + rows = planChannelInterpolation(d.chXOffsets, targetDy); + interpolated = (static_cast(rows.size()) != d.channels); + } + if (rows.empty()) + for (int c = 0; c < d.channels; ++c) rows.push_back({c, c, 0.0}); + const int ny = static_cast(rows.size()); + + // 扫值域 → Quant(中点 offset, 64000 裕度)。 + double vmin = std::numeric_limits::infinity(); + double vmax = -std::numeric_limits::infinity(); + for (int c = 0; c < d.channels; ++c) + for (int t = 0; t < d.traces; ++t) + for (int s = 0; s < d.samples; ++s) { + const double v = sample(c, t, s); + if (v < vmin) vmin = v; + if (v > vmax) vmax = v; + } + if (!(vmin <= vmax)) { vmin = 0.0; vmax = 0.0; } + geopro::core::Quant quant; + quant.scale = (vmax > vmin) ? (vmax - vmin) / 64000.0 : 1.0; + quant.offset = 0.5 * (vmin + vmax); + + // 逐(输出行 j, 输出道 to, 采样 s)填,散射写入(绝不 memcpy)。 + geopro::core::BuiltI16 built; + built.vol = geopro::core::ScalarVolumeI16(nxOut, ny, nz); + for (int j = 0; j < ny; ++j) { + const int a = rows[j].a, b = rows[j].b; + const double wb = rows[j].wb, wa = 1.0 - wb; + for (int to = 0; to < nxOut; ++to) { + const int t = to * stride; + for (int s = 0; s < nz; ++s) { + const double va = sample(a, t, s); + const double vb = (b == a) ? va : sample(b, t, s); + built.vol.at(to, j, s) = quant.toQ(wa * va + wb * vb); + } + } + } + + const double dy = interpolated ? targetDy : d.dyWhenNotInterpolated; + built.quant = quant; + built.origin = {0.0, 0.0, 0.0}; + built.spacing = {d.dxBase * stride, dy, d.dz}; + built.vminPhys = vmin; + built.vmaxPhys = vmax; + return built; +} +} // namespace geopro::io::gpr diff --git a/src/io/gpr/RadarVolumeAssembler.hpp b/src/io/gpr/RadarVolumeAssembler.hpp new file mode 100644 index 0000000..676ebb3 --- /dev/null +++ b/src/io/gpr/RadarVolumeAssembler.hpp @@ -0,0 +1,17 @@ +#ifndef GEOPRO_IO_GPR_RADARVOLUMEASSEMBLER_HPP +#define GEOPRO_IO_GPR_RADARVOLUMEASSEMBLER_HPP +#include +#include +#include "core/algo/GprVolumeBuilder.hpp" +namespace geopro::io::gpr { +struct RadarCubeDesc { + int channels = 0; int traces = 0; int samples = 0; + std::vector chXOffsets; + double dxBase = 1.0; double dyWhenNotInterpolated = 1.0; double dz = 1.0; +}; +using CubeSampler = std::function; +geopro::core::BuiltI16 assembleRadarVolume(const RadarCubeDesc& desc, + const CubeSampler& sample, + int coarse, double targetDy); +} // namespace geopro::io::gpr +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5f4da8..8aaa004 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -231,6 +231,8 @@ 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) +# RadarVolumeAssembler:格式无关共享建体(扫值域→Quant→通道插值→逐体素填→spacing)。 +target_sources(geopro_tests PRIVATE io/gpr/test_radar_volume_assembler.cpp) target_link_libraries(geopro_tests PRIVATE geopro_gpr3dv_bridge) add_subdirectory(spike) # spike S3: banded contour 渲染验证 diff --git a/tests/io/gpr/test_radar_volume_assembler.cpp b/tests/io/gpr/test_radar_volume_assembler.cpp new file mode 100644 index 0000000..312cc1d --- /dev/null +++ b/tests/io/gpr/test_radar_volume_assembler.cpp @@ -0,0 +1,39 @@ +#include +#include "core/algo/GprVolumeBuilder.hpp" +#include "io/gpr/RadarVolumeAssembler.hpp" + +using geopro::io::gpr::RadarCubeDesc; +using geopro::io::gpr::assembleRadarVolume; + +// 2 道 × 3 通道 × 4 采样,值 = 100*c + 10*t + s。不插值(chXOffsets 空)、coarse=1。 +TEST(RadarVolumeAssembler, AxisMapAndQuantRoundTrip) { + RadarCubeDesc d; + d.channels = 3; d.traces = 2; d.samples = 4; + d.dxBase = 0.1; d.dyWhenNotInterpolated = 0.5; d.dz = 0.05; + auto sampler = [](int c, int t, int s) { return 100.0 * c + 10.0 * t + s; }; + + const geopro::core::BuiltI16 b = assembleRadarVolume(d, sampler, /*coarse=*/1, /*targetDy=*/0.0); + + EXPECT_EQ(b.vol.nx(), 2); // 道 + EXPECT_EQ(b.vol.ny(), 3); // 通道(未插值=原通道数) + EXPECT_EQ(b.vol.nz(), 4); // 采样 + EXPECT_DOUBLE_EQ(b.spacing[0], 0.1); + EXPECT_DOUBLE_EQ(b.spacing[1], 0.5); + EXPECT_DOUBLE_EQ(b.spacing[2], 0.05); + EXPECT_NEAR(b.vminPhys, 0.0, 1e-9); // c0,t0,s0 + EXPECT_NEAR(b.vmaxPhys, 213.0, 1e-9); // c2,t1,s3 = 200+10+3 + // 反量化对位:体素(道 t=1, 通道 c=2, 采样 s=3) 应≈213(量化误差内)。 + const double recon = b.quant.toPhys(b.vol.at(1, 2, 3)); + EXPECT_NEAR(recon, 213.0, b.quant.scale); +} + +// coarse=2:4 道 → nxOut=2,dx×2。 +TEST(RadarVolumeAssembler, CoarseDownsamplesTracesAndScalesDx) { + RadarCubeDesc d; + d.channels = 1; d.traces = 4; d.samples = 2; d.dxBase = 0.1; + auto sampler = [](int, int t, int s) { return 10.0 * t + s; }; + const geopro::core::BuiltI16 b = assembleRadarVolume(d, sampler, /*coarse=*/2, 0.0); + EXPECT_EQ(b.vol.nx(), 2); + EXPECT_DOUBLE_EQ(b.spacing[0], 0.2); + EXPECT_NEAR(b.quant.toPhys(b.vol.at(1, 0, 0)), 20.0, b.quant.scale); // 输出道1 = 源道2 +}