From c6ff9c2271e09081e5bd7c23fe36571103377d40 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 10:28:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=20int16=20?= =?UTF-8?q?=E9=87=8F=E5=8C=96=E4=BD=93=E7=B1=BB=E5=9E=8B=20ScalarVolumeI16?= =?UTF-8?q?=20+=20Quant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/core/model/ScalarVolumeI16.hpp | 66 +++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/core/test_scalar_volume_i16.cpp | 22 +++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/core/model/ScalarVolumeI16.hpp create mode 100644 tests/core/test_scalar_volume_i16.cpp diff --git a/src/core/model/ScalarVolumeI16.hpp b/src/core/model/ScalarVolumeI16.hpp new file mode 100644 index 0000000..82a4809 --- /dev/null +++ b/src/core/model/ScalarVolumeI16.hpp @@ -0,0 +1,66 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace geopro::core { + +// int16 量化标量体(雷达体专用,内存/显存/磁盘 = double 体的 1/4)。 +// 与 double 的 ScalarVolume 并列,不污染主路径。布局:i 最快、k 最慢(匹配 vtkImageData)。 + +// 物理值 ↔ int16 量化映射。 +struct Quant { + double scale = 1.0; + double offset = 0.0; + + // 物理值→int16:round((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(std::numeric_limits::min()) + 1; + constexpr long kHi = static_cast(std::numeric_limits::max()); + if (q < kLo) q = kLo; + if (q > kHi) q = kHi; + return static_cast(q); + } + + // int16→物理值:q*scale + offset。 + double toPhys(std::int16_t q) const { + return static_cast(q) * scale + offset; + } +}; + +class ScalarVolumeI16 { +public: + // 空值哨兵 → 渲染透明。 + static constexpr std::int16_t kBlank = std::numeric_limits::min(); + + // data_ 填 0(非 kBlank);空值由调用方显式置 kBlank。 + ScalarVolumeI16(int nx, int ny, int nz) + : nx_(nx), ny_(ny), nz_(nz), + data_(static_cast(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& data() { return data_; } + const std::vector& 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(k) * ny_ + j) * nx_ + i; + } + + int nx_, ny_, nz_; + std::vector data_; +}; + +} // namespace geopro::core diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b381f8..b6f4629 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/core/test_scalar_volume_i16.cpp b/tests/core/test_scalar_volume_i16.cpp new file mode 100644 index 0000000..4adcaa9 --- /dev/null +++ b/tests/core/test_scalar_volume_i16.cpp @@ -0,0 +1,22 @@ +#include "core/model/ScalarVolumeI16.hpp" +#include +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); +}