diff --git a/src/io/gpr/GprSurveyAssembler.cpp b/src/io/gpr/GprSurveyAssembler.cpp index 041fce5..e9e3c2e 100644 --- a/src/io/gpr/GprSurveyAssembler.cpp +++ b/src/io/gpr/GprSurveyAssembler.cpp @@ -36,35 +36,14 @@ std::string readFileText(const std::string& path) { return ss.str(); } -} // namespace - -geopro::core::GprSurvey assembleGprSurvey( - const std::vector& channelIprbPaths, - const std::string& ordPath) { - // 1. .ord -> 各通道横偏(文件序)。 - const std::vector 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 headers; - std::vector 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& channelY0, + const std::vector& headers, + const std::vector& 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(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 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& channelIprbPaths, + const std::string& ordPath) { + // 1. .ord -> 各通道横偏(文件序)。 + const std::vector 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 headers; + std::vector 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& channelIprbPaths, + const std::string& ordPath, + std::int64_t t0, std::int64_t t1) { + // 1. .ord -> 各通道横偏(文件序)。 + const std::vector 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 headers; + std::vector 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(t0) * dx); +} + } // namespace geopro::io::gpr diff --git a/src/io/gpr/GprSurveyAssembler.hpp b/src/io/gpr/GprSurveyAssembler.hpp index d0da002..400ca22 100644 --- a/src/io/gpr/GprSurveyAssembler.hpp +++ b/src/io/gpr/GprSurveyAssembler.hpp @@ -1,6 +1,7 @@ #ifndef GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP #define GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP +#include #include #include @@ -23,6 +24,18 @@ geopro::core::GprSurvey assembleGprSurvey( const std::vector& 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& channelIprbPaths, + const std::string& ordPath, + std::int64_t t0, std::int64_t t1); + } // namespace geopro::io::gpr #endif // GEOPRO_IO_GPR_GPRSURVEYASSEMBLER_HPP diff --git a/tests/io/gpr/test_gpr_survey_assembler.cpp b/tests/io/gpr/test_gpr_survey_assembler.cpp index a997649..d903852 100644 --- a/tests/io/gpr/test_gpr_survey_assembler.cpp +++ b/tests/io/gpr/test_gpr_survey_assembler.cpp @@ -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(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 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);