geopro/tools/gpr_poc/main.cpp

446 lines
15 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.

// gpr_poc —— POC-B headless 度量 CLI。
//
// 串起整条地基:发现 14 通道 .iprb + .ord → assembleGprSurvey → buildGprVolume
// → ChunkedVolumeStore::write → buildPyramid → WholeVolumeSource(load)
// 在真实/合成数据上输出可测的真实指标(耗时/维度/体积/压缩比/加载/峰值内存)。
//
// 子命令:
// gpr_poc build <dir> [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out <storeDir>] [--levels 2]
// gpr_poc load <storeDir>
// gpr_poc selftest
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include "Probe.hpp"
#include "core/algo/GprVolumeBuilder.hpp"
#include "core/algo/IInterpolator.hpp"
#include "core/model/GprSurvey.hpp"
#include "core/model/ScalarVolumeI16.hpp"
#include "data/store/ChunkedVolumeStore.hpp"
#include "io/gpr/GprSurveyAssembler.hpp"
#include "render/source/WholeVolumeSource.hpp"
namespace fs = std::filesystem;
using geopro::tools::Probe;
using geopro::tools::Stopwatch;
namespace {
constexpr int kChannels = 14;
// ---- 命令行参数解析(极简 --key value----
struct Args {
std::map<std::string, std::string> kv;
std::vector<std::string> positional;
std::string get(const std::string& key, const std::string& def) const {
auto it = kv.find(key);
return it != kv.end() ? it->second : def;
}
};
Args parseArgs(int argc, char** argv, int start) {
Args a;
for (int i = start; i < argc; ++i) {
std::string tok = argv[i];
if (tok.rfind("--", 0) == 0 && i + 1 < argc) {
a.kv[tok.substr(2)] = argv[i + 1];
++i;
} else {
a.positional.push_back(tok);
}
}
return a;
}
// 把一行指标追加写入 last-metrics.txt与可执行同目录的工具源目录无关
// 写到当前工作目录便于汇总CSV 风格一行)。
void writeMetricLine(const std::string& line) {
std::ofstream f("last-metrics.txt", std::ios::app);
if (f) f << line << "\n";
}
// 发现某线 14 通道 .iprb按通道号 A01..A14 排序)+ 该线 .ord。
struct LineFiles {
std::vector<std::string> iprb; // 已按通道号排序
std::string ord;
};
LineFiles discoverLine(const std::string& dir, const std::string& line) {
LineFiles lf;
std::map<int, std::string> byChannel; // 通道号 → 路径(自动按号排序)
std::string ordPath;
for (const auto& e : fs::directory_iterator(dir)) {
if (!e.is_regular_file()) continue;
const std::string name = e.path().filename().string();
const std::string ext = e.path().extension().string();
// .ord优先匹配本线含 "_<line>" 且以 .ord 结尾),否则记下工区任一 .ord 作兜底。
if (ext == ".ord") {
if (name.find("_" + line + ".") != std::string::npos) {
ordPath = e.path().string();
} else if (ordPath.empty()) {
ordPath = e.path().string();
}
continue;
}
// .iprb匹配 "*_<line>_A<NN>.iprb"。
if (ext != ".iprb") continue;
const std::string tag = "_" + line + "_A";
const std::size_t pos = name.find(tag);
if (pos == std::string::npos) continue;
const std::size_t numStart = pos + tag.size();
std::size_t numEnd = numStart;
while (numEnd < name.size() &&
std::isdigit(static_cast<unsigned char>(name[numEnd]))) {
++numEnd;
}
if (numEnd == numStart) continue;
const int ch = std::stoi(name.substr(numStart, numEnd - numStart));
byChannel[ch] = e.path().string();
}
for (const auto& [ch, path] : byChannel) lf.iprb.push_back(path);
lf.ord = ordPath;
return lf;
}
// 由 survey 推 GridSpecX 沿测线Y 跨通道Z 深度。
geopro::core::GridSpec specFromSurvey(const geopro::core::GprSurvey& s,
double cellXY, double cellZ) {
geopro::core::GridSpec spec{};
const double rangeX =
(s.ntraces > 1) ? (s.ntraces - 1) * s.dx : 0.0;
const double y0 = s.channelY.empty() ? 0.0 : s.channelY.front();
const double y1 = s.channelY.empty() ? 0.0 : s.channelY.back();
const double rangeY = y1 - y0;
const double rangeZ =
(s.samples > 1) ? (s.samples - 1) * s.dz : 0.0;
auto cells = [](double range, double cell) {
if (cell <= 0.0) return 1;
return static_cast<int>(std::ceil(range / cell)) + 1;
};
spec.ox = s.x0;
spec.oy = y0;
spec.oz = s.z0;
spec.dx = cellXY;
spec.dy = cellXY;
spec.dz = cellZ;
spec.nx = cells(rangeX, cellXY);
spec.ny = cells(rangeY, cellXY);
spec.nz = cells(rangeZ, cellZ);
spec.power = 2.0;
spec.maxDist = cellXY * 2.0;
return spec;
}
// 落盘 data.bin 体积(所有 data*.bin 之和,含金字塔各级)。
std::int64_t storeDataBytes(const std::string& dir) {
std::int64_t total = 0;
for (const auto& e : fs::directory_iterator(dir)) {
if (!e.is_regular_file()) continue;
const std::string name = e.path().filename().string();
if (name.rfind("data", 0) == 0 &&
e.path().extension().string() == ".bin") {
total += static_cast<std::int64_t>(e.file_size());
}
}
return total;
}
int cmdBuild(int argc, char** argv) {
const Args a = parseArgs(argc, argv, 2);
if (a.positional.empty()) {
std::cerr << "用法: gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n";
return 2;
}
const std::string dir = a.positional[0];
const std::string line = a.get("line", "001");
const double cellXY = std::stod(a.get("cellXY", "0.2"));
const double cellZ = std::stod(a.get("cellZ", "0.05"));
const int levels = std::stoi(a.get("levels", "2"));
const std::string out =
a.get("out", (fs::temp_directory_path() / ("gpr_store_" + line)).string());
std::cout << "[build] dir=" << dir << " line=" << line
<< " cellXY=" << cellXY << " cellZ=" << cellZ
<< " levels=" << levels << " out=" << out << "\n";
const LineFiles lf = discoverLine(dir, line);
std::cout << "[build] 发现通道数=" << lf.iprb.size()
<< " ord=" << (lf.ord.empty() ? "(无)" : lf.ord) << "\n";
if (lf.iprb.size() != static_cast<std::size_t>(kChannels)) {
std::cerr << "[build] 警告: 通道数 != " << kChannels
<< "(仍按发现数继续)\n";
}
if (lf.iprb.empty() || lf.ord.empty()) {
std::cerr << "[build] 错误: 未发现 .iprb 或 .ord\n";
return 1;
}
// 1) 装配
Stopwatch swAsm;
geopro::core::GprSurvey survey =
geopro::io::gpr::assembleGprSurvey(lf.iprb, lf.ord);
const double asmMs = swAsm.elapsedMs();
std::cout << "[build] 装配完成 ntraces=" << survey.ntraces
<< " samples=" << survey.samples
<< " channels=" << survey.channelY.size()
<< " dx=" << survey.dx << " dz=" << survey.dz << "\n";
// 2) 建体
const geopro::core::GridSpec spec = specFromSurvey(survey, cellXY, cellZ);
std::cout << "[build] GridSpec nx=" << spec.nx << " ny=" << spec.ny
<< " nz=" << spec.nz << " dx=" << spec.dx << " dy=" << spec.dy
<< " dz=" << spec.dz << " maxDist=" << spec.maxDist << "\n";
Stopwatch swBuild;
geopro::core::BuiltI16 built = geopro::core::buildGprVolume(survey, spec);
const double buildMs = swBuild.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
// 3) 落盘 + 金字塔
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 指标 ===\n";
std::cout << "装配耗时(ms) : " << asmMs << "\n";
std::cout << "建体耗时(ms) : " << buildMs << "\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 << "原始体积(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=" + line + ",cellXY=" + std::to_string(cellXY) +
",cellZ=" + std::to_string(cellZ) + ",nx=" + std::to_string(nx) +
",ny=" + std::to_string(ny) + ",nz=" + std::to_string(nz) +
",voxels=" + std::to_string(voxels) +
",rawB=" + std::to_string(rawBytes) +
",dataB=" + std::to_string(dataBytes) +
",ratio=" + std::to_string(ratio) + ",asmMs=" + std::to_string(asmMs) +
",buildMs=" + std::to_string(buildMs) +
",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()) {
std::cerr << "用法: gpr_poc load <storeDir>\n";
return 2;
}
const std::string dir = a.positional[0];
std::cout << "[load] storeDir=" << dir << "\n";
Stopwatch sw;
geopro::render::WholeVolumeSource src(dir);
const double loadMs = sw.elapsedMs();
const auto& m = src.meta();
const std::int64_t voxels =
static_cast<std::int64_t>(m.nx) * m.ny * m.nz;
const std::int64_t wholeBytes = voxels * 2; // VTK_SHORT
const double peak = Probe::peakMemMB();
std::cout << "\n=== load 指标 ===\n";
std::cout << "加载耗时(ms) : " << loadMs << "\n";
std::cout << "整卷维度 : " << m.nx << " x " << m.ny << " x " << m.nz
<< "\n";
std::cout << "整卷字节(B) : " << wholeBytes << " ("
<< wholeBytes / (1024.0 * 1024.0) << " MB)\n";
std::cout << "峰值内存(MB) : " << peak << "\n";
writeMetricLine("load,dir=" + dir + ",nx=" + std::to_string(m.nx) +
",ny=" + std::to_string(m.ny) +
",nz=" + std::to_string(m.nz) +
",wholeB=" + std::to_string(wholeBytes) +
",loadMs=" + std::to_string(loadMs) +
",peakMB=" + std::to_string(peak));
return 0;
}
// ---- selftest合成极小数据走完整 build→load 管线 ----
// 写一个极小通道的 .iprb + .iprhsamples 采样、traces 道,值 = base + t + s
void writeSyntheticChannel(const fs::path& iprbPath, int samples, int traces,
std::int16_t base) {
const fs::path iprhPath =
fs::path(iprbPath).replace_extension(".iprh");
std::ofstream h(iprhPath);
h << "SAMPLES: " << samples << "\n";
h << "LAST TRACE: " << (traces - 1) << "\n";
h << "CHANNELS: 2\n";
h << "TIMEWINDOW: 100.0\n";
h << "SOIL VELOCITY: 100.0\n"; // m/µs → ×1e6 → 1e8 m/s
h << "DISTANCE INTERVAL: 0.05\n";
h.close();
std::ofstream b(iprbPath, std::ios::binary);
// 布局 [trace*samples + s]s 最快。
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));
}
}
}
int cmdSelftest() {
std::cout << "[selftest] 构造极小合成 survey2 通道)...\n";
const fs::path tmp =
fs::temp_directory_path() / "gpr_poc_selftest";
std::error_code ec;
fs::remove_all(tmp, ec);
fs::create_directories(tmp);
const int samples = 8;
const int traces = 12;
// 2 通道 .iprb/.iprh + .ord末列==1 标记有效通道,第 2 列为横偏 Y
writeSyntheticChannel(tmp / "syn_001_A01.iprb", samples, traces,
/*base=*/100);
writeSyntheticChannel(tmp / "syn_001_A02.iprb", samples, traces,
/*base=*/200);
{
std::ofstream ord(tmp / "syn_001.ord");
ord << "0 0.000000 -1.5 1\n";
ord << "1 1.000000 -1.5 1\n";
}
const std::vector<std::string> iprb = {
(tmp / "syn_001_A01.iprb").string(),
(tmp / "syn_001_A02.iprb").string()};
const std::string ord = (tmp / "syn_001.ord").string();
bool ok = true;
auto check = [&](bool cond, const std::string& msg) {
if (!cond) {
std::cerr << "[selftest] FAIL: " << msg << "\n";
ok = false;
}
};
try {
// 装配
geopro::core::GprSurvey survey =
geopro::io::gpr::assembleGprSurvey(iprb, ord);
check(survey.ntraces == traces, "ntraces");
check(survey.samples == samples, "samples");
check(survey.channelY.size() == 2, "channels");
// channelY 升序A01 偏移 0.0 在前A02 偏移 1.0 在后。
check(survey.channelY.front() < survey.channelY.back(), "channelY 升序");
// 建体cellXY 取通道间距 1.0 → ny=2cellZ 较细确保 nz>1。
const double cellXY = 1.0;
const double cellZ = std::max(survey.dz, 1e-12);
const geopro::core::GridSpec spec =
specFromSurvey(survey, cellXY, cellZ);
std::cout << "[selftest] GridSpec " << spec.nx << "x" << spec.ny << "x"
<< spec.nz << " dz=" << spec.dz << "\n";
check(spec.ny == 2, "ny==2");
geopro::core::BuiltI16 built =
geopro::core::buildGprVolume(survey, spec);
check(built.vol.nx() == spec.nx, "built nx");
check(built.vol.ny() == spec.ny, "built ny");
check(built.vol.nz() == spec.nz, "built nz");
// 落盘 + 金字塔
const std::string store = (tmp / "store").string();
fs::create_directories(store);
geopro::data::ChunkedVolumeStore::write(store, built, /*brick=*/4);
{
geopro::data::ChunkedVolumeStore s(store);
s.buildPyramid(1);
check(s.levels() == 2, "金字塔层数==2");
}
// 加载整卷,校验维度一致
geopro::render::WholeVolumeSource src(store);
check(src.meta().nx == spec.nx, "load nx");
check(src.meta().ny == spec.ny, "load ny");
check(src.meta().nz == spec.nz, "load nz");
// 某体素值合理性x0/y0 角点应有非 blank 量化值(落格命中首道首通道)。
const std::int16_t q = built.vol.at(0, 0, 0);
check(q != geopro::core::ScalarVolumeI16::kBlank, "(0,0,0) 非 blank");
} catch (const std::exception& e) {
std::cerr << "[selftest] 异常: " << e.what() << "\n";
ok = false;
}
fs::remove_all(tmp, ec);
std::cout << "[selftest] " << (ok ? "PASS" : "FAIL") << "\n";
return ok ? 0 : 1;
}
void usage() {
std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n"
" gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n"
" gpr_poc load <storeDir>\n"
" gpr_poc selftest\n";
}
} // namespace
int main(int argc, char** argv) {
if (argc < 2) {
usage();
return 2;
}
const std::string cmd = argv[1];
try {
if (cmd == "build") return cmdBuild(argc, argv);
if (cmd == "load") return cmdLoad(argc, argv);
if (cmd == "selftest") return cmdSelftest();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << "\n";
return 1;
}
usage();
return 2;
}