160 lines
6.4 KiB
C++
160 lines
6.4 KiB
C++
#ifndef GEOPRO_DATA_STORE_CHUNKEDVOLUMESTORE_HPP
|
||
#define GEOPRO_DATA_STORE_CHUNKEDVOLUMESTORE_HPP
|
||
|
||
#include <array>
|
||
#include <cstdint>
|
||
#include <string>
|
||
#include <utility>
|
||
#include <vector>
|
||
|
||
#include "model/ScalarVolumeI16.hpp" // geopro::core::Quant
|
||
|
||
namespace geopro::core {
|
||
struct BuiltI16; // src/core/algo/GprVolumeBuilder.hpp
|
||
}
|
||
|
||
namespace geopro::data {
|
||
|
||
// 分块存储的 sidecar 元数据(meta.json 反序列化结果,不含逐块索引)。
|
||
struct StoreMeta {
|
||
int nx = 0, ny = 0, nz = 0;
|
||
int brick = 64;
|
||
std::array<double, 3> origin{{0, 0, 0}};
|
||
std::array<double, 3> spacing{{0, 0, 0}};
|
||
geopro::core::Quant quant; // scale/offset
|
||
double vminPhys = 0, vmaxPhys = 0;
|
||
};
|
||
|
||
// GPR 三维体的分块压缩落盘(B/C 共用基座)。
|
||
// 格式:dir/meta.json(几何 + 量化 + 逐块索引)+ dir/data.bin(逐块 qCompress 流)。
|
||
// 块布局与体一致(块内 i 最快、k 最慢);边缘块尺寸 < brick。
|
||
// 偏移/长度全程 64 位(块偏移可能 > 2GB)。
|
||
class ChunkedVolumeStore {
|
||
public:
|
||
// 落盘:dir/meta.json + dir/data.bin。逐块 int16 → qCompress 压缩流,
|
||
// 块索引/偏移/压缩长度记入 meta.json。dir 不存在则创建。
|
||
static void write(const std::string& dir, const geopro::core::BuiltI16& b,
|
||
int brick = 64);
|
||
|
||
// 只读 meta.json(不打开 data.bin)。
|
||
static StoreMeta readMeta(const std::string& dir);
|
||
|
||
// 读 meta + 打开 data.bin。
|
||
explicit ChunkedVolumeStore(const std::string& dir);
|
||
|
||
const StoreMeta& meta() const { return meta_; }
|
||
|
||
// --- level 0 兼容接口(语义不变,= 全分辨率级)---
|
||
int bricksX() const { return bricksX_; }
|
||
int bricksY() const { return bricksY_; }
|
||
int bricksZ() const { return bricksZ_; }
|
||
|
||
// 读单块 → 还原 int16 vector。返回长度 = bw*bh*bd(内部块 = brick³,边缘块更小)。
|
||
// == readBrick(0, bx, by, bz)。
|
||
std::vector<std::int16_t> readBrick(int bx, int by, int bz) const;
|
||
|
||
// --- 金字塔(多分辨率 LOD)---
|
||
// 在已 write 的 store 目录上构建金字塔:level 0=全分辨率(已存),
|
||
// level 1..levels 逐级 2× 降采样(维度 ceil(n/2)),故总级数 = levels+1。
|
||
// 同时为所有 level(含 0)计算并存每块 (min,max)(跳过 kBlank)。结果写回
|
||
// meta.json + 各级数据文件(level 0 复用现有 data.bin,level L 写
|
||
// data_L<level>.bin)。levels<=0 视为无金字塔(仅 level 0)。
|
||
void buildPyramid(int levels);
|
||
|
||
// 总层数(含 level 0);未建金字塔时 = 1。
|
||
int levels() const { return levelCount_; }
|
||
|
||
// 各级块数;bricksX() == bricksX(0) 保持兼容。
|
||
int bricksX(int level) const;
|
||
int bricksY(int level) const;
|
||
int bricksZ(int level) const;
|
||
|
||
// 各级体素维度。
|
||
void dims(int level, int& nx, int& ny, int& nz) const;
|
||
|
||
// 读某级单块 → 还原 int16 vector。level 0 与兼容重载等价。
|
||
std::vector<std::int16_t> readBrick(int level, int bx, int by, int bz) const;
|
||
|
||
// 每块 (min,max),跳过 kBlank;全 blank 块返回 (kBlank,kBlank)。
|
||
// 对未建金字塔的 level 0,惰性读块计算。
|
||
std::pair<std::int16_t, std::int16_t> brickRange(int level, int bx, int by,
|
||
int bz) const;
|
||
|
||
private:
|
||
// 单块在所属级数据文件中的位置、未压缩尺寸与值域。
|
||
struct BrickEntry {
|
||
std::int64_t offset = 0;
|
||
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;
|
||
};
|
||
|
||
// 一个分辨率级:维度 + 块数 + 逐块索引 + 数据文件名。
|
||
struct Level {
|
||
int nx = 0, ny = 0, nz = 0;
|
||
int bx = 0, by = 0, bz = 0; // 块数
|
||
std::string dataFile;
|
||
std::vector<BrickEntry> bricks;
|
||
};
|
||
|
||
int brickIndexAt(const Level& lv, int bx, int by, int bz) const {
|
||
return (bz * lv.by + by) * lv.bx + bx;
|
||
}
|
||
const Level& levelAt(int level) const;
|
||
std::vector<std::int16_t> readBrickFrom(const Level& lv, int bx, int by,
|
||
int bz) const;
|
||
|
||
std::string dir_;
|
||
StoreMeta meta_;
|
||
int bricksX_ = 0, bricksY_ = 0, bricksZ_ = 0; // = level 0 块数(兼容字段)
|
||
std::vector<BrickEntry> bricks_; // = level 0 索引(兼容字段,readBrick(bx,by,bz) 用)
|
||
|
||
int levelCount_ = 1;
|
||
// mutable:brickRange 为 const,但惰性算出值域后需就地缓存(置 hasRange=true)。
|
||
mutable std::vector<Level> levels_; // levels_[0] 即 level 0(与 bricks_ 同源)
|
||
|
||
friend class StreamingVolumeWriter;
|
||
};
|
||
|
||
// 逐块增量写 level0 store(不持整卷)。块写入顺序任意,但每块只写一次。
|
||
// 产出与 ChunkedVolumeStore::write(整卷) 逐 brick + meta 完全一致:data.bin 为
|
||
// 逐块 qCompress 流(按 bz 最慢、bx 最快的固定顺序排布),meta.json 结构同 write,
|
||
// 故 ChunkedVolumeStore(dir)/readBrick 能照常读。偏移/长度全程 64 位。
|
||
class StreamingVolumeWriter {
|
||
public:
|
||
// 用 StoreMeta 定 dims/brick/origin/spacing/quant/vminmax(与 write 一致的元信息)。
|
||
StreamingVolumeWriter(const std::string& dir, const StoreMeta& meta);
|
||
|
||
// 写一块:voxels 为该块体素(大小=bw*bh*bd,块内 i 最快,与 write 同布局)。
|
||
// bx/by/bz 为块索引;体素数不符或同块重复写 → 抛 std::runtime_error。
|
||
void writeBrick(int bx, int by, int bz,
|
||
const std::vector<std::int16_t>& voxels);
|
||
|
||
// 收尾:写 meta.json(含所有已写块的索引)。有缺块 → 抛 std::runtime_error。
|
||
void finalize();
|
||
|
||
private:
|
||
// 单块在 data.bin 中的索引(与 ChunkedVolumeStore::BrickEntry 子集对应)。
|
||
struct Entry {
|
||
std::int64_t offset = 0;
|
||
std::int64_t compressedLen = 0;
|
||
int bw = 0, bh = 0, bd = 0;
|
||
bool written = false;
|
||
};
|
||
|
||
std::string dir_;
|
||
StoreMeta meta_;
|
||
int bricksX_ = 0, bricksY_ = 0, bricksZ_ = 0;
|
||
std::vector<Entry> entries_; // 固定顺序索引(bz 最慢、bx 最快)
|
||
std::int64_t offset_ = 0; // data.bin 当前追加偏移(64 位)
|
||
std::int64_t written_ = 0; // 已写块计数
|
||
bool finalized_ = false;
|
||
};
|
||
|
||
} // namespace geopro::data
|
||
|
||
#endif // GEOPRO_DATA_STORE_CHUNKEDVOLUMESTORE_HPP
|