refactor(gpr): 抽出共享 assembleRadarVolume,Impulse 路改调(消填体 DRY)

This commit is contained in:
gaozheng 2026-06-29 12:14:25 +08:00
parent 18b78a85d3
commit abd3027610
6 changed files with 159 additions and 91 deletions

View File

@ -16,6 +16,7 @@ set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC O
# GpsTrack(geopro_io_gpr) .gps # GpsTrack(geopro_io_gpr) .gps
add_library(geopro_gpr3dv_bridge STATIC add_library(geopro_gpr3dv_bridge STATIC
Gpr3dvVolumeBridge.cpp Gpr3dvVolumeBridge.cpp
RadarVolumeAssembler.cpp
Gpr3dvSurveyVolumeBridge.cpp) Gpr3dvSurveyVolumeBridge.cpp)
target_include_directories(geopro_gpr3dv_bridge PUBLIC ${CMAKE_SOURCE_DIR}/src) target_include_directories(geopro_gpr3dv_bridge PUBLIC ${CMAKE_SOURCE_DIR}/src)
target_compile_features(geopro_gpr3dv_bridge PUBLIC cxx_std_17) target_compile_features(geopro_gpr3dv_bridge PUBLIC cxx_std_17)

View File

@ -1,8 +1,8 @@
#include "io/gpr/Gpr3dvVolumeBridge.hpp" #include "io/gpr/Gpr3dvVolumeBridge.hpp"
#include <algorithm>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
#include <limits>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
@ -13,7 +13,7 @@
#include "RadarProcessor.h" #include "RadarProcessor.h"
#include "core/model/ScalarVolumeI16.hpp" #include "core/model/ScalarVolumeI16.hpp"
#include "io/gpr/GprGeometry.hpp" // planChannelInterpolation #include "io/gpr/RadarVolumeAssembler.hpp" // assembleRadarVolume(共享建体)
namespace geopro::io::gpr { namespace geopro::io::gpr {
@ -71,7 +71,6 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir,
const std::string& linePrefix, const std::string& linePrefix,
BridgeMetrics* metricsOut, BridgeMetrics* metricsOut,
int coarse, double targetDy) { int coarse, double targetDy) {
const int stride = coarse > 1 ? coarse : 1; // 沿测线下采样步长(≥1)
const QString dir = QString::fromLocal8Bit(lineDir.c_str()); const QString dir = QString::fromLocal8Bit(lineDir.c_str());
const QString base = QString::fromLocal8Bit(linePrefix.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(traces) + "/" +
std::to_string(samples) + ")"); std::to_string(samples) + ")");
} }
// 下采样后输出道数(向上取整保留末道附近)nxOut = ceil(traces/stride)。 // §1 线内通道插值偏移:读各通道真实横向偏移(header.chXOffsets)。绝不跨线;间距/
const int nxOut = (traces + stride - 1) / stride; // 通道数从数据来,不假设。空/不等长 → 不启用通道插值(helper 内退路=逐通道 identity)。
const int nx = nxOut; // X=道(沿测线,已按 stride 下采样)
const int nz = samples; // Z=样本(深度)
// §1 线内通道插值:读各通道真实横向偏移(header.chXOffsets) → 规则网格化 Y 到 targetDy。
// 绝不跨线;间距/通道数从数据来,不假设。退路(无偏移/未启用)= 逐通道 identity。
std::vector<double> latOff; std::vector<double> latOff;
const auto& chx = processed.header.chXOffsets; const auto& chx = processed.header.chXOffsets;
if (chx.size() == channels) if (chx.size() == channels)
for (int c = 0; c < channels; ++c) for (int c = 0; c < channels; ++c)
latOff.push_back(static_cast<double>(chx[c])); latOff.push_back(static_cast<double>(chx[c]));
std::vector<geopro::io::gpr::ChannelInterpRow> rows;
bool interpolated = false;
if (static_cast<int>(latOff.size()) == channels && targetDy > 0.0) {
rows = planChannelInterpolation(latOff, targetDy);
interpolated = (static_cast<int>(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<int>(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<int>(ch.size())) return 0.0;
const auto& tr = ch[t];
return s < static_cast<int>(tr.size()) ? static_cast<double>(tr[s]) : 0.0;
};
const auto tFill = std::chrono::steady_clock::now(); const auto tFill = std::chrono::steady_clock::now();
short rawMin = std::numeric_limits<short>::max(); geopro::core::BuiltI16 built = assembleRadarVolume(desc, sample, coarse, targetDy);
short rawMax = std::numeric_limits<short>::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<double>(rawMin);
const double vmax = static_cast<double>(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<int>(chA.size());
const bool hasB = t < static_cast<int>(chB.size());
for (int s = 0; s < samples; ++s) {
const double va =
(hasA && s < static_cast<int>(chA[t].size())) ? chA[t][s] : 0.0;
const double vb =
(hasB && s < static_cast<int>(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);
}
}
}
const double fillMs = nowMs(tFill); const double fillMs = nowMs(tFill);
// 5) 几何origin=0spacing 按 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) { if (metricsOut) {
metricsOut->nx = nx; metricsOut->nx = built.vol.nx();
metricsOut->ny = ny; metricsOut->ny = built.vol.ny();
metricsOut->nz = nz; metricsOut->nz = built.vol.nz();
metricsOut->meanAbsBefore = meanBefore; metricsOut->meanAbsBefore = meanBefore;
metricsOut->meanAbsAfter = meanAfter; metricsOut->meanAbsAfter = meanAfter;
metricsOut->vminPhys = vmin; metricsOut->vminPhys = built.vminPhys;
metricsOut->vmaxPhys = vmax; metricsOut->vmaxPhys = built.vmaxPhys;
metricsOut->dx = dx; metricsOut->dx = built.spacing[0];
metricsOut->dy = dy; metricsOut->dy = built.spacing[1];
metricsOut->dz = dz; metricsOut->dz = built.spacing[2];
metricsOut->loadMs = loadMs; metricsOut->loadMs = loadMs;
metricsOut->pipelineMs = pipelineMs; metricsOut->pipelineMs = pipelineMs;
metricsOut->fillMs = fillMs; metricsOut->fillMs = fillMs;

View File

@ -0,0 +1,68 @@
#include "io/gpr/RadarVolumeAssembler.hpp"
#include <cmath>
#include <limits>
#include <stdexcept>
#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<ChannelInterpRow> rows;
bool interpolated = false;
if (static_cast<int>(d.chXOffsets.size()) == d.channels && targetDy > 0.0) {
rows = planChannelInterpolation(d.chXOffsets, targetDy);
interpolated = (static_cast<int>(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<int>(rows.size());
// 扫值域 → Quant(中点 offset, 64000 裕度)。
double vmin = std::numeric_limits<double>::infinity();
double vmax = -std::numeric_limits<double>::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

View File

@ -0,0 +1,17 @@
#ifndef GEOPRO_IO_GPR_RADARVOLUMEASSEMBLER_HPP
#define GEOPRO_IO_GPR_RADARVOLUMEASSEMBLER_HPP
#include <functional>
#include <vector>
#include "core/algo/GprVolumeBuilder.hpp"
namespace geopro::io::gpr {
struct RadarCubeDesc {
int channels = 0; int traces = 0; int samples = 0;
std::vector<double> chXOffsets;
double dxBase = 1.0; double dyWhenNotInterpolated = 1.0; double dz = 1.0;
};
using CubeSampler = std::function<double(int c, int t, int s)>;
geopro::core::BuiltI16 assembleRadarVolume(const RadarCubeDesc& desc,
const CubeSampler& sample,
int coarse, double targetDy);
} // namespace geopro::io::gpr
#endif

View File

@ -231,6 +231,8 @@ target_link_libraries(geopro_tests PRIVATE geopro_io_gpr)
# Gpr3dvVolumeBridge(P2)gpr3dv geopro 量化体( X=道/Y=通道/Z=样本) # Gpr3dvVolumeBridge(P2)gpr3dv geopro 量化体( X=道/Y=通道/Z=样本)
# geopro_gpr3dv_bridge( vendored gpr3dv + Qt) # geopro_gpr3dv_bridge( vendored gpr3dv + Qt)
target_sources(geopro_tests PRIVATE io/gpr/test_gpr3dv_volume_bridge.cpp) 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) target_link_libraries(geopro_tests PRIVATE geopro_gpr3dv_bridge)
add_subdirectory(spike) # spike S3: banded contour add_subdirectory(spike) # spike S3: banded contour

View File

@ -0,0 +1,39 @@
#include <gtest/gtest.h>
#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=24 道 → nxOut=2dx×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
}