feat(data): 逐线 GPR 体反量化适配器 + GprVolumeRepository
新增数据层方案A纯数据通道,把逐线 GPR 处理后 int16 量化体(io::gpr buildLineVolumeFromGpr3dv 产 BuiltI16)反量化成 app 渲染链吃的稠密 float 体 (data::VolumeGrid),使真实雷达三维体可经现成 loadVolume->addVolume->buildVoxel 显示(替 mock),零碰并行会话 UI/树/异常热文件。 - builtI16ToVolumeGrid:纯函数适配器,逐体素 Quant::toPhys 反量化, kBlank 空值哨兵->NaN(下游 buildVoxel 映射透明),origin/spacing/vmin-vmax 搬运。 - createGprVolumeGrid(lineDir,linePrefix,coarse=4):走 P1/P2 链建逐线体->适配。 - 单测:适配器逐值反量化/维度/spacing/origin/kBlank->NaN;全链合成多通道 .iprb 走真链产出有效 VolumeGrid;缺线抛异常。434 测试全过,无回归。
This commit is contained in:
parent
4e2bdc3b81
commit
d70590cbef
|
|
@ -18,9 +18,11 @@ add_library(geopro_data STATIC
|
|||
api/ApiDatasetCommandRepository.cpp
|
||||
api/Api3dRepository.cpp
|
||||
api/DatasetLoadHandles.cpp
|
||||
api/NavRequest.cpp)
|
||||
api/NavRequest.cpp
|
||||
GprVolumeRepository.cpp)
|
||||
target_include_directories(geopro_data PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(geopro_data PUBLIC geopro_core geopro_net Qt6::Core PRIVATE nlohmann_json::nlohmann_json)
|
||||
# geopro_gpr3dv_bridge:逐线 GPR 体(BuiltI16)来源,GprVolumeRepository 反量化为 VolumeGrid。
|
||||
target_link_libraries(geopro_data PUBLIC geopro_core geopro_net Qt6::Core geopro_gpr3dv_bridge PRIVATE nlohmann_json::nlohmann_json)
|
||||
target_compile_features(geopro_data PUBLIC cxx_std_17)
|
||||
set_target_properties(geopro_data PROPERTIES AUTOMOC ON AUTOUIC OFF AUTORCC OFF)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
#include "data/GprVolumeRepository.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "core/model/ScalarVolumeI16.hpp"
|
||||
#include "io/gpr/Gpr3dvVolumeBridge.hpp"
|
||||
|
||||
namespace geopro::data {
|
||||
|
||||
VolumeGrid builtI16ToVolumeGrid(const geopro::core::BuiltI16& built) {
|
||||
const int nx = built.vol.nx();
|
||||
const int ny = built.vol.ny();
|
||||
const int nz = built.vol.nz();
|
||||
|
||||
VolumeGrid out;
|
||||
out.vol = geopro::core::ScalarVolume(nx, ny, nz);
|
||||
out.origin = built.origin;
|
||||
out.spacing = built.spacing;
|
||||
out.vmin = built.vminPhys;
|
||||
out.vmax = built.vmaxPhys;
|
||||
|
||||
// 逐体素反量化(布局一致:i 最快、k 最慢)。
|
||||
// kBlank → NaN:下游 render::buildVoxel 把 NaN 映射到 [vmin,vmax] 外的哨兵 →
|
||||
// 传递函数置全透明(与 float 路径空值语义一致)。
|
||||
const std::vector<std::int16_t>& src = built.vol.data();
|
||||
std::vector<double>& dst = out.vol.data();
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
for (std::size_t idx = 0; idx < src.size(); ++idx) {
|
||||
const std::int16_t q = src[idx];
|
||||
dst[idx] = (q == geopro::core::ScalarVolumeI16::kBlank)
|
||||
? nan
|
||||
: built.quant.toPhys(q);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
VolumeGrid createGprVolumeGrid(const std::string& lineDir,
|
||||
const std::string& linePrefix, int coarse) {
|
||||
// 走 P1/P2 链(io::gpr)得处理后 int16 量化体 → 反量化为 app 的 float 体。
|
||||
// metricsOut 传 nullptr:repository 只产数据,度量留给 gpr_poc CLI。
|
||||
const geopro::core::BuiltI16 built =
|
||||
geopro::io::gpr::buildLineVolumeFromGpr3dv(lineDir, linePrefix,
|
||||
/*metricsOut=*/nullptr, coarse);
|
||||
return builtI16ToVolumeGrid(built);
|
||||
}
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef GEOPRO_DATA_GPRVOLUMEREPOSITORY_HPP
|
||||
#define GEOPRO_DATA_GPRVOLUMEREPOSITORY_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/algo/GprVolumeBuilder.hpp" // geopro::core::BuiltI16
|
||||
#include "repo/I3dSceneRepository.hpp" // geopro::data::VolumeGrid
|
||||
|
||||
namespace geopro::data {
|
||||
|
||||
// 把逐线 GPR 处理后量化体(int16 BuiltI16)反量化成 app 渲染链吃的稠密 float 体
|
||||
// (VolumeGrid),使真实雷达三维体可经现成 loadVolume→addVolume→buildVoxel 显示。
|
||||
//
|
||||
// 数据层方案 A(纯数据层,零 UI/render/controller 改动):
|
||||
// io::gpr::buildLineVolumeFromGpr3dv(P1/P2 链) → core::BuiltI16(int16+Quant)
|
||||
// → builtI16ToVolumeGrid(反量化) → data::VolumeGrid(float)。
|
||||
// 与现 mock(Api3dRepository 反演剖面 IDW)同格式输出,故下游 addVolume 无需任何改动。
|
||||
|
||||
// 纯函数适配器:BuiltI16(int16 量化体) → VolumeGrid(float 稠密体)。
|
||||
// - 逐体素 Quant::toPhys 反量化;kBlank(空值哨兵)→ NaN(下游 buildVoxel 映射为透明)。
|
||||
// - origin/spacing 原样搬运;vmin/vmax 用 BuiltI16 的物理值域。
|
||||
// - 布局一致(i 最快、k 最慢),两体可逐体素对位拷贝。
|
||||
VolumeGrid builtI16ToVolumeGrid(const geopro::core::BuiltI16& built);
|
||||
|
||||
// 走 P1/P2 链建逐线 GPR 体并适配成 app 的 VolumeGrid。
|
||||
// lineDir/linePrefix 同 gpr3dv-smoke / build-line(如 "D:/Downloads/明星路", "明星路_001")。
|
||||
// coarse(下采样因子,≥1):沿测线(道/X 轴)每 coarse 道取 1,省内存;横向/深度保全分辨率。
|
||||
// 稠密 VolumeGrid 全内存,长线需较大 coarse 控内存(默认 4 = build-line POC 档)。
|
||||
// 失败(加载失败/立方体为空)→ 抛 std::runtime_error(由 io::gpr 链抛出,原样透传)。
|
||||
VolumeGrid createGprVolumeGrid(const std::string& lineDir,
|
||||
const std::string& linePrefix, int coarse = 4);
|
||||
|
||||
} // namespace geopro::data
|
||||
|
||||
#endif // GEOPRO_DATA_GPRVOLUMEREPOSITORY_HPP
|
||||
|
|
@ -54,6 +54,9 @@ target_sources(geopro_tests PRIVATE data/test_dataset_load_handles.cpp)
|
|||
target_sources(geopro_tests PRIVATE data/test_async_repo_dispatch.cpp)
|
||||
# NavRequest 离线单测(QVariant payload: done/failed/abort 闸门)。
|
||||
target_sources(geopro_tests PRIVATE data/test_nav_request.cpp)
|
||||
# GprVolumeRepository:逐线 GPR int16 量化体(BuiltI16)→ app 渲染链 float 体(VolumeGrid)。
|
||||
# 纯适配器逐值反量化 + 全链(合成多通道 .iprb 走真 P1/P2)产出有效 VolumeGrid。
|
||||
target_sources(geopro_tests PRIVATE data/test_gpr_volume_repository.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_data)
|
||||
|
||||
# store 层:ChunkedVolumeStore(GPR 三维体分块压缩落盘 round-trip + 边缘块 + 压缩生效)。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
// 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);
|
||||
|
||||
// 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
|
||||
Loading…
Reference in New Issue