geopro/tests/data/test_gpr_volume_repository.cpp

157 lines
5.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// GprVolumeRepository逐线 GPR int16 量化体 → app 渲染链 float 体(VolumeGrid)。
// 1) builtI16ToVolumeGrid 纯适配器:维度/反量化值/spacing/origin/vmin-vmax/kBlank→NaN。
// 2) createGprVolumeGrid 全链:合成多通道 .iprb 走真 P1/P2 链 → 反量化体维度/spacing 自洽。
#include <gtest/gtest.h>
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#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<std::int16_t>(base + t + s);
b.write(reinterpret_cast<const char*>(&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);
// spacingX=道距×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