feat(core): 新增 int16 量化体类型 ScalarVolumeI16 + Quant

GPR 三维体地基:int16 量化标量体,内存/显存/磁盘为 double 体的 1/4。
- Quant: 物理值↔int16 映射,toQ 下钳保留 INT16_MIN 给 kBlank 哨兵
- ScalarVolumeI16: 与 double ScalarVolume 并列,i 最快 k 最慢布局
- idx(i,j,k) 64 位计算(整卷可达约 96 亿体素,防 int 溢出)
- header-only,纯 C++17,零 Qt/VTK
This commit is contained in:
gaozheng 2026-06-23 10:28:40 +08:00
parent 0bbed9c0c3
commit c6ff9c2271
3 changed files with 89 additions and 0 deletions

View File

@ -0,0 +1,66 @@
#pragma once
#include <cstdint>
#include <vector>
#include <limits>
#include <cmath>
#include <cstddef>
namespace geopro::core {
// int16 量化标量体(雷达体专用,内存/显存/磁盘 = double 体的 1/4
// 与 double 的 ScalarVolume 并列不污染主路径。布局i 最快、k 最慢(匹配 vtkImageData
// 物理值 ↔ int16 量化映射。
struct Quant {
double scale = 1.0;
double offset = 0.0;
// 物理值→int16round((v-offset)/scale),钳到 [INT16_MIN+1, INT16_MAX]。
// INT16_MIN 保留给 ScalarVolumeI16::kBlank空值哨兵不可被正常值占用。
std::int16_t toQ(double v) const {
long q = std::lround((v - offset) / scale);
constexpr long kLo = static_cast<long>(std::numeric_limits<std::int16_t>::min()) + 1;
constexpr long kHi = static_cast<long>(std::numeric_limits<std::int16_t>::max());
if (q < kLo) q = kLo;
if (q > kHi) q = kHi;
return static_cast<std::int16_t>(q);
}
// int16→物理值q*scale + offset。
double toPhys(std::int16_t q) const {
return static_cast<double>(q) * scale + offset;
}
};
class ScalarVolumeI16 {
public:
// 空值哨兵 → 渲染透明。
static constexpr std::int16_t kBlank = std::numeric_limits<std::int16_t>::min();
// data_ 填 0非 kBlank空值由调用方显式置 kBlank。
ScalarVolumeI16(int nx, int ny, int nz)
: nx_(nx), ny_(ny), nz_(nz),
data_(static_cast<std::size_t>(nx) * ny * nz, 0) {}
int nx() const { return nx_; }
int ny() const { return ny_; }
int nz() const { return nz_; }
std::int16_t& at(int i, int j, int k) { return data_[idx(i, j, k)]; }
std::int16_t at(int i, int j, int k) const { return data_[idx(i, j, k)]; }
std::vector<std::int16_t>& data() { return data_; }
const std::vector<std::int16_t>& data() const { return data_; }
private:
// i 最快、j 次之、k 最慢 ⇒ ((k*ny + j)*nx + i)。
// 64 位域计算:整卷体素数 nx*ny*nz 可达约 96 亿(>2^31),先转 size_t 再乘以防溢出。
std::size_t idx(int i, int j, int k) const {
return (static_cast<std::size_t>(k) * ny_ + j) * nx_ + i;
}
int nx_, ny_, nz_;
std::vector<std::int16_t> data_;
};
} // namespace geopro::core

View File

@ -32,6 +32,7 @@ target_sources(geopro_tests PRIVATE core/test_idw.cpp)
target_sources(geopro_tests PRIVATE core/test_crs_transform.cpp)
target_sources(geopro_tests PRIVATE core/test_model_data.cpp)
target_sources(geopro_tests PRIVATE core/test_geo_frame.cpp)
target_sources(geopro_tests PRIVATE core/test_scalar_volume_i16.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_core)
target_sources(geopro_tests PRIVATE data/test_parsers.cpp)

View File

@ -0,0 +1,22 @@
#include "core/model/ScalarVolumeI16.hpp"
#include <gtest/gtest.h>
using namespace geopro::core;
TEST(ScalarVolumeI16, QuantRoundTrip) {
Quant q{0.5, -10.0};
EXPECT_EQ(q.toQ(-10.0), 0);
EXPECT_NEAR(q.toPhys(q.toQ(3.0)), 3.0, 0.25); // 半个量化步内
}
TEST(ScalarVolumeI16, QuantClampReservesBlank) {
Quant q{1.0, 0.0};
EXPECT_GT(q.toQ(-1e9), ScalarVolumeI16::kBlank); // 下钳不撞 kBlank
EXPECT_EQ(q.toQ(1e9), 32767); // 上钳到 INT16_MAX
}
TEST(ScalarVolumeI16, IndexLayout) {
ScalarVolumeI16 v(2, 2, 2);
v.at(1, 0, 1) = 7;
EXPECT_EQ(v.data()[(1 * 2 + 0) * 2 + 1], 7); // ((k*ny+j)*nx+i)=((1*2+0)*2+1)=5
EXPECT_EQ(v.at(1, 0, 1), 7);
}