geopro/tests/data/store/test_pyramid.cpp

160 lines
6.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "data/store/ChunkedVolumeStore.hpp"
#include "core/algo/GprVolumeBuilder.hpp"
#include <gtest/gtest.h>
#include <filesystem>
#include <cstdint>
using namespace geopro::data;
namespace {
geopro::core::BuiltI16 makeRamp(int n) {
geopro::core::BuiltI16 b;
b.vol = geopro::core::ScalarVolumeI16(n, n, n);
for (int k = 0; k < n; k++)
for (int j = 0; j < n; j++)
for (int i = 0; i < n; i++) b.vol.at(i, j, k) = (short)(i); // 沿 x 斜坡
b.quant = {1.0, 0.0};
b.origin = {{0, 0, 0}};
b.spacing = {{1, 1, 1}};
b.vminPhys = 0;
b.vmaxPhys = n;
return b;
}
} // namespace
TEST(Pyramid, BuildsHalfResLevelsAndRanges) {
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr").string();
std::filesystem::remove_all(dir);
ChunkedVolumeStore::write(dir, makeRamp(64), 32);
ChunkedVolumeStore s(dir);
s.buildPyramid(2); // level 0(64³),1(32³),2(16³)
EXPECT_GE(s.levels(), 3);
int nx, ny, nz;
s.dims(1, nx, ny, nz);
EXPECT_EQ(nx, 32);
EXPECT_EQ(ny, 32);
EXPECT_EQ(nz, 32);
// level0 块0 的 range:x 斜坡 0..31(brick32) → min=0,max=31
auto r0 = s.brickRange(0, 0, 0, 0);
EXPECT_EQ(r0.first, 0);
EXPECT_EQ(r0.second, 31);
auto blk1 = s.readBrick(1, 0, 0, 0); // level1 块,降采样后非空
EXPECT_FALSE(blk1.empty());
std::filesystem::remove_all(dir);
}
TEST(Pyramid, Level0ReadCompatUnchanged) {
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_compat").string();
std::filesystem::remove_all(dir);
ChunkedVolumeStore::write(dir, makeRamp(64), 32);
ChunkedVolumeStore s(dir);
s.buildPyramid(1);
EXPECT_EQ(s.readBrick(0, 0, 0), s.readBrick(0, 0, 0, 0)); // 兼容重载等价
std::filesystem::remove_all(dir);
}
// 降采样语义:level1(0,0,0) 由 level0 每 2×2×2 平均得到。
// x 斜坡:level1 体素 i 来自 level0 的 (2i, 2i+1) 平均 = round((2i + 2i+1)/2) = 2i(取整向偶或截断,
// 这里两值差 1,平均 2i+0.5,round→2i+1 或 2i 取决实现;仅校验单调与范围)。
TEST(Pyramid, DownsampledRangeWithinSource) {
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_ds").string();
std::filesystem::remove_all(dir);
ChunkedVolumeStore::write(dir, makeRamp(64), 32);
ChunkedVolumeStore s(dir);
s.buildPyramid(2);
// level1 维度 32³;以 brick32 仍是 1 块(覆盖 i=0..31 → 源 i=0..63)。
int nx, ny, nz;
s.dims(1, nx, ny, nz);
EXPECT_EQ(nx, 32);
auto r1 = s.brickRange(1, 0, 0, 0);
// 降采样值落在源范围 [0,63] 内,且块覆盖全 x → min≈0, max≈63 附近(round 后)。
EXPECT_GE(r1.first, 0);
EXPECT_LE(r1.second, 63);
EXPECT_LT(r1.first, r1.second); // 斜坡降采样后仍有跨度
// level1 块尺寸 = 32³。
auto blk1 = s.readBrick(1, 0, 0, 0);
EXPECT_EQ(blk1.size(), 32u * 32 * 32);
std::filesystem::remove_all(dir);
}
// 全 blank 块 → range 记 (kBlank,kBlank),降采样后该区域仍 blank。
TEST(Pyramid, AllBlankBrickRange) {
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_blank").string();
std::filesystem::remove_all(dir);
geopro::core::BuiltI16 b;
b.vol = geopro::core::ScalarVolumeI16(64, 64, 64);
for (auto& v : b.vol.data()) v = geopro::core::ScalarVolumeI16::kBlank;
b.quant = {1.0, 0.0};
b.origin = {{0, 0, 0}};
b.spacing = {{1, 1, 1}};
b.vminPhys = 0;
b.vmaxPhys = 0;
ChunkedVolumeStore::write(dir, b, 32);
ChunkedVolumeStore s(dir);
s.buildPyramid(2);
auto r0 = s.brickRange(0, 0, 0, 0);
EXPECT_EQ(r0.first, geopro::core::ScalarVolumeI16::kBlank);
EXPECT_EQ(r0.second, geopro::core::ScalarVolumeI16::kBlank);
auto r1 = s.brickRange(1, 0, 0, 0);
EXPECT_EQ(r1.first, geopro::core::ScalarVolumeI16::kBlank);
std::filesystem::remove_all(dir);
}
// 真实全零块(非 blank):brickRange 返回 (0,0) 且不退化为惰性。
// (0,0) 是合法值域,旧实现用 (vmin==0&&vmax==0) 当「未计算」哨兵会误判;
// hasRange 标志修正后:buildPyramid 算出的全零块 hasRange=true,返回 (0,0)。
TEST(Pyramid, RealAllZeroBrickRangeIsZeroZeroNotDegenerate) {
auto dir =
(std::filesystem::temp_directory_path() / "gpr_pyr_zero").string();
std::filesystem::remove_all(dir);
geopro::core::BuiltI16 b;
b.vol = geopro::core::ScalarVolumeI16(64, 64, 64);
for (auto& v : b.vol.data()) v = 0; // 真实 0(非 kBlank)
b.quant = {1.0, 0.0};
b.origin = {{0, 0, 0}};
b.spacing = {{1, 1, 1}};
b.vminPhys = 0;
b.vmaxPhys = 0;
ChunkedVolumeStore::write(dir, b, 32);
ChunkedVolumeStore s(dir);
s.buildPyramid(1);
auto r0 = s.brickRange(0, 0, 0, 0);
EXPECT_EQ(r0.first, 0);
EXPECT_EQ(r0.second, 0); // 合法 (0,0),不退化、不是 kBlank
EXPECT_NE(r0.first, geopro::core::ScalarVolumeI16::kBlank);
std::filesystem::remove_all(dir);
}
// 老 store 全零块(无金字塔):首次 brickRange 惰性算出 (0,0),不无限退化。
TEST(Pyramid, LegacyRealAllZeroBrickRangeIsZeroZero) {
auto dir =
(std::filesystem::temp_directory_path() / "gpr_pyr_zero_legacy").string();
std::filesystem::remove_all(dir);
geopro::core::BuiltI16 b;
b.vol = geopro::core::ScalarVolumeI16(64, 64, 64);
for (auto& v : b.vol.data()) v = 0;
b.quant = {1.0, 0.0};
b.origin = {{0, 0, 0}};
b.spacing = {{1, 1, 1}};
b.vminPhys = 0;
b.vmaxPhys = 0;
ChunkedVolumeStore::write(dir, b, 32);
ChunkedVolumeStore s(dir); // 未 buildPyramid:老 store,块无 hasRange
auto r0 = s.brickRange(0, 0, 0, 0); // 惰性算 → (0,0)
EXPECT_EQ(r0.first, 0);
EXPECT_EQ(r0.second, 0);
std::filesystem::remove_all(dir);
}
// 老 store(未 buildPyramid):levels()==1;brickRange(0,...) 仍可惰性算。
TEST(Pyramid, LegacyStoreNoPyramidLevelsIsOne) {
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_legacy").string();
std::filesystem::remove_all(dir);
ChunkedVolumeStore::write(dir, makeRamp(64), 32);
ChunkedVolumeStore s(dir); // 未调用 buildPyramid
EXPECT_EQ(s.levels(), 1);
EXPECT_EQ(s.bricksX(), s.bricksX(0));
auto r0 = s.brickRange(0, 0, 0, 0); // 惰性算
EXPECT_EQ(r0.first, 0);
EXPECT_EQ(r0.second, 31);
std::filesystem::remove_all(dir);
}