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:
parent
9d3b103e32
commit
81e18ebd2d
|
|
@ -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 已是待装配的道段(全线或 slab);ntraces 取各通道段道数最小值(对齐);
|
||||
// x0 由调用方给出(全线=0,slab=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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue