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:
gaozheng 2026-06-24 20:50:10 +08:00
parent 8b57dd9679
commit a32822f7d6
7 changed files with 485 additions and 0 deletions

View File

@ -8,3 +8,12 @@ target_link_libraries(geopro_io_gpr PUBLIC geopro_core)
# C++17 Qt/VTK AUTOMOC/UIC/RCC # C++17 Qt/VTK AUTOMOC/UIC/RCC
set_target_properties(geopro_io_gpr PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) 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)

View File

@ -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=0spacing 按 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

View File

@ -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

View File

@ -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_sources(geopro_tests PRIVATE io/gpr/test_gps_track.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_io_gpr) 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 add_subdirectory(spike) # spike S3: banded contour

View File

@ -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);
// 世界 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, ThrowsOnMissingLine) {
// 目录无任何 _A.iprh → loadImpulseMultiChannel 失败 → 抛异常。
geopro::io::gpr::BridgeMetrics bm;
EXPECT_THROW(
geopro::io::gpr::buildLineVolumeFromGpr3dv(dir_.string(), "nope", &bm),
std::runtime_error);
}
} // namespace

View File

@ -16,6 +16,7 @@ target_include_directories(gpr_poc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(gpr_poc PRIVATE target_link_libraries(gpr_poc PRIVATE
geopro_io_gpr geopro_io_gpr
geopro_gpr3dv_bridge
geopro_core geopro_core
geopro_store geopro_store
geopro_render) geopro_render)

View File

@ -33,6 +33,7 @@
#include "core/model/ColorScale.hpp" #include "core/model/ColorScale.hpp"
#include "core/model/ScalarVolumeI16.hpp" #include "core/model/ScalarVolumeI16.hpp"
#include "data/store/ChunkedVolumeStore.hpp" #include "data/store/ChunkedVolumeStore.hpp"
#include "io/gpr/Gpr3dvVolumeBridge.hpp"
#include "io/gpr/GprSurveyAssembler.hpp" #include "io/gpr/GprSurveyAssembler.hpp"
#include "io/gpr/IprHeader.hpp" #include "io/gpr/IprHeader.hpp"
#include "render/actors/VoxelActor.hpp" #include "render/actors/VoxelActor.hpp"
@ -798,6 +799,109 @@ int cmdBuildGeo(int argc, char** argv) {
return 0; 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) { int cmdLoad(int argc, char** argv) {
const Args a = parseArgs(argc, argv, 2); const Args a = parseArgs(argc, argv, 2);
if (a.positional.empty()) { if (a.positional.empty()) {
@ -4063,6 +4167,8 @@ void usage() {
"[--maxLines N]\n" "[--maxLines N]\n"
" gpr_poc build-geo <dir> [--cellXY 0.5] [--cellZ 0.1] " " gpr_poc build-geo <dir> [--cellXY 0.5] [--cellZ 0.1] "
"[--out <storeDir>] [--levels 4] [--maxLines N] [--curvilinear]\n" "[--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 load <storeDir>\n"
" gpr_poc selftest\n" " gpr_poc selftest\n"
" gpr_poc offscreen-smoke\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") return cmdBuild(argc, argv);
if (cmd == "build-stream") return cmdBuildStream(argc, argv); if (cmd == "build-stream") return cmdBuildStream(argc, argv);
if (cmd == "build-geo") return cmdBuildGeo(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 == "load") return cmdLoad(argc, argv);
if (cmd == "selftest") return cmdSelftest(); if (cmd == "selftest") return cmdSelftest();
if (cmd == "offscreen-smoke") return cmdOffscreenSmoke(); if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();