geopro/tests/io/gpr/test_gpr3dv_volume_bridge.cpp

166 lines
7.1 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.

// 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);
// 世界 spacingX=道距(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