refactor(gpr): 抽出共享 assembleRadarVolume,Impulse 路改调(消填体 DRY)
This commit is contained in:
parent
18b78a85d3
commit
abd3027610
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include "io/gpr/Gpr3dvVolumeBridge.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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<double> latOff;
|
||||
const auto& chx = processed.header.chXOffsets;
|
||||
if (chx.size() == channels)
|
||||
for (int c = 0; c < channels; ++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();
|
||||
short rawMin = std::numeric_limits<short>::max();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 渲染验证
|
||||
|
|
|
|||
|
|
@ -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=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
|
||||
}
|
||||
Loading…
Reference in New Issue