#include "data/StreamingVolumeBuilder.hpp" #include #include #include #include #include #include #include "core/algo/GprVolumeBuilder.hpp" #include "core/algo/IInterpolator.hpp" // GridSpec #include "data/store/ChunkedVolumeStore.hpp" #include "io/gpr/GprSurveyAssembler.hpp" using namespace geopro::data; namespace { void writeText(const std::string& p, const std::string& s) { std::ofstream f(p); f << s; } void writeI16(const std::string& p, const std::vector& v) { std::ofstream f(p, std::ios::binary); f.write(reinterpret_cast(v.data()), static_cast(v.size() * sizeof(std::int16_t))); } std::string tmpDir(const char* name) { return (std::filesystem::temp_directory_path() / name).string(); } // 写两通道 .iprb/.ord(+.iprh) 到 dir,每通道 nTraces 道、nSamples 采样。 // 值随 (channel,trace,sample) 变化,确保块内容彼此不同(真正区分逐块对拍)。 // 返回各通道 .iprb 路径 + .ord 路径。 struct SurveyFiles { std::vector iprb; std::string ord; }; SurveyFiles makeTwoChannelSurveyFiles(const std::string& dir, int nTraces, int nSamples) { std::filesystem::create_directories(dir); const int lastTrace = nTraces - 1; const std::string hdr = "SAMPLES: " + std::to_string(nSamples) + "\nLAST TRACE: " + std::to_string(lastTrace) + "\nCHANNELS: 2\nTIMEWINDOW: 4.0\n" "SOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.05\n"; auto gen = [&](int chan) { std::vector v(static_cast(nTraces) * nSamples); for (int t = 0; t < nTraces; ++t) for (int s = 0; s < nSamples; ++s) v[static_cast(t) * nSamples + s] = static_cast( (chan * 1000 + t * 7 + s * 3) % 251 - 50); // 含负值 return v; }; writeText(dir + "/A.iprh", hdr); writeI16(dir + "/A.iprb", gen(0)); writeText(dir + "/B.iprh", hdr); writeI16(dir + "/B.iprb", gen(1)); // A->横偏 1.0、B->横偏 0.0(B 的 Y 更小,验证 Y 升序重排不影响一致性)。 writeText(dir + "/x.ord", "0 1.0 -1.5 1\n1 0.0 -1.5 1\n"); return {{dir + "/A.iprb", dir + "/B.iprb"}, dir + "/x.ord"}; } geopro::core::GridSpec makeSpec() { geopro::core::GridSpec spec{}; spec.nx = 200; // > 128 → sliceXBricks=2 时跨多个 slab spec.ny = 5; spec.nz = 8; spec.ox = 0.0; spec.oy = 0.0; spec.oz = 0.0; spec.dx = 0.05; // 与 survey.dx 同步,使网格 X 对齐道 spec.dy = 0.25; spec.dz = 0.2; spec.power = 2.0; spec.maxDist = 2.0; return spec; } } // namespace // 核心验收:流式逐 slab 建体 vs 非流式整卷 buildGprVolume+write,逐块 + meta 一致。 TEST(StreamingVolumeBuilder, MatchesNonStreaming) { const std::string srcDir = tmpDir("svb_src"); std::filesystem::remove_all(srcDir); auto files = makeTwoChannelSurveyFiles(srcDir, /*nTraces*/ 250, /*nSamples*/ 8); const auto spec = makeSpec(); const int brick = 64; const std::string dirA = tmpDir("svb_nsA"); const std::string dirB = tmpDir("svb_strB"); std::filesystem::remove_all(dirA); std::filesystem::remove_all(dirB); // 金标准:全装配 → buildGprVolume → write。 auto full = geopro::io::gpr::assembleGprSurvey(files.iprb, files.ord); auto built = geopro::core::buildGprVolume(full, spec); ChunkedVolumeStore::write(dirA, built, brick); // 流式:sliceXBricks=2 强制多 slab。 buildGprVolumeStreaming(files.iprb, files.ord, spec, dirB, /*sliceXBricks*/ 2); ChunkedVolumeStore A(dirA), B(dirB); // 全 meta 字段一致。 EXPECT_EQ(B.meta().nx, A.meta().nx); EXPECT_EQ(B.meta().ny, A.meta().ny); EXPECT_EQ(B.meta().nz, A.meta().nz); EXPECT_EQ(B.meta().brick, A.meta().brick); EXPECT_EQ(B.meta().origin, A.meta().origin); EXPECT_EQ(B.meta().spacing, A.meta().spacing); EXPECT_EQ(B.meta().quant.scale, A.meta().quant.scale); EXPECT_EQ(B.meta().quant.offset, A.meta().quant.offset); EXPECT_EQ(B.meta().vminPhys, A.meta().vminPhys); EXPECT_EQ(B.meta().vmaxPhys, A.meta().vmaxPhys); // 逐 brick 完全一致。 const int bX = A.bricksX(), bY = A.bricksY(), bZ = A.bricksZ(); EXPECT_EQ(B.bricksX(), bX); EXPECT_EQ(B.bricksY(), bY); EXPECT_EQ(B.bricksZ(), bZ); for (int bz = 0; bz < bZ; ++bz) for (int by = 0; by < bY; ++by) for (int bx = 0; bx < bX; ++bx) EXPECT_EQ(A.readBrick(bx, by, bz), B.readBrick(bx, by, bz)) << "brick mismatch at " << bx << "," << by << "," << bz; std::filesystem::remove_all(srcDir); std::filesystem::remove_all(dirA); std::filesystem::remove_all(dirB); } // 不同 sliceXBricks 都与非流式一致(slab 边界划分不影响结果)。 TEST(StreamingVolumeBuilder, SliceCountInvariant) { const std::string srcDir = tmpDir("svb_inv_src"); std::filesystem::remove_all(srcDir); auto files = makeTwoChannelSurveyFiles(srcDir, /*nTraces*/ 250, /*nSamples*/ 8); const auto spec = makeSpec(); const std::string dirA = tmpDir("svb_inv_A"); std::filesystem::remove_all(dirA); auto full = geopro::io::gpr::assembleGprSurvey(files.iprb, files.ord); auto built = geopro::core::buildGprVolume(full, spec); ChunkedVolumeStore::write(dirA, built, 64); ChunkedVolumeStore A(dirA); for (int slice : {1, 3, 8}) { const std::string dirB = tmpDir("svb_inv_B"); std::filesystem::remove_all(dirB); buildGprVolumeStreaming(files.iprb, files.ord, spec, dirB, slice); ChunkedVolumeStore B(dirB); EXPECT_EQ(B.meta().quant.scale, A.meta().quant.scale) << "slice=" << slice; EXPECT_EQ(B.meta().vminPhys, A.meta().vminPhys) << "slice=" << slice; for (int bz = 0; bz < A.bricksZ(); ++bz) for (int by = 0; by < A.bricksY(); ++by) for (int bx = 0; bx < A.bricksX(); ++bx) EXPECT_EQ(A.readBrick(bx, by, bz), B.readBrick(bx, by, bz)) << "slice=" << slice << " brick " << bx << "," << by << "," << bz; std::filesystem::remove_all(dirB); } std::filesystem::remove_all(srcDir); std::filesystem::remove_all(dirA); }