fix(store): brickRange 用 hasRange 标志替代 (0,0) 哨兵
(0,0) 是合法值域(真实全零块,kBlank=INT16_MIN 非 0),旧实现用 (vmin==0&&vmax==0) 当未计算哨兵会误判,导致全零块每次 brickRange 都无谓解压重算,且 buildPyramid 后仍走惰性。 - BrickEntry 加 bool hasRange 显式标志 - brickRange: hasRange 真→直接返回;假→惰性算并就地缓存(mutable levels_) - meta.json 序列化/反序列化带 hasRange(老 store 缺字段→false,惰性兼容) - buildPyramid 回填值域时一并置 hasRange=true - 补测试:真实全零块 brickRange 返回 (0,0) 不退化(金字塔/老 store 两路)
This commit is contained in:
parent
c21226a3d7
commit
86e2b6b8a8
|
|
@ -194,7 +194,9 @@ ChunkedVolumeStore::ChunkedVolumeStore(const std::string& dir) : dir_(dir) {
|
||||||
be.bw = e.at("bw").get<int>();
|
be.bw = e.at("bw").get<int>();
|
||||||
be.bh = e.at("bh").get<int>();
|
be.bh = e.at("bh").get<int>();
|
||||||
be.bd = e.at("bd").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.vmin = e.value("min", static_cast<std::int16_t>(0));
|
||||||
be.vmax = e.value("max", static_cast<std::int16_t>(0));
|
be.vmax = e.value("max", static_cast<std::int16_t>(0));
|
||||||
bricks_.push_back(be);
|
bricks_.push_back(be);
|
||||||
|
|
@ -236,6 +238,8 @@ ChunkedVolumeStore::ChunkedVolumeStore(const std::string& dir) : dir_(dir) {
|
||||||
be.bw = e.at("bw").get<int>();
|
be.bw = e.at("bw").get<int>();
|
||||||
be.bh = e.at("bh").get<int>();
|
be.bh = e.at("bh").get<int>();
|
||||||
be.bd = e.at("bd").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.vmin = e.value("min", kBlank);
|
||||||
be.vmax = e.value("max", kBlank);
|
be.vmax = e.value("max", kBlank);
|
||||||
lv.bricks.push_back(be);
|
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) {
|
for (std::size_t i = 0; i < jb0.size(); ++i) {
|
||||||
const std::int16_t mn = jb0[i].value("min", kBlank);
|
const std::int16_t mn = jb0[i].value("min", kBlank);
|
||||||
const std::int16_t mx = jb0[i].value("max", 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].vmin = mn;
|
||||||
levels_[0].bricks[i].vmax = mx;
|
levels_[0].bricks[i].vmax = mx;
|
||||||
|
levels_[0].bricks[i].hasRange = hr;
|
||||||
bricks_[i].vmin = mn;
|
bricks_[i].vmin = mn;
|
||||||
bricks_[i].vmax = mx;
|
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(
|
std::pair<std::int16_t, std::int16_t> ChunkedVolumeStore::brickRange(
|
||||||
int level, int bx, int by, int bz) const {
|
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) {
|
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");
|
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)));
|
lv.bricks.at(static_cast<std::size_t>(brickIndexAt(lv, bx, by, bz)));
|
||||||
// level 0 老 store 无存储 min/max(vmin==vmax==0 哨兵):惰性读块计算。
|
// 已算(buildPyramid 时算过、或之前惰性算并缓存)→ 直接返回。
|
||||||
if (level == 0 && be.vmin == 0 && be.vmax == 0) {
|
// 注意:用 hasRange 显式标志而非 (0,0) 哨兵——(0,0) 是合法值域,且 kBlank!=0。
|
||||||
return computeRange(readBrickFrom(lv, bx, by, bz));
|
if (be.hasRange) {
|
||||||
}
|
|
||||||
return {be.vmin, be.vmax};
|
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) {
|
void ChunkedVolumeStore::buildPyramid(int levels) {
|
||||||
|
|
@ -467,6 +483,7 @@ void ChunkedVolumeStore::buildPyramid(int levels) {
|
||||||
be.bd = bd;
|
be.bd = bd;
|
||||||
be.vmin = rng.first;
|
be.vmin = rng.first;
|
||||||
be.vmax = rng.second;
|
be.vmax = rng.second;
|
||||||
|
be.hasRange = true; // buildPyramid 现算现存,值域确定。
|
||||||
lv.bricks.push_back(be);
|
lv.bricks.push_back(be);
|
||||||
offset += clen;
|
offset += clen;
|
||||||
}
|
}
|
||||||
|
|
@ -496,6 +513,7 @@ void ChunkedVolumeStore::buildPyramid(int levels) {
|
||||||
for (std::size_t i = 0; i < jb.size(); ++i) {
|
for (std::size_t i = 0; i < jb.size(); ++i) {
|
||||||
jb[i]["min"] = levels_[0].bricks[i].vmin;
|
jb[i]["min"] = levels_[0].bricks[i].vmin;
|
||||||
jb[i]["max"] = levels_[0].bricks[i].vmax;
|
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},
|
{"bh", be.bh},
|
||||||
{"bd", be.bd},
|
{"bd", be.bd},
|
||||||
{"min", be.vmin},
|
{"min", be.vmin},
|
||||||
{"max", be.vmax}});
|
{"max", be.vmax},
|
||||||
|
{"hasRange", be.hasRange}});
|
||||||
}
|
}
|
||||||
jlevels.push_back(json{{"nx", lv.nx},
|
jlevels.push_back(json{{"nx", lv.nx},
|
||||||
{"ny", lv.ny},
|
{"ny", lv.ny},
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@ class ChunkedVolumeStore {
|
||||||
std::int64_t compressedLen = 0;
|
std::int64_t compressedLen = 0;
|
||||||
int bw = 0, bh = 0, bd = 0;
|
int bw = 0, bh = 0, bd = 0;
|
||||||
std::int16_t vmin = 0, vmax = 0; // 块内 (min,max),跳过 kBlank;全 blank=(kBlank,kBlank)
|
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) 用)
|
std::vector<BrickEntry> bricks_; // = level 0 索引(兼容字段,readBrick(bx,by,bz) 用)
|
||||||
|
|
||||||
int levelCount_ = 1;
|
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
|
} // namespace geopro::data
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,52 @@ TEST(Pyramid, AllBlankBrickRange) {
|
||||||
std::filesystem::remove_all(dir);
|
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,...) 仍可惰性算。
|
// 老 store(未 buildPyramid):levels()==1;brickRange(0,...) 仍可惰性算。
|
||||||
TEST(Pyramid, LegacyStoreNoPyramidLevelsIsOne) {
|
TEST(Pyramid, LegacyStoreNoPyramidLevelsIsOne) {
|
||||||
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_legacy").string();
|
auto dir = (std::filesystem::temp_directory_path() / "gpr_pyr_legacy").string();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue