feat(gpr3dv): 桥接处理后立方体→geopro量化/分块/VTK体绘制(P2 渲第一条线真三维体)
把 gpr3dv(P1)处理链产出的处理后立方体桥接到 geopro 已有的量化+ChunkedVolumeStore
+VTK 体绘制,渲出第一条线(明星路_001)的真三维体。算法零改动,仅复用 geopro 量化/
store/渲染。
- 新增桥接 src/io/gpr/Gpr3dvVolumeBridge.{hpp,cpp}(独立 target geopro_gpr3dv_bridge,
不污染纯 C++17 解析层):走 P1 原版 API load→buildVolumeData→runPipeline(默认链)
→再 buildVolumeData(处理后),得 volumeData[通道][道][样本];轴映射 X=道/Y=通道/
Z=样本;扫值域定 Quant(offset=中点);世界 spacing(dx=道距/dy=通道横距/dz=深度采样距)。
- gpr_poc 加子命令 build-line <lineDir> <linePrefix> --out <store> [--levels N]:
桥接→ChunkedVolumeStore::write+buildPyramid,报维度/量化/spacing/内存/耗时;view 渲。
- 测试 tests/io/gpr/test_gpr3dv_volume_bridge.cpp:合成 2 通道 Impulse 测线走真链,
校验轴映射/spacing/量化(offset=中点)/稠密体无 kBlank/缺线抛异常。
真实数据验收(明星路_001):体维度 45305×14×796(道×通道×样本,样本由 821 经零时校正
裁至 796),处理后值域[-9249,9206] scale=0.288 offset=-21.5,spacing dx=0.049/dy=0.105/
dz=0.0101m,落盘 845MB 压缩比 1.14x;view --preview/--base 渲出无纹理错、整卷盖全。
全量测试通过。
This commit is contained in:
parent
8b57dd9679
commit
a32822f7d6
|
|
@ -8,3 +8,12 @@ target_link_libraries(geopro_io_gpr PUBLIC geopro_core)
|
|||
|
||||
# 纯 C++17 解析层,零 Qt/VTK。顶层全局开启了 AUTOMOC/UIC/RCC,这里显式关闭保持纯净。
|
||||
set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
|
||||
|
||||
# gpr3dv 桥接层(P2):把 vendored gpr3dv(P1) 处理后立方体 → geopro 量化体(BuiltI16)。
|
||||
# 独立 target(不污染上面的纯 C++17 解析层):链 geopro_gpr3dv(Qt)+geopro_core。
|
||||
add_library(geopro_gpr3dv_bridge STATIC Gpr3dvVolumeBridge.cpp)
|
||||
target_include_directories(geopro_gpr3dv_bridge PUBLIC ${CMAKE_SOURCE_DIR}/src)
|
||||
target_compile_features(geopro_gpr3dv_bridge PUBLIC cxx_std_17)
|
||||
target_link_libraries(geopro_gpr3dv_bridge PUBLIC geopro_core geopro_gpr3dv)
|
||||
set_target_properties(geopro_gpr3dv_bridge PROPERTIES
|
||||
AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,185 @@
|
|||
#include "io/gpr/Gpr3dvVolumeBridge.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "GPRDataModel.h"
|
||||
#include "IprhParser.h"
|
||||
#include "RadarProcessor.h"
|
||||
|
||||
#include "core/model/ScalarVolumeI16.hpp"
|
||||
|
||||
namespace geopro::io::gpr {
|
||||
|
||||
namespace {
|
||||
|
||||
// 全体 traces 平均绝对幅值——朴素的「处理是否生效」标量证据(与 P1 冒烟同口径)。
|
||||
double meanAbsAmplitude(const GPRDataModel& model) {
|
||||
long double sum = 0.0L;
|
||||
long long count = 0;
|
||||
for (const RadarTrace& tr : model.traces) {
|
||||
for (short v : tr.amplitudes) {
|
||||
sum += static_cast<long double>(v < 0 ? -v : v);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count > 0 ? static_cast<double>(sum / count) : 0.0;
|
||||
}
|
||||
|
||||
// 通道横向间距(米):优先 chYOffsets 跨度 /(通道数-1);取不到退路 ≈1.37/(通道数-1)。
|
||||
double channelSpacingY(const GPRDataModel::Header& h, int channels) {
|
||||
if (channels <= 1) return 1.0;
|
||||
const auto& off = h.chYOffsets;
|
||||
if (off.size() >= 2) {
|
||||
float lo = off.front(), hi = off.front();
|
||||
for (float v : off) {
|
||||
lo = std::min(lo, v);
|
||||
hi = std::max(hi, v);
|
||||
}
|
||||
const double span = static_cast<double>(hi) - static_cast<double>(lo);
|
||||
if (span > 1e-9) return span / (channels - 1);
|
||||
}
|
||||
// 退路:明星路 14 通道横向跨度 ≈1.37m(-0.686~+0.686)。
|
||||
constexpr double kArrayWidthM = 1.37;
|
||||
return kArrayWidthM / (channels - 1);
|
||||
}
|
||||
|
||||
// 深度采样间距(米):(timeWindow/samples) ns × 波速(m/ns) / 2(往返)。
|
||||
// 与 GPRDataModel::calculateDepth 同式。取不到 → 1.0(单位间距)。
|
||||
double depthSpacingZ(const GPRDataModel::Header& h) {
|
||||
if (h.samplesPerTrace <= 0 || h.timeWindowNs <= 0.0) return 1.0;
|
||||
const double timePerSample = h.timeWindowNs / h.samplesPerTrace; // ns
|
||||
const double vel = h.waveVelocity > 0.0 ? h.waveVelocity : 0.1; // m/ns
|
||||
const double dz = timePerSample * vel / 2.0;
|
||||
return dz > 1e-12 ? dz : 1.0;
|
||||
}
|
||||
|
||||
double nowMs(std::chrono::steady_clock::time_point t0) {
|
||||
const auto t1 = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir,
|
||||
const std::string& linePrefix,
|
||||
BridgeMetrics* metricsOut) {
|
||||
const QString dir = QString::fromLocal8Bit(lineDir.c_str());
|
||||
const QString base = QString::fromLocal8Bit(linePrefix.c_str());
|
||||
|
||||
// 1) 走 P1 链:load → buildVolumeData(处理前) → runPipeline → 再 buildVolumeData。
|
||||
const auto tLoad = std::chrono::steady_clock::now();
|
||||
GPRDataModel model;
|
||||
if (!IprhParser::loadImpulseMultiChannel(dir, base, model)) {
|
||||
throw std::runtime_error("loadImpulseMultiChannel 失败: " + lineDir + " / " +
|
||||
linePrefix);
|
||||
}
|
||||
model.buildVolumeData(); // 处理前立方体(用于 before 统计)
|
||||
const double meanBefore = meanAbsAmplitude(model);
|
||||
const double loadMs = nowMs(tLoad);
|
||||
|
||||
const auto tPipe = std::chrono::steady_clock::now();
|
||||
RadarProcessor::ProcPipeline pipeline;
|
||||
pipeline.setDefaultFlow();
|
||||
GPRDataModel processed = RadarProcessor::runPipeline(model, pipeline);
|
||||
// ★关键:runPipeline 原位变换 traces 且零时校正会改 samplesPerTrace,
|
||||
// 不重建 volumeData,故必须再建一次拿处理后立方体。
|
||||
processed.buildVolumeData();
|
||||
const double meanAfter = meanAbsAmplitude(processed);
|
||||
const double pipelineMs = nowMs(tPipe);
|
||||
|
||||
// 2) 立方体维度:volumeData[通道][道][样本] → 轴 X=道/Y=通道/Z=样本。
|
||||
const int channels = processed.volumeData.size();
|
||||
const int traces = channels > 0 ? processed.volumeData[0].size() : 0;
|
||||
const int samples =
|
||||
(channels > 0 && traces > 0) ? processed.volumeData[0][0].size() : 0;
|
||||
if (channels <= 0 || traces <= 0 || samples <= 0) {
|
||||
throw std::runtime_error("处理后立方体为空(通道/道/样本 = " +
|
||||
std::to_string(channels) + "/" +
|
||||
std::to_string(traces) + "/" +
|
||||
std::to_string(samples) + ")");
|
||||
}
|
||||
const int nx = traces; // X=道(沿测线)
|
||||
const int ny = channels; // Y=通道(横向)
|
||||
const int nz = samples; // Z=样本(深度)
|
||||
|
||||
// 3) 扫处理后值域 → Quant(offset=中点,防溢出)。
|
||||
const auto tFill = std::chrono::steady_clock::now();
|
||||
short rawMin = std::numeric_limits<short>::max();
|
||||
short rawMax = std::numeric_limits<short>::min();
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
const auto& chData = processed.volumeData[c];
|
||||
for (int t = 0; t < traces && t < chData.size(); ++t) {
|
||||
const auto& trData = chData[t];
|
||||
for (int s = 0; s < samples && s < trData.size(); ++s) {
|
||||
const short v = trData[s];
|
||||
if (v < rawMin) rawMin = v;
|
||||
if (v > rawMax) rawMax = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rawMin > rawMax) { // 退化(理论不至):置零区间。
|
||||
rawMin = 0;
|
||||
rawMax = 0;
|
||||
}
|
||||
const double vmin = static_cast<double>(rawMin);
|
||||
const double vmax = static_cast<double>(rawMax);
|
||||
|
||||
geopro::core::Quant quant;
|
||||
// 量化到 int16 有效区间 [-32767, 32767](kBlank=-32768 保留),留两端裕度用 64000。
|
||||
quant.scale = (vmax > vmin) ? (vmax - vmin) / 64000.0 : 1.0;
|
||||
quant.offset = 0.5 * (vmin + vmax); // 中点 → 防溢出
|
||||
|
||||
// 4) 逐 (ch,trace,sample) 填体。GPR 立方体为稠密体(每体素有值),无空洞 → 不置 kBlank。
|
||||
geopro::core::BuiltI16 built;
|
||||
built.vol = geopro::core::ScalarVolumeI16(nx, ny, nz);
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
const auto& chData = processed.volumeData[c];
|
||||
for (int t = 0; t < traces; ++t) {
|
||||
const bool hasTrace = t < chData.size();
|
||||
for (int s = 0; s < samples; ++s) {
|
||||
short v = 0;
|
||||
if (hasTrace && s < chData[t].size()) v = chData[t][s];
|
||||
// X=道 t、Y=通道 c、Z=样本 s。
|
||||
built.vol.at(t, c, s) = quant.toQ(static_cast<double>(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
const double fillMs = nowMs(tFill);
|
||||
|
||||
// 5) 几何:origin=0,spacing 按 X=道距 / Y=通道横距 / Z=深度采样距。
|
||||
const GPRDataModel::Header& h = processed.header;
|
||||
const double dx = h.distanceInc > 1e-9 ? h.distanceInc : 1.0;
|
||||
const double dy = channelSpacingY(h, channels);
|
||||
const double dz = depthSpacingZ(h);
|
||||
|
||||
built.quant = quant;
|
||||
built.origin = {0.0, 0.0, 0.0};
|
||||
built.spacing = {dx, dy, dz};
|
||||
built.vminPhys = vmin;
|
||||
built.vmaxPhys = vmax;
|
||||
|
||||
if (metricsOut) {
|
||||
metricsOut->nx = nx;
|
||||
metricsOut->ny = ny;
|
||||
metricsOut->nz = nz;
|
||||
metricsOut->meanAbsBefore = meanBefore;
|
||||
metricsOut->meanAbsAfter = meanAfter;
|
||||
metricsOut->vminPhys = vmin;
|
||||
metricsOut->vmaxPhys = vmax;
|
||||
metricsOut->dx = dx;
|
||||
metricsOut->dy = dy;
|
||||
metricsOut->dz = dz;
|
||||
metricsOut->loadMs = loadMs;
|
||||
metricsOut->pipelineMs = pipelineMs;
|
||||
metricsOut->fillMs = fillMs;
|
||||
}
|
||||
|
||||
return built;
|
||||
}
|
||||
|
||||
} // namespace geopro::io::gpr
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP
|
||||
#define GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/algo/GprVolumeBuilder.hpp" // geopro::core::BuiltI16
|
||||
|
||||
namespace geopro::io::gpr {
|
||||
|
||||
// 把 vendored gpr3dv(P1) 处理链产出的【处理后立方体】桥接成 geopro 的量化体
|
||||
// (BuiltI16),供 ChunkedVolumeStore::write + buildPyramid + VTK 体绘制消费。
|
||||
//
|
||||
// 链路(走 P1 原版 API,算法零改动):
|
||||
// IprhParser::loadImpulseMultiChannel(dir, base, model)
|
||||
// → model.buildVolumeData() // 处理前立方体
|
||||
// → RadarProcessor::runPipeline(默认链) // 原位变换 traces,不重建 volumeData
|
||||
// → model.buildVolumeData() // ★必须再建:拿处理后立方体
|
||||
// 得 volumeData[通道][道][样本]。
|
||||
//
|
||||
// 轴映射(geopro 体):X=道(沿测线)、Y=通道(横向)、Z=样本(深度)。
|
||||
// 即 nx=道数、ny=通道数、nz=样本数,逐 (ch,trace,sample) 填值。
|
||||
// 量化:扫处理后值域 → Quant(offset=中点,防溢出),空值=kBlank。
|
||||
// 世界 spacing:
|
||||
// X=道间距 dx(header DISTANCE INTERVAL,取不到用 1.0);
|
||||
// Y=通道横向间距(chYOffsets 跨度/(通道数-1),取不到用 ~1.37/(通道数-1));
|
||||
// Z=深度采样间距(timeWindow/samples × 波速 / 2,取不到用 1.0)。origin=0。
|
||||
struct BridgeMetrics {
|
||||
int nx = 0, ny = 0, nz = 0; // 体维度(道 × 通道 × 样本)
|
||||
double meanAbsBefore = 0.0; // 处理前 traces 平均绝对幅值
|
||||
double meanAbsAfter = 0.0; // 处理后 traces 平均绝对幅值
|
||||
double vminPhys = 0.0, vmaxPhys = 0.0;
|
||||
double dx = 0.0, dy = 0.0, dz = 0.0; // 世界 spacing
|
||||
double loadMs = 0.0; // 读 + 建立方体
|
||||
double pipelineMs = 0.0; // runPipeline + 再建立方体
|
||||
double fillMs = 0.0; // 扫值域 + 量化填体
|
||||
};
|
||||
|
||||
// 走 P1 链得处理后立方体 → 量化映射成 geopro BuiltI16(轴 X=道/Y=通道/Z=样本)。
|
||||
// lineDir/linePrefix 同 gpr3dv-smoke(如 "D:/Downloads/明星路", "明星路_001")。
|
||||
// metricsOut 非空时回填维度/量化/spacing/耗时(供 CLI 报告,不编造)。
|
||||
// 失败(加载失败/立方体为空) → 抛 std::runtime_error。
|
||||
geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir,
|
||||
const std::string& linePrefix,
|
||||
BridgeMetrics* metricsOut);
|
||||
|
||||
} // namespace geopro::io::gpr
|
||||
|
||||
#endif // GEOPRO_IO_GPR_GPR3DVVOLUMEBRIDGE_HPP
|
||||
|
|
@ -223,4 +223,9 @@ target_sources(geopro_tests PRIVATE io/gpr/test_gpr_survey_assembler.cpp)
|
|||
target_sources(geopro_tests PRIVATE io/gpr/test_gps_track.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_io_gpr)
|
||||
|
||||
# Gpr3dvVolumeBridge(P2):gpr3dv 处理后立方体 → geopro 量化体(轴 X=道/Y=通道/Z=样本)。
|
||||
# 链 geopro_gpr3dv_bridge(含 vendored gpr3dv + Qt)。
|
||||
target_sources(geopro_tests PRIVATE io/gpr/test_gpr3dv_volume_bridge.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_gpr3dv_bridge)
|
||||
|
||||
add_subdirectory(spike) # spike S3: banded contour 渲染验证
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
// 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) {
|
||||
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 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
|
||||
|
|
@ -16,6 +16,7 @@ target_include_directories(gpr_poc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
|||
|
||||
target_link_libraries(gpr_poc PRIVATE
|
||||
geopro_io_gpr
|
||||
geopro_gpr3dv_bridge
|
||||
geopro_core
|
||||
geopro_store
|
||||
geopro_render)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "core/model/ColorScale.hpp"
|
||||
#include "core/model/ScalarVolumeI16.hpp"
|
||||
#include "data/store/ChunkedVolumeStore.hpp"
|
||||
#include "io/gpr/Gpr3dvVolumeBridge.hpp"
|
||||
#include "io/gpr/GprSurveyAssembler.hpp"
|
||||
#include "io/gpr/IprHeader.hpp"
|
||||
#include "render/actors/VoxelActor.hpp"
|
||||
|
|
@ -798,6 +799,109 @@ int cmdBuildGeo(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// build-line:走 gpr3dv(P1) 处理链→处理后立方体→geopro 量化体→分块+金字塔(P2)
|
||||
// ============================================================================
|
||||
//
|
||||
// 与 build/build-stream/build-geo 不同:本命令不走 geopro 自家解析+采样核建体,
|
||||
// 而是【复用 vendored gpr3dv 的读+处理链】(算法零改动)产出处理后立方体
|
||||
// volumeData[通道][道][样本],再桥接(Gpr3dvVolumeBridge)成 geopro int16 量化体
|
||||
// (轴 X=道/Y=通道/Z=样本),落 ChunkedVolumeStore + buildPyramid。原样渲(14 格 Y
|
||||
// 薄体),不做横向插值加密。
|
||||
int cmdBuildLine(int argc, char** argv) {
|
||||
const Args a = parseArgs(argc, argv, 2);
|
||||
if (a.positional.size() < 2) {
|
||||
std::cerr << "用法: gpr_poc build-line <lineDir> <linePrefix> "
|
||||
"--out <storeDir> [--levels 3]\n"
|
||||
"例: gpr_poc build-line \"D:/Downloads/明星路\" 明星路_001 "
|
||||
"--out tmp/line001_proc --levels 3\n";
|
||||
return 2;
|
||||
}
|
||||
const std::string lineDir = a.positional[0];
|
||||
const std::string linePrefix = a.positional[1];
|
||||
const int levels = std::stoi(a.get("levels", "3"));
|
||||
const std::string out =
|
||||
a.get("out", (fs::temp_directory_path() / "gpr_store_line").string());
|
||||
|
||||
std::cout << "[build-line] lineDir=" << lineDir << " linePrefix=" << linePrefix
|
||||
<< " levels=" << levels << " out=" << out << "\n";
|
||||
|
||||
// 1) gpr3dv 处理链 → 处理后立方体 → 桥接量化体。
|
||||
Stopwatch swBridge;
|
||||
geopro::io::gpr::BridgeMetrics bm;
|
||||
geopro::core::BuiltI16 built =
|
||||
geopro::io::gpr::buildLineVolumeFromGpr3dv(lineDir, linePrefix, &bm);
|
||||
const double bridgeMs = swBridge.elapsedMs();
|
||||
|
||||
const std::int64_t nx = built.vol.nx(), ny = built.vol.ny(),
|
||||
nz = built.vol.nz();
|
||||
const std::int64_t voxels = nx * ny * nz;
|
||||
const std::int64_t rawBytes = voxels * 2; // int16
|
||||
|
||||
std::cout << "[build-line] 处理前后平均绝对幅值: " << bm.meanAbsBefore << " → "
|
||||
<< bm.meanAbsAfter << " (处理"
|
||||
<< (std::abs(bm.meanAbsAfter - bm.meanAbsBefore) > 1e-9 ? "已生效"
|
||||
: "未变化")
|
||||
<< ")\n";
|
||||
std::cout << "[build-line] 体维度(道×通道×样本) = " << nx << " x " << ny
|
||||
<< " x " << nz << "\n";
|
||||
std::cout << "[build-line] 世界 spacing dx=" << bm.dx << " dy=" << bm.dy
|
||||
<< " dz=" << bm.dz << " (m)\n";
|
||||
|
||||
// 2) 落盘 + 金字塔(道很长 45305>16384 → 需 levels≥2 使最粗层≤16384)。
|
||||
fs::create_directories(out);
|
||||
Stopwatch swWrite;
|
||||
geopro::data::ChunkedVolumeStore::write(out, built);
|
||||
const double writeMs = swWrite.elapsedMs();
|
||||
|
||||
Stopwatch swPyr;
|
||||
{
|
||||
geopro::data::ChunkedVolumeStore store(out);
|
||||
store.buildPyramid(levels);
|
||||
}
|
||||
const double pyrMs = swPyr.elapsedMs();
|
||||
|
||||
const std::int64_t dataBytes = storeDataBytes(out);
|
||||
const double ratio =
|
||||
dataBytes > 0 ? static_cast<double>(rawBytes) / dataBytes : 0.0;
|
||||
const double peak = Probe::peakMemMB();
|
||||
|
||||
std::cout << "\n=== build-line 指标(gpr3dv 处理后真三维体)===\n";
|
||||
std::cout << "桥接耗时(ms) : " << bridgeMs << " (含 读 " << bm.loadMs
|
||||
<< " + 处理 " << bm.pipelineMs << " + 量化填体 " << bm.fillMs
|
||||
<< ")\n";
|
||||
std::cout << "落盘耗时(ms) : " << writeMs << "\n";
|
||||
std::cout << "金字塔耗时(ms) : " << pyrMs << "\n";
|
||||
std::cout << "体维度 : " << nx << " x " << ny << " x " << nz << "\n";
|
||||
std::cout << "体素数 : " << voxels << "\n";
|
||||
std::cout << "处理后值域 : [" << bm.vminPhys << ", " << bm.vmaxPhys
|
||||
<< "] 量化 scale=" << built.quant.scale
|
||||
<< " offset=" << built.quant.offset << "\n";
|
||||
std::cout << "世界 spacing : dx=" << bm.dx << " dy=" << bm.dy
|
||||
<< " dz=" << bm.dz << " (m)\n";
|
||||
std::cout << "原始体积(B) : " << rawBytes << " ("
|
||||
<< rawBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||
std::cout << "data.bin(B) : " << dataBytes << " ("
|
||||
<< dataBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||
std::cout << "压缩比 : " << ratio << " x\n";
|
||||
std::cout << "峰值内存(MB) : " << peak << "\n";
|
||||
|
||||
writeMetricLine(
|
||||
"build-line,prefix=" + linePrefix + ",nx=" + std::to_string(nx) +
|
||||
",ny=" + std::to_string(ny) + ",nz=" + std::to_string(nz) +
|
||||
",voxels=" + std::to_string(voxels) +
|
||||
",vmin=" + std::to_string(bm.vminPhys) +
|
||||
",vmax=" + std::to_string(bm.vmaxPhys) +
|
||||
",dx=" + std::to_string(bm.dx) + ",dy=" + std::to_string(bm.dy) +
|
||||
",dz=" + std::to_string(bm.dz) + ",rawB=" + std::to_string(rawBytes) +
|
||||
",dataB=" + std::to_string(dataBytes) +
|
||||
",ratio=" + std::to_string(ratio) +
|
||||
",bridgeMs=" + std::to_string(bridgeMs) +
|
||||
",writeMs=" + std::to_string(writeMs) +
|
||||
",pyrMs=" + std::to_string(pyrMs) + ",peakMB=" + std::to_string(peak));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmdLoad(int argc, char** argv) {
|
||||
const Args a = parseArgs(argc, argv, 2);
|
||||
if (a.positional.empty()) {
|
||||
|
|
@ -4063,6 +4167,8 @@ void usage() {
|
|||
"[--maxLines N]\n"
|
||||
" gpr_poc build-geo <dir> [--cellXY 0.5] [--cellZ 0.1] "
|
||||
"[--out <storeDir>] [--levels 4] [--maxLines N] [--curvilinear]\n"
|
||||
" gpr_poc build-line <lineDir> <linePrefix> --out <storeDir> "
|
||||
"[--levels 3]\n"
|
||||
" gpr_poc load <storeDir>\n"
|
||||
" gpr_poc selftest\n"
|
||||
" gpr_poc offscreen-smoke\n"
|
||||
|
|
@ -4097,6 +4203,7 @@ int main(int argc, char** argv) {
|
|||
if (cmd == "build") return cmdBuild(argc, argv);
|
||||
if (cmd == "build-stream") return cmdBuildStream(argc, argv);
|
||||
if (cmd == "build-geo") return cmdBuildGeo(argc, argv);
|
||||
if (cmd == "build-line") return cmdBuildLine(argc, argv);
|
||||
if (cmd == "load") return cmdLoad(argc, argv);
|
||||
if (cmd == "selftest") return cmdSelftest();
|
||||
if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();
|
||||
|
|
|
|||
Loading…
Reference in New Issue