feat/vtk-3d-view #7
|
|
@ -194,7 +194,9 @@ ChunkedVolumeStore::ChunkedVolumeStore(const std::string& dir) : dir_(dir) {
|
|||
be.bw = e.at("bw").get<int>();
|
||||
be.bh = e.at("bh").get<int>();
|
||||
be.bd = e.at("bd").get<int>();
|
||||
// 老 store 无 min/max;缺失时置 0(brickRange(0,...) 会惰性算)。
|
||||
// hasRange 显式标志:老 store 无此字段 → false(brickRange 走惰性算)。
|
||||
// 若有 min/max 字段则视为已算(兼容 buildPyramid 之前写的、只带 min/max 的 meta)。
|
||||
be.hasRange = e.value("hasRange", e.contains("min") && e.contains("max"));
|
||||
be.vmin = e.value("min", static_cast<std::int16_t>(0));
|
||||
be.vmax = e.value("max", static_cast<std::int16_t>(0));
|
||||
bricks_.push_back(be);
|
||||
|
|
@ -236,6 +238,8 @@ ChunkedVolumeStore::ChunkedVolumeStore(const std::string& dir) : dir_(dir) {
|
|||
be.bw = e.at("bw").get<int>();
|
||||
be.bh = e.at("bh").get<int>();
|
||||
be.bd = e.at("bd").get<int>();
|
||||
// 降采样级由 buildPyramid 写入,恒带 min/max → hasRange 默认 true。
|
||||
be.hasRange = e.value("hasRange", true);
|
||||
be.vmin = e.value("min", kBlank);
|
||||
be.vmax = e.value("max", kBlank);
|
||||
lv.bricks.push_back(be);
|
||||
|
|
@ -249,10 +253,13 @@ ChunkedVolumeStore::ChunkedVolumeStore(const std::string& dir) : dir_(dir) {
|
|||
for (std::size_t i = 0; i < jb0.size(); ++i) {
|
||||
const std::int16_t mn = jb0[i].value("min", kBlank);
|
||||
const std::int16_t mx = jb0[i].value("max", kBlank);
|
||||
const bool hr = jb0[i].value("hasRange", jb0[i].contains("min"));
|
||||
levels_[0].bricks[i].vmin = mn;
|
||||
levels_[0].bricks[i].vmax = mx;
|
||||
levels_[0].bricks[i].hasRange = hr;
|
||||
bricks_[i].vmin = mn;
|
||||
bricks_[i].vmax = mx;
|
||||
bricks_[i].hasRange = hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,17 +329,26 @@ std::vector<std::int16_t> ChunkedVolumeStore::readBrick(int level, int bx,
|
|||
|
||||
std::pair<std::int16_t, std::int16_t> ChunkedVolumeStore::brickRange(
|
||||
int level, int bx, int by, int bz) const {
|
||||
const Level& lv = levelAt(level);
|
||||
if (level < 0 || level >= static_cast<int>(levels_.size())) {
|
||||
throw std::out_of_range("ChunkedVolumeStore: level out of range");
|
||||
}
|
||||
Level& lv = levels_[static_cast<std::size_t>(level)]; // mutable:可缓存惰性结果
|
||||
if (bx < 0 || by < 0 || bz < 0 || bx >= lv.bx || by >= lv.by || bz >= lv.bz) {
|
||||
throw std::out_of_range("ChunkedVolumeStore::brickRange: brick index out of range");
|
||||
}
|
||||
const BrickEntry& be =
|
||||
BrickEntry& be =
|
||||
lv.bricks.at(static_cast<std::size_t>(brickIndexAt(lv, bx, by, bz)));
|
||||
// level 0 老 store 无存储 min/max(vmin==vmax==0 哨兵):惰性读块计算。
|
||||
if (level == 0 && be.vmin == 0 && be.vmax == 0) {
|
||||
return computeRange(readBrickFrom(lv, bx, by, bz));
|
||||
}
|
||||
// 已算(buildPyramid 时算过、或之前惰性算并缓存)→ 直接返回。
|
||||
// 注意:用 hasRange 显式标志而非 (0,0) 哨兵——(0,0) 是合法值域,且 kBlank!=0。
|
||||
if (be.hasRange) {
|
||||
return {be.vmin, be.vmax};
|
||||
}
|
||||
// 未算(老 store level 0):惰性读块计算,并就地缓存 + 置 hasRange。
|
||||
const auto rng = computeRange(readBrickFrom(lv, bx, by, bz));
|
||||
be.vmin = rng.first;
|
||||
be.vmax = rng.second;
|
||||
be.hasRange = true;
|
||||
return rng;
|
||||
}
|
||||
|
||||
void ChunkedVolumeStore::buildPyramid(int levels) {
|
||||
|
|
@ -467,6 +483,7 @@ void ChunkedVolumeStore::buildPyramid(int levels) {
|
|||
be.bd = bd;
|
||||
be.vmin = rng.first;
|
||||
be.vmax = rng.second;
|
||||
be.hasRange = true; // buildPyramid 现算现存,值域确定。
|
||||
lv.bricks.push_back(be);
|
||||
offset += clen;
|
||||
}
|
||||
|
|
@ -496,6 +513,7 @@ void ChunkedVolumeStore::buildPyramid(int levels) {
|
|||
for (std::size_t i = 0; i < jb.size(); ++i) {
|
||||
jb[i]["min"] = levels_[0].bricks[i].vmin;
|
||||
jb[i]["max"] = levels_[0].bricks[i].vmax;
|
||||
jb[i]["hasRange"] = levels_[0].bricks[i].hasRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -510,7 +528,8 @@ void ChunkedVolumeStore::buildPyramid(int levels) {
|
|||
{"bh", be.bh},
|
||||
{"bd", be.bd},
|
||||
{"min", be.vmin},
|
||||
{"max", be.vmax}});
|
||||
{"max", be.vmax},
|
||||
{"hasRange", be.hasRange}});
|
||||
}
|
||||
jlevels.push_back(json{{"nx", lv.nx},
|
||||
{"ny", lv.ny},
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ class ChunkedVolumeStore {
|
|||
std::int64_t compressedLen = 0;
|
||||
int bw = 0, bh = 0, bd = 0;
|
||||
std::int16_t vmin = 0, vmax = 0; // 块内 (min,max),跳过 kBlank;全 blank=(kBlank,kBlank)
|
||||
// 显式「值域已算」标志:替代 (0,0) 哨兵。(0,0) 是合法值域,不能当未计算用。
|
||||
// false → brickRange 惰性读块计算(并缓存);true → 直接返回 (vmin,vmax)。
|
||||
bool hasRange = false;
|
||||
};
|
||||
|
||||
// 一个分辨率级:维度 + 块数 + 逐块索引 + 数据文件名。
|
||||
|
|
@ -110,7 +113,8 @@ class ChunkedVolumeStore {
|
|||
std::vector<BrickEntry> bricks_; // = level 0 索引(兼容字段,readBrick(bx,by,bz) 用)
|
||||
|
||||
int levelCount_ = 1;
|
||||
std::vector<Level> levels_; // levels_[0] 即 level 0(与 bricks_ 同源)
|
||||
// mutable:brickRange 为 const,但惰性算出值域后需就地缓存(置 hasRange=true)。
|
||||
mutable std::vector<Level> levels_; // levels_[0] 即 level 0(与 bricks_ 同源)
|
||||
};
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
|
|||
|
|
@ -98,6 +98,52 @@ TEST(Pyramid, AllBlankBrickRange) {
|
|||
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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue