feat(gpr): 加 assembleGprSurveySlab 装配道区间 slab

只装配 [t0,t1) 道段的 GprSurvey,各通道用 readIprbRange 只读该段,
内存只随 slab 大小。x0=t0*dx 使 slab 世界 X 与全线对齐(B4 拼接靠此)。
抽公共 assembleFromScans helper 复用校验/标尺/Y 升序置换/值转置,
不改 assembleGprSurvey 公开行为。新增 slab 对拍与越界/通道数测试。
This commit is contained in:
gaozheng 2026-06-23 21:37:56 +08:00
parent 9d3b103e32
commit 81e18ebd2d
3 changed files with 159 additions and 32 deletions

View File

@ -36,35 +36,14 @@ std::string readFileText(const std::string& path) {
return ss.str();
}
} // namespace
geopro::core::GprSurvey assembleGprSurvey(
const std::vector<std::string>& channelIprbPaths,
const std::string& ordPath) {
// 1. .ord -> 各通道横偏(文件序)。
const std::vector<double> channelY0 =
parseChannelXOffsets(readFileText(ordPath));
if (channelY0.size() != channelIprbPaths.size()) {
throw std::runtime_error(
"通道数不一致: .ord 有效通道数与 .iprb 路径数量不符");
}
const std::size_t nchan = channelIprbPaths.size();
if (nchan == 0) {
throw std::runtime_error("无通道可装配");
}
// 2. 各通道读 header + BScan。
std::vector<IprHeader> headers;
std::vector<BScan> scans;
headers.reserve(nchan);
scans.reserve(nchan);
for (const std::string& iprbPath : channelIprbPaths) {
const IprHeader h = parseIprHeader(readFileText(toHeaderPath(iprbPath)));
headers.push_back(h);
scans.push_back(readIprb(iprbPath, h));
}
// 3. 校验 samples 一致ntraces 取各通道 traces 最小值(对齐)。
// 由各通道 .ord 横偏、header、已读 BScan 段装配 GprSurvey。
// scans 已是待装配的道段(全线或 slabntraces 取各通道段道数最小值(对齐);
// x0 由调用方给出(全线=0slab=t0*dx。samples 校验、Y 升序置换、值转置同源。
geopro::core::GprSurvey assembleFromScans(const std::vector<double>& channelY0,
const std::vector<IprHeader>& headers,
const std::vector<BScan>& scans,
double x0) {
const std::size_t nchan = scans.size();
const int samples = scans.front().samples;
std::int64_t minTraces = scans.front().traces;
for (std::size_t c = 0; c < nchan; ++c) {
@ -74,16 +53,15 @@ geopro::core::GprSurvey assembleGprSurvey(
minTraces = std::min(minTraces, scans[c].traces);
}
// 4. 由首通道 header 定标尺。
geopro::core::GprSurvey survey;
survey.samples = samples;
survey.ntraces = static_cast<int>(minTraces);
survey.x0 = 0.0;
survey.x0 = x0;
survey.dx = headers.front().distanceInterval;
survey.z0 = 0.0;
survey.dz = (samples > 1) ? depthOfSample(1, headers.front()) : 0.0;
// 5. 按 Y 升序求置换order[c] = 升序第 c 位对应的原通道索引。
// 按 Y 升序求置换order[c] = 升序第 c 位对应的原通道索引。
std::vector<std::size_t> order(nchan);
std::iota(order.begin(), order.end(), std::size_t{0});
std::stable_sort(order.begin(), order.end(),
@ -111,4 +89,69 @@ geopro::core::GprSurvey assembleGprSurvey(
return survey;
}
} // namespace
geopro::core::GprSurvey assembleGprSurvey(
const std::vector<std::string>& channelIprbPaths,
const std::string& ordPath) {
// 1. .ord -> 各通道横偏(文件序)。
const std::vector<double> channelY0 =
parseChannelXOffsets(readFileText(ordPath));
if (channelY0.size() != channelIprbPaths.size()) {
throw std::runtime_error(
"通道数不一致: .ord 有效通道数与 .iprb 路径数量不符");
}
const std::size_t nchan = channelIprbPaths.size();
if (nchan == 0) {
throw std::runtime_error("无通道可装配");
}
// 2. 各通道读 header + 全线 BScan。
std::vector<IprHeader> headers;
std::vector<BScan> scans;
headers.reserve(nchan);
scans.reserve(nchan);
for (const std::string& iprbPath : channelIprbPaths) {
const IprHeader h = parseIprHeader(readFileText(toHeaderPath(iprbPath)));
headers.push_back(h);
scans.push_back(readIprb(iprbPath, h));
}
// 3. 全线 x0=0其余校验/标尺/排序/置换由公共 helper 完成。
return assembleFromScans(channelY0, headers, scans, 0.0);
}
geopro::core::GprSurvey assembleGprSurveySlab(
const std::vector<std::string>& channelIprbPaths,
const std::string& ordPath,
std::int64_t t0, std::int64_t t1) {
// 1. .ord -> 各通道横偏(文件序)。
const std::vector<double> channelY0 =
parseChannelXOffsets(readFileText(ordPath));
if (channelY0.size() != channelIprbPaths.size()) {
throw std::runtime_error(
"通道数不一致: .ord 有效通道数与 .iprb 路径数量不符");
}
const std::size_t nchan = channelIprbPaths.size();
if (nchan == 0) {
throw std::runtime_error("无通道可装配");
}
// 2. 各通道读 header + 仅 [t0,t1) 道段readIprbRange 内做越界校验)。
std::vector<IprHeader> headers;
std::vector<BScan> scans;
headers.reserve(nchan);
scans.reserve(nchan);
for (const std::string& iprbPath : channelIprbPaths) {
const IprHeader h = parseIprHeader(readFileText(toHeaderPath(iprbPath)));
headers.push_back(h);
scans.push_back(readIprbRange(iprbPath, h, t0, t1));
}
// 3. x0 = t0*dx使该段世界 X 与全线对齐;其余同全线装配。
const double dx = headers.front().distanceInterval;
return assembleFromScans(channelY0, headers, scans,
static_cast<double>(t0) * dx);
}
} // namespace geopro::io::gpr

View File

@ -1,6 +1,7 @@
#ifndef GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP
#define GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP
#include <cstdint>
#include <string>
#include <vector>
@ -23,6 +24,18 @@ geopro::core::GprSurvey assembleGprSurvey(
const std::vector<std::string>& channelIprbPaths,
const std::string& ordPath);
// 只装配道区间 [t0,t1) 的 GprSurvey各通道用 readIprbRange 只读该段,
// 内存只随 slab 大小非全线供流式建体B4 拼接)使用。
//
// 与 assembleGprSurvey 的差异:
// - ntraces = (t1-t0),仅装配 [t0,t1) 道;
// - x0 = t0*dx使 slab 世界 X 与全线对齐B4 拼接靠此),其余标尺/排序/置换同上;
// - t0/t1 越界(t1>总道数 或 t0>t1 或 t0<0)、通道数不符、samples 不一致 抛 std::runtime_error。
geopro::core::GprSurvey assembleGprSurveySlab(
const std::vector<std::string>& channelIprbPaths,
const std::string& ordPath,
std::int64_t t0, std::int64_t t1);
} // namespace geopro::io::gpr
#endif // GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP

View File

@ -65,6 +65,77 @@ TEST(GprSurveyAssembler, ThrowsWhenChannelCountMismatch) {
std::filesystem::remove_all(d);
}
TEST(GprSurveyAssembler, SlabMatchesFullForRange) {
auto d = (std::filesystem::temp_directory_path() / "gpr_asm_slab").string();
std::filesystem::create_directories(d);
// 两通道各 4 道 (lastTrace=3), samples=2。
const char* HDR4 =
"SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n"
"SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n";
writeText(d + "/A.iprh", HDR4);
writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17}); // 4 道
writeText(d + "/B.iprh", HDR4);
writeI16(d + "/B.iprb", {20, 21, 22, 23, 24, 25, 26, 27}); // 4 道
writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); // A->1.0, B->0.0
auto full = assembleGprSurvey({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord");
auto slab = assembleGprSurveySlab({d + "/A.iprb", d + "/B.iprb"}, d + "/x.ord",
1, 3); // 道 [1,3)
EXPECT_EQ(slab.ntraces, 2);
EXPECT_EQ(slab.samples, full.samples);
EXPECT_NEAR(slab.dx, full.dx, 1e-9);
EXPECT_NEAR(slab.dz, full.dz, 1e-9);
EXPECT_NEAR(slab.z0, full.z0, 1e-9);
EXPECT_NEAR(slab.x0, full.x0 + 1 * full.dx, 1e-9); // x0 对齐到 t0
ASSERT_EQ(slab.channelY.size(), full.channelY.size());
for (std::size_t c = 0; c < slab.channelY.size(); ++c) {
EXPECT_NEAR(slab.channelY[c], full.channelY[c], 1e-9);
}
// 值对拍: slab(c,t,s) == full(c, t+1, s)
for (int c = 0; c < static_cast<int>(slab.channelY.size()); ++c)
for (int t = 0; t < slab.ntraces; ++t)
for (int s = 0; s < slab.samples; ++s)
EXPECT_NEAR(slab.at(c, t, s), full.at(c, t + 1, s), 1e-9);
std::filesystem::remove_all(d);
}
TEST(GprSurveyAssembler, SlabOutOfRangeThrows) {
auto d = (std::filesystem::temp_directory_path() / "gpr_asm_slab_oob").string();
std::filesystem::create_directories(d);
const char* HDR4 =
"SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n"
"SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n";
writeText(d + "/A.iprh", HDR4);
writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17}); // 4 道
writeText(d + "/B.iprh", HDR4);
writeI16(d + "/B.iprb", {20, 21, 22, 23, 24, 25, 26, 27}); // 4 道
writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n");
const std::vector<std::string> paths = {d + "/A.iprb", d + "/B.iprb"};
// t1 超总道数 (总道数=4)。
EXPECT_THROW(assembleGprSurveySlab(paths, d + "/x.ord", 2, 5),
std::runtime_error);
// t0 > t1。
EXPECT_THROW(assembleGprSurveySlab(paths, d + "/x.ord", 3, 1),
std::runtime_error);
std::filesystem::remove_all(d);
}
TEST(GprSurveyAssembler, SlabThrowsWhenChannelCountMismatch) {
auto d =
(std::filesystem::temp_directory_path() / "gpr_asm_slab_cm").string();
std::filesystem::create_directories(d);
const char* HDR4 =
"SAMPLES: 2\nLAST TRACE: 3\nCHANNELS: 2\nTIMEWINDOW: 4.0\n"
"SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n";
writeText(d + "/A.iprh", HDR4);
writeI16(d + "/A.iprb", {10, 11, 12, 13, 14, 15, 16, 17});
writeText(d + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); // 2 有效通道
// 只传 1 个 iprb 路径 -> 抛错。
EXPECT_THROW(assembleGprSurveySlab({d + "/A.iprb"}, d + "/x.ord", 1, 3),
std::runtime_error);
std::filesystem::remove_all(d);
}
TEST(GprSurveyAssembler, AlignsTracesToMinimum) {
auto d = (std::filesystem::temp_directory_path() / "gpr_asm_align").string();
std::filesystem::create_directories(d);