// GprVolumeRepository:逐线 GPR int16 量化体 → app 渲染链 float 体(VolumeGrid)。 // 1) builtI16ToVolumeGrid 纯适配器:维度/反量化值/spacing/origin/vmin-vmax/kBlank→NaN。 // 2) createGprVolumeGrid 全链:合成多通道 .iprb 走真 P1/P2 链 → 反量化体维度/spacing 自洽。 #include #include #include #include #include #include #include "core/algo/GprVolumeBuilder.hpp" #include "core/model/ScalarVolumeI16.hpp" #include "data/GprVolumeRepository.hpp" namespace fs = std::filesystem; namespace { // 适配器单测:手搭一个 2x1x2 的 BuiltI16(已知 quant/spacing/origin) → 校验反量化逐值。 TEST(GprVolumeRepositoryAdapter, DequantDimsSpacingAndBlank) { geopro::core::BuiltI16 built; built.vol = geopro::core::ScalarVolumeI16(2, 1, 2); // Quant: phys = q*scale + offset = q*0.5 + 10。 built.quant.scale = 0.5; built.quant.offset = 10.0; built.origin = {1.0, 2.0, 3.0}; built.spacing = {0.2, 1.37, 0.05}; built.vminPhys = 5.0; built.vmaxPhys = 20.0; // 填 4 个体素:3 个正常值 + 1 个 kBlank。 built.vol.at(0, 0, 0) = 4; // phys = 4*0.5+10 = 12 built.vol.at(1, 0, 0) = -2; // phys = -2*0.5+10 = 9 built.vol.at(0, 0, 1) = 20; // phys = 20*0.5+10 = 20 built.vol.at(1, 0, 1) = geopro::core::ScalarVolumeI16::kBlank; // → NaN const geopro::data::VolumeGrid g = geopro::data::builtI16ToVolumeGrid(built); // 维度。 EXPECT_EQ(g.vol.nx(), 2); EXPECT_EQ(g.vol.ny(), 1); EXPECT_EQ(g.vol.nz(), 2); // 反量化逐值。 EXPECT_DOUBLE_EQ(g.vol.at(0, 0, 0), 12.0); EXPECT_DOUBLE_EQ(g.vol.at(1, 0, 0), 9.0); EXPECT_DOUBLE_EQ(g.vol.at(0, 0, 1), 20.0); EXPECT_TRUE(std::isnan(g.vol.at(1, 0, 1))); // kBlank → NaN(下游透明) // origin/spacing 原样搬运。 EXPECT_DOUBLE_EQ(g.origin[0], 1.0); EXPECT_DOUBLE_EQ(g.origin[1], 2.0); EXPECT_DOUBLE_EQ(g.origin[2], 3.0); EXPECT_DOUBLE_EQ(g.spacing[0], 0.2); EXPECT_DOUBLE_EQ(g.spacing[1], 1.37); EXPECT_DOUBLE_EQ(g.spacing[2], 0.05); // 物理值域 = BuiltI16 的 vminPhys/vmaxPhys。 EXPECT_DOUBLE_EQ(g.vmin, 5.0); EXPECT_DOUBLE_EQ(g.vmax, 20.0); EXPECT_TRUE(g.valid()); } // 写一个合成通道:.iprh 文本头 + .iprb 纯 int16 波形([trace*samples + s],s 最快)。 // 与 test_gpr3dv_volume_bridge 同口径,确保 createGprVolumeGrid 走真 P1/P2 链。 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 GprVolumeRepositoryChainTest : public ::testing::Test { protected: void SetUp() override { dir_ = fs::temp_directory_path() / "gpr_volume_repo_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(GprVolumeRepositoryChainTest, FullChainProducesValidVolumeGrid) { const int samples = 64; const int traces = 40; const int channels = 2; const double dxHeader = 0.05; const double timeWindowNs = 100.0; const double soilVel = 0.1; 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); // coarse=2:沿测线下采样,dx ×2 保形。 geopro::data::VolumeGrid g; ASSERT_NO_THROW( { g = geopro::data::createGprVolumeGrid(dir_.string(), "syn_001", 2); }); // 维度:Y=通道数;X/Z 正。 EXPECT_EQ(g.vol.ny(), channels); EXPECT_GT(g.vol.nx(), 0); EXPECT_GT(g.vol.nz(), 0); // spacing:X=道距×coarse、Y=通道横距(跨度1.0/(2-1)=1.0)、Z=深度采样距>0。 EXPECT_DOUBLE_EQ(g.spacing[0], dxHeader * 2); EXPECT_NEAR(g.spacing[1], 1.0, 1e-6); EXPECT_GT(g.spacing[2], 0.0); // origin=0;值域自洽(vmin<=vmax,搬自 BuiltI16.vminPhys/vmaxPhys,处理后极小合成 // 数据可能退化为相等区间 → 与 io::gpr 桥接同口径,不强求严格 <)。 EXPECT_DOUBLE_EQ(g.origin[0], 0.0); EXPECT_DOUBLE_EQ(g.origin[1], 0.0); EXPECT_DOUBLE_EQ(g.origin[2], 0.0); EXPECT_LE(g.vmin, g.vmax); // 稠密体:抽查角点为有限值(非 NaN)——GPR 立方体每体素有值,反量化后无空洞。 EXPECT_TRUE(std::isfinite(g.vol.at(0, 0, 0))); EXPECT_TRUE(std::isfinite(g.vol.at(g.vol.nx() - 1, g.vol.ny() - 1, g.vol.nz() - 1))); } TEST_F(GprVolumeRepositoryChainTest, ThrowsOnMissingLine) { EXPECT_THROW(geopro::data::createGprVolumeGrid(dir_.string(), "nope", 1), std::runtime_error); } } // namespace