// 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