173 lines
6.0 KiB
C++
173 lines
6.0 KiB
C++
#include "data/StreamingVolumeBuilder.hpp"
|
||
|
||
#include <gtest/gtest.h>
|
||
|
||
#include <cstdint>
|
||
#include <filesystem>
|
||
#include <fstream>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#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<std::int16_t>& v) {
|
||
std::ofstream f(p, std::ios::binary);
|
||
f.write(reinterpret_cast<const char*>(v.data()),
|
||
static_cast<std::streamsize>(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<std::string> 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<std::int16_t> v(static_cast<std::size_t>(nTraces) * nSamples);
|
||
for (int t = 0; t < nTraces; ++t)
|
||
for (int s = 0; s < nSamples; ++s)
|
||
v[static_cast<std::size_t>(t) * nSamples + s] = static_cast<std::int16_t>(
|
||
(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);
|
||
}
|