From 4e2bdc3b814c68ac39bd217f501a3cfa4ac34c9b Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 25 Jun 2026 08:42:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(gpr):=20build-line=20=E5=81=A5=E5=A3=AE?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BB=BB=E6=84=8F=E6=B5=8B=E7=BA=BF=20+=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20build-all=20=E6=89=B9=E9=87=8F=E5=BB=BA?= =?UTF-8?q?=E4=BD=93(=E7=A3=81=E7=9B=98=E5=AE=88=E6=8A=A4/coarse=20?= =?UTF-8?q?=E4=B8=8B=E9=87=87=E6=A0=B7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 桥接 buildLineVolumeFromGpr3dv 增 coarse 参数:沿测线(道/X 轴)按步长下采样, spacing.x ×stride 保持真实世界尺度;通道/样本保留全分辨率。默认 coarse=1 全分辨率, 对现有调用与测试零影响。 - build-line 增 --coarse F;单线建体核心抽出 buildOneLine,加体维度退化守护 (短桩线 nx<2 等报因跳过不落盘),整条 try/catch 不崩。 - 新增 build-all --outBase [--levels N] [--coarse F] [--minFreeGB G]:扫 *_A.iprh 发现全部测线,逐条建到 baseDir//; 每条建前查磁盘剩余,低于阈值(默认 3G)停并报已建哪些;单条异常捕获跳过不中断批量; 末尾打印各线状态(成功/跳过+因/维度/大小)与合计占用。 - 通道/道/样本全从数据读,不写死;不破坏现有 gpr/bridge 测试(12/12 通过)。 --- src/io/gpr/Gpr3dvVolumeBridge.cpp | 24 ++-- src/io/gpr/Gpr3dvVolumeBridge.hpp | 5 +- tools/gpr_poc/main.cpp | 205 ++++++++++++++++++++++++++---- 3 files changed, 202 insertions(+), 32 deletions(-) diff --git a/src/io/gpr/Gpr3dvVolumeBridge.cpp b/src/io/gpr/Gpr3dvVolumeBridge.cpp index f78d0d3..589fd31 100644 --- a/src/io/gpr/Gpr3dvVolumeBridge.cpp +++ b/src/io/gpr/Gpr3dvVolumeBridge.cpp @@ -67,7 +67,9 @@ double nowMs(std::chrono::steady_clock::time_point t0) { geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, const std::string& linePrefix, - BridgeMetrics* metricsOut) { + BridgeMetrics* metricsOut, + int coarse) { + const int stride = coarse > 1 ? coarse : 1; // 沿测线下采样步长(≥1) const QString dir = QString::fromLocal8Bit(lineDir.c_str()); const QString base = QString::fromLocal8Bit(linePrefix.c_str()); @@ -103,7 +105,9 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, std::to_string(traces) + "/" + std::to_string(samples) + ")"); } - const int nx = traces; // X=道(沿测线) + // 下采样后输出道数(向上取整保留末道附近):nxOut = ceil(traces/stride)。 + const int nxOut = (traces + stride - 1) / stride; + const int nx = nxOut; // X=道(沿测线,已按 stride 下采样) const int ny = channels; // Y=通道(横向) const int nz = samples; // Z=样本(深度) @@ -135,17 +139,19 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, quant.offset = 0.5 * (vmin + vmax); // 中点 → 防溢出 // 4) 逐 (ch,trace,sample) 填体。GPR 立方体为稠密体(每体素有值),无空洞 → 不置 kBlank。 + // 沿测线按 stride 下采样:输出道 to → 源道 t = to*stride。 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 to = 0; to < nxOut; ++to) { + const int t = to * stride; + const bool hasTrace = t < static_cast(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(v)); + if (hasTrace && s < static_cast(chData[t].size())) v = chData[t][s]; + // X=输出道 to、Y=通道 c、Z=样本 s。 + built.vol.at(to, c, s) = quant.toQ(static_cast(v)); } } } @@ -153,7 +159,9 @@ geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, // 5) 几何:origin=0,spacing 按 X=道距 / Y=通道横距 / Z=深度采样距。 const GPRDataModel::Header& h = processed.header; - const double dx = h.distanceInc > 1e-9 ? h.distanceInc : 1.0; + // 下采样后相邻输出道在世界中跨 stride 个原始道距 → dx ×stride 保持真实尺度。 + const double dxBase = h.distanceInc > 1e-9 ? h.distanceInc : 1.0; + const double dx = dxBase * stride; const double dy = channelSpacingY(h, channels); const double dz = depthSpacingZ(h); diff --git a/src/io/gpr/Gpr3dvVolumeBridge.hpp b/src/io/gpr/Gpr3dvVolumeBridge.hpp index 6af49c7..b404559 100644 --- a/src/io/gpr/Gpr3dvVolumeBridge.hpp +++ b/src/io/gpr/Gpr3dvVolumeBridge.hpp @@ -38,10 +38,13 @@ struct BridgeMetrics { // 走 P1 链得处理后立方体 → 量化映射成 geopro BuiltI16(轴 X=道/Y=通道/Z=样本)。 // lineDir/linePrefix 同 gpr3dv-smoke(如 "D:/Downloads/明星路", "明星路_001")。 // metricsOut 非空时回填维度/量化/spacing/耗时(供 CLI 报告,不编造)。 +// coarse(下采样因子,≥1):沿测线(道/X 轴)每 coarse 道取 1,spacing.x ×coarse 保形; +// 通道/样本(横向/深度)保留全分辨率。coarse≤1 即全分辨率。磁盘紧张时省空间用。 // 失败(加载失败/立方体为空) → 抛 std::runtime_error。 geopro::core::BuiltI16 buildLineVolumeFromGpr3dv(const std::string& lineDir, const std::string& linePrefix, - BridgeMetrics* metricsOut); + BridgeMetrics* metricsOut, + int coarse = 1); } // namespace geopro::io::gpr diff --git a/tools/gpr_poc/main.cpp b/tools/gpr_poc/main.cpp index c276dc6..68a1696 100644 --- a/tools/gpr_poc/main.cpp +++ b/tools/gpr_poc/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -808,33 +809,60 @@ int cmdBuildGeo(int argc, char** argv) { // 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 " - "--out [--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()); +// 磁盘剩余空间(GB):查 path 所在卷可用字节。失败(路径不存在等)→ -1(视为未知)。 +double freeSpaceGB(const std::string& path) { + std::error_code ec; + // 用已存在的父目录查(out 目录可能还没建)。 + fs::path p = fs::absolute(path, ec); + while (!p.empty() && !fs::exists(p, ec)) p = p.parent_path(); + if (p.empty()) p = fs::current_path(ec); + const fs::space_info si = fs::space(p, ec); + if (ec) return -1.0; + return static_cast(si.available) / (1024.0 * 1024.0 * 1024.0); +} + +// 单线建体结果(供 build-all 汇总,不编造)。ok=false 时 reason 给清晰原因。 +struct LineBuildResult { + std::string prefix; + bool ok = false; + std::string reason; // 失败/跳过原因 + std::int64_t nx = 0, ny = 0, nz = 0; + std::int64_t dataBytes = 0; +}; + +// 单线建体核心:gpr3dv 处理链 → 桥接量化体(可 coarse 下采样) → 落盘 + 金字塔。 +// 异常(加载失败/立方体空/短桩线维度退化)由调用方捕获,不在此中断批量。 +LineBuildResult buildOneLine(const std::string& lineDir, + const std::string& linePrefix, + const std::string& out, int levels, int coarse) { + LineBuildResult r; + r.prefix = linePrefix; std::cout << "[build-line] lineDir=" << lineDir << " linePrefix=" << linePrefix - << " levels=" << levels << " out=" << out << "\n"; + << " levels=" << levels << " coarse=" << coarse << " out=" << out + << "\n"; - // 1) gpr3dv 处理链 → 处理后立方体 → 桥接量化体。 + // 1) gpr3dv 处理链 → 处理后立方体 → 桥接量化体(coarse 沿测线下采样)。 Stopwatch swBridge; geopro::io::gpr::BridgeMetrics bm; - geopro::core::BuiltI16 built = - geopro::io::gpr::buildLineVolumeFromGpr3dv(lineDir, linePrefix, &bm); + geopro::core::BuiltI16 built = geopro::io::gpr::buildLineVolumeFromGpr3dv( + lineDir, linePrefix, &bm, coarse); const double bridgeMs = swBridge.elapsedMs(); const std::int64_t nx = built.vol.nx(), ny = built.vol.ny(), nz = built.vol.nz(); + r.nx = nx; + r.ny = ny; + r.nz = nz; + // 短桩线/退化体守护:维度过小无法建有意义的金字塔/体绘制 → 报因跳过(不落盘)。 + if (nx < 2 || ny < 1 || nz < 2) { + r.ok = false; + r.reason = "体维度退化(道×通道×样本=" + std::to_string(nx) + "x" + + std::to_string(ny) + "x" + std::to_string(nz) + + "),无法建可看体,跳过"; + std::cerr << "[build-line] " << linePrefix << " " << r.reason << "\n"; + return r; + } const std::int64_t voxels = nx * ny * nz; const std::int64_t rawBytes = voxels * 2; // int16 @@ -848,7 +876,7 @@ int cmdBuildLine(int argc, char** argv) { std::cout << "[build-line] 世界 spacing dx=" << bm.dx << " dy=" << bm.dy << " dz=" << bm.dz << " (m)\n"; - // 2) 落盘 + 金字塔(道很长 45305>16384 → 需 levels≥2 使最粗层≤16384)。 + // 2) 落盘 + 金字塔(道很长 → 需 levels≥2 使最粗层≤16384)。 fs::create_directories(out); Stopwatch swWrite; geopro::data::ChunkedVolumeStore::write(out, built); @@ -862,6 +890,7 @@ int cmdBuildLine(int argc, char** argv) { const double pyrMs = swPyr.elapsedMs(); const std::int64_t dataBytes = storeDataBytes(out); + r.dataBytes = dataBytes; const double ratio = dataBytes > 0 ? static_cast(rawBytes) / dataBytes : 0.0; const double peak = Probe::peakMemMB(); @@ -887,9 +916,9 @@ int cmdBuildLine(int argc, char** argv) { 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) + + "build-line,prefix=" + linePrefix + ",coarse=" + std::to_string(coarse) + + ",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) + @@ -899,6 +928,133 @@ int cmdBuildLine(int argc, char** argv) { ",bridgeMs=" + std::to_string(bridgeMs) + ",writeMs=" + std::to_string(writeMs) + ",pyrMs=" + std::to_string(pyrMs) + ",peakMB=" + std::to_string(peak)); + + r.ok = true; + return r; +} + +int cmdBuildLine(int argc, char** argv) { + const Args a = parseArgs(argc, argv, 2); + if (a.positional.size() < 2) { + std::cerr << "用法: gpr_poc build-line " + "--out [--levels 3] [--coarse F]\n" + "例: gpr_poc build-line \"D:/Downloads/明星路\" 明星路_001 " + "--out tmp/line001_proc --levels 3 --coarse 4\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 int coarse = std::stoi(a.get("coarse", "1")); + const std::string out = + a.get("out", (fs::temp_directory_path() / "gpr_store_line").string()); + + try { + const LineBuildResult r = + buildOneLine(lineDir, linePrefix, out, levels, coarse); + if (!r.ok) { + std::cerr << "[build-line] 跳过: " << r.reason << "\n"; + return 1; + } + return 0; + } catch (const std::exception& e) { + std::cerr << "[build-line] 失败(" << linePrefix << "): " << e.what() << "\n"; + return 1; + } +} + +// build-all:发现目录下所有测线(_Axx 分组),逐条 build-line 到 baseDir//。 +// 磁盘守护:每条建前查可用空间,低于阈值(默认 3GB)即停并报已建哪些。 +// 短桩线/异常单条捕获并跳过(报因),不中断其余。 +int cmdBuildAll(int argc, char** argv) { + const Args a = parseArgs(argc, argv, 2); + if (a.positional.empty() || !a.kv.count("outBase")) { + std::cerr << "用法: gpr_poc build-all --outBase " + "[--levels 3] [--coarse F] [--minFreeGB 3]\n" + "例: gpr_poc build-all \"D:/Downloads/明星路\" " + "--outBase tmp/lines_all --levels 3 --coarse 4\n"; + return 2; + } + const std::string lineDir = a.positional[0]; + const std::string outBase = a.get("outBase", ""); + const int levels = std::stoi(a.get("levels", "3")); + const int coarse = std::stoi(a.get("coarse", "1")); + const double minFreeGB = std::stod(a.get("minFreeGB", "3")); + + // 1) 发现所有测线前缀:扫 *__A.iprh,取 "<...>_" 部分(去 _A)。 + std::set prefixSet; + std::error_code ec; + for (const auto& e : fs::directory_iterator(lineDir, ec)) { + if (!e.is_regular_file()) continue; + const std::string name = e.path().filename().string(); + if (e.path().extension().string() != ".iprh") continue; + // 找 "_A" 通道后缀,截断得测线前缀。 + const std::size_t pos = name.rfind("_A"); + if (pos == std::string::npos) continue; + std::size_t d = pos + 2; + while (d < name.size() && std::isdigit(static_cast(name[d]))) + ++d; + if (d == pos + 2) continue; // _A 后无数字 → 非通道文件 + prefixSet.insert(name.substr(0, pos)); + } + if (prefixSet.empty()) { + std::cerr << "[build-all] 未在 " << lineDir << " 发现任何测线(*_A.iprh)\n"; + return 1; + } + std::vector prefixes(prefixSet.begin(), prefixSet.end()); + std::cout << "[build-all] 发现 " << prefixes.size() << " 条测线,outBase=" + << outBase << " levels=" << levels << " coarse=" << coarse + << " minFreeGB=" << minFreeGB << "\n"; + + fs::create_directories(outBase, ec); + + // 2) 逐条建:磁盘守护 → buildOneLine(单条 try/catch)。 + std::vector results; + bool stoppedByDisk = false; + for (const std::string& prefix : prefixes) { + const double freeGB = freeSpaceGB(outBase); + std::cout << "\n[build-all] --- " << prefix << " --- 剩余磁盘 " + << freeGB << " GB\n"; + if (freeGB >= 0.0 && freeGB < minFreeGB) { + std::cerr << "[build-all] 磁盘守护触发: 剩余 " << freeGB << " GB < " + << minFreeGB << " GB,停止,未建 " << prefix << " 及其后。\n"; + stoppedByDisk = true; + break; + } + const std::string out = (fs::path(outBase) / prefix).string(); + LineBuildResult r; + r.prefix = prefix; + try { + r = buildOneLine(lineDir, prefix, out, levels, coarse); + } catch (const std::exception& e) { + r.ok = false; + r.reason = std::string("异常: ") + e.what(); + std::cerr << "[build-all] " << prefix << " 失败: " << e.what() + << "(跳过,继续)\n"; + } + results.push_back(r); + } + + // 3) 汇总。 + std::cout << "\n=== build-all 汇总 ===\n"; + int okCount = 0; + std::int64_t totalBytes = 0; + for (const auto& r : results) { + if (r.ok) { + ++okCount; + totalBytes += r.dataBytes; + std::cout << " [OK] " << r.prefix << " 维度=" << r.nx << "x" << r.ny + << "x" << r.nz << " data=" + << r.dataBytes / (1024.0 * 1024.0) << " MB\n"; + } else { + std::cout << " [跳过] " << r.prefix << " 原因=" << r.reason << "\n"; + } + } + std::cout << "成功 " << okCount << "/" << prefixes.size() + << " 条,合计 data=" << totalBytes / (1024.0 * 1024.0 * 1024.0) + << " GB,剩余磁盘 " << freeSpaceGB(outBase) << " GB\n"; + if (stoppedByDisk) + std::cout << "注意: 因磁盘守护提前停止,部分测线未建。\n"; return 0; } @@ -4388,7 +4544,9 @@ void usage() { " gpr_poc build-geo [--cellXY 0.5] [--cellZ 0.1] " "[--out ] [--levels 4] [--maxLines N] [--curvilinear]\n" " gpr_poc build-line --out " - "[--levels 3]\n" + "[--levels 3] [--coarse F]\n" + " gpr_poc build-all --outBase " + "[--levels 3] [--coarse F] [--minFreeGB 3]\n" " gpr_poc load \n" " gpr_poc selftest\n" " gpr_poc offscreen-smoke\n" @@ -4424,6 +4582,7 @@ int main(int argc, char** 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 == "build-all") return cmdBuildAll(argc, argv); if (cmd == "load") return cmdLoad(argc, argv); if (cmd == "selftest") return cmdSelftest(); if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();