166 lines
7.1 KiB
C++
166 lines
7.1 KiB
C++
// Gpr3dvVolumeBridge(P2):把 gpr3dv(P1) 处理后立方体桥接成 geopro 量化体。
|
||
// 用极小合成多通道 Impulse 测线(2 通道)走真链(load→build→runPipeline→build),
|
||
// 校验轴映射(X=道/Y=通道/Z=样本)、世界 spacing、量化(offset=中点)与处理生效性。
|
||
|
||
#include <gtest/gtest.h>
|
||
|
||
#include <cstdint>
|
||
#include <filesystem>
|
||
#include <fstream>
|
||
#include <string>
|
||
|
||
#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,
|
||
double chXOffset = 1e30) { // 1e30=不写 CH_X_OFFSET(不触发通道插值)
|
||
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";
|
||
if (chXOffset < 1e29) h << "CH_X_OFFSET: " << chXOffset << "\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<std::int16_t>(base + t + s);
|
||
b.write(reinterpret_cast<const char*>(&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, ChannelInterpDensifiesYThroughBridge) {
|
||
// §1 端到端:3 通道带真实横向偏移 CH_X_OFFSET=-0.5/0/+0.5(跨度 1.0)。
|
||
// targetDy=0.25 → ny=round(1.0/0.25)+1=5(从 3 通道插值加密到 5 平面),dy=0.25。
|
||
const int samples = 64, traces = 40, channels = 3;
|
||
writeSyntheticChannel(dir_ / "syn_001_A01.iprh", samples, traces, 100, -1.5,
|
||
0.05, 100.0, 0.1, channels, /*chXOffset=*/-0.5);
|
||
writeSyntheticChannel(dir_ / "syn_001_A02.iprh", samples, traces, 200, -1.5,
|
||
0.05, 100.0, 0.1, channels, /*chXOffset=*/0.0);
|
||
writeSyntheticChannel(dir_ / "syn_001_A03.iprh", samples, traces, 300, -1.5,
|
||
0.05, 100.0, 0.1, channels, /*chXOffset=*/0.5);
|
||
|
||
geopro::io::gpr::BridgeMetrics bm;
|
||
geopro::core::BuiltI16 built;
|
||
ASSERT_NO_THROW({
|
||
built = geopro::io::gpr::buildLineVolumeFromGpr3dv(
|
||
dir_.string(), "syn_001", &bm, /*coarse=*/1, /*targetDy=*/0.25);
|
||
});
|
||
|
||
EXPECT_EQ(built.vol.ny(), 5); // 3 通道 → 5 网格平面
|
||
EXPECT_EQ(built.vol.ny(), bm.ny);
|
||
EXPECT_NEAR(built.spacing[1], 0.25, 1e-9); // dy = targetDy
|
||
// 稠密(无 kBlank);插值值落在原通道值域内(线性混合不外溢)。
|
||
EXPECT_NE(built.vol.at(0, 2, 0), geopro::core::ScalarVolumeI16::kBlank);
|
||
|
||
// 关闭插值(targetDy=0)→ ny 回到原通道数 3。
|
||
geopro::core::BuiltI16 raw;
|
||
ASSERT_NO_THROW({
|
||
raw = geopro::io::gpr::buildLineVolumeFromGpr3dv(
|
||
dir_.string(), "syn_001", nullptr, /*coarse=*/1, /*targetDy=*/0.0);
|
||
});
|
||
EXPECT_EQ(raw.vol.ny(), channels);
|
||
}
|
||
|
||
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
|