Skip to main content

volren_core/volume/
dyn_volume.rs

1//! Type-erased volume enum for storing any supported scalar type.
2
3use super::{Volume, VolumeInfo};
4use glam::{DMat3, DVec3, UVec3};
5
6/// A volume whose scalar type is determined at runtime.
7///
8/// This enum lets a consumer store any supported scalar flavour behind one
9/// handle without carrying `T` as a generic parameter.
10///
11/// # VTK Equivalent
12/// `vtkImageData::GetScalarType()` — a runtime tag rather than a compile-time type.
13#[derive(Debug, Clone)]
14#[non_exhaustive]
15pub enum DynVolume {
16    /// Unsigned 8-bit integer voxels.
17    U8(Volume<u8>),
18    /// Signed 8-bit integer voxels.
19    I8(Volume<i8>),
20    /// Unsigned 16-bit integer voxels.
21    U16(Volume<u16>),
22    /// Signed 16-bit integer voxels (most common for CT Hounsfield units).
23    I16(Volume<i16>),
24    /// Unsigned 32-bit integer voxels.
25    U32(Volume<u32>),
26    /// Signed 32-bit integer voxels.
27    I32(Volume<i32>),
28    /// 32-bit floating-point voxels.
29    F32(Volume<f32>),
30    /// 64-bit floating-point voxels.
31    F64(Volume<f64>),
32}
33
34impl DynVolume {
35    /// Raw byte slice of the underlying voxel data, suitable for GPU upload.
36    #[must_use]
37    pub fn as_bytes(&self) -> &[u8] {
38        match self {
39            Self::U8(v) => v.as_bytes(),
40            Self::I8(v) => v.as_bytes(),
41            Self::U16(v) => v.as_bytes(),
42            Self::I16(v) => v.as_bytes(),
43            Self::U32(v) => v.as_bytes(),
44            Self::I32(v) => v.as_bytes(),
45            Self::F32(v) => v.as_bytes(),
46            Self::F64(v) => v.as_bytes(),
47        }
48    }
49
50    /// The (min, max) scalar range, normalised to `f64`.
51    #[must_use]
52    pub fn scalar_range(&self) -> (f64, f64) {
53        match self {
54            Self::U8(v) => v.scalar_range(),
55            Self::I8(v) => v.scalar_range(),
56            Self::U16(v) => v.scalar_range(),
57            Self::I16(v) => v.scalar_range(),
58            Self::U32(v) => v.scalar_range(),
59            Self::I32(v) => v.scalar_range(),
60            Self::F32(v) => v.scalar_range(),
61            Self::F64(v) => v.scalar_range(),
62        }
63    }
64
65    /// Sample with trilinear interpolation at a continuous voxel index.
66    #[must_use]
67    pub fn sample_linear(&self, ijk: DVec3) -> Option<f64> {
68        match self {
69            Self::U8(v) => v.sample_linear(ijk),
70            Self::I8(v) => v.sample_linear(ijk),
71            Self::U16(v) => v.sample_linear(ijk),
72            Self::I16(v) => v.sample_linear(ijk),
73            Self::U32(v) => v.sample_linear(ijk),
74            Self::I32(v) => v.sample_linear(ijk),
75            Self::F32(v) => v.sample_linear(ijk),
76            Self::F64(v) => v.sample_linear(ijk),
77        }
78    }
79
80    /// Number of bytes per scalar component.
81    #[must_use]
82    pub fn bytes_per_component(&self) -> usize {
83        match self {
84            Self::U8(_) | Self::I8(_) => 1,
85            Self::U16(_) | Self::I16(_) => 2,
86            Self::U32(_) | Self::I32(_) | Self::F32(_) => 4,
87            Self::F64(_) => 8,
88        }
89    }
90}
91
92impl VolumeInfo for DynVolume {
93    fn dimensions(&self) -> UVec3 {
94        match self {
95            Self::U8(v) => v.dimensions(),
96            Self::I8(v) => v.dimensions(),
97            Self::U16(v) => v.dimensions(),
98            Self::I16(v) => v.dimensions(),
99            Self::U32(v) => v.dimensions(),
100            Self::I32(v) => v.dimensions(),
101            Self::F32(v) => v.dimensions(),
102            Self::F64(v) => v.dimensions(),
103        }
104    }
105    fn spacing(&self) -> DVec3 {
106        match self {
107            Self::U8(v) => v.spacing(),
108            Self::I8(v) => v.spacing(),
109            Self::U16(v) => v.spacing(),
110            Self::I16(v) => v.spacing(),
111            Self::U32(v) => v.spacing(),
112            Self::I32(v) => v.spacing(),
113            Self::F32(v) => v.spacing(),
114            Self::F64(v) => v.spacing(),
115        }
116    }
117    fn origin(&self) -> DVec3 {
118        match self {
119            Self::U8(v) => v.origin(),
120            Self::I8(v) => v.origin(),
121            Self::U16(v) => v.origin(),
122            Self::I16(v) => v.origin(),
123            Self::U32(v) => v.origin(),
124            Self::I32(v) => v.origin(),
125            Self::F32(v) => v.origin(),
126            Self::F64(v) => v.origin(),
127        }
128    }
129    fn direction(&self) -> DMat3 {
130        match self {
131            Self::U8(v) => v.direction(),
132            Self::I8(v) => v.direction(),
133            Self::U16(v) => v.direction(),
134            Self::I16(v) => v.direction(),
135            Self::U32(v) => v.direction(),
136            Self::I32(v) => v.direction(),
137            Self::F32(v) => v.direction(),
138            Self::F64(v) => v.direction(),
139        }
140    }
141    fn components(&self) -> u32 {
142        match self {
143            Self::U8(v) => v.components(),
144            Self::I8(v) => v.components(),
145            Self::U16(v) => v.components(),
146            Self::I16(v) => v.components(),
147            Self::U32(v) => v.components(),
148            Self::I32(v) => v.components(),
149            Self::F32(v) => v.components(),
150            Self::F64(v) => v.components(),
151        }
152    }
153}
154
155/// Helper macro to convert a `Volume<T>` into the matching `DynVolume` variant.
156macro_rules! impl_from_volume {
157    ($T:ty, $Variant:ident) => {
158        impl From<Volume<$T>> for DynVolume {
159            fn from(v: Volume<$T>) -> Self {
160                Self::$Variant(v)
161            }
162        }
163    };
164}
165
166impl_from_volume!(u8, U8);
167impl_from_volume!(i8, I8);
168impl_from_volume!(u16, U16);
169impl_from_volume!(i16, I16);
170impl_from_volume!(u32, U32);
171impl_from_volume!(i32, I32);
172impl_from_volume!(f32, F32);
173impl_from_volume!(f64, F64);
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use glam::{DMat3, DVec3, UVec3};
179
180    fn make_i16_volume() -> DynVolume {
181        let data: Vec<i16> = (-4i16..4i16).collect();
182        let vol = Volume::from_data(
183            data,
184            UVec3::new(2, 2, 2),
185            DVec3::ONE,
186            DVec3::ZERO,
187            DMat3::IDENTITY,
188            1,
189        )
190        .unwrap();
191        DynVolume::I16(vol)
192    }
193
194    #[test]
195    fn dyn_scalar_range() {
196        let dv = make_i16_volume();
197        let (lo, hi) = dv.scalar_range();
198        approx::assert_abs_diff_eq!(lo, -4.0, epsilon = 1e-10);
199        approx::assert_abs_diff_eq!(hi, 3.0, epsilon = 1e-10);
200    }
201
202    #[test]
203    fn dyn_dimensions() {
204        let dv = make_i16_volume();
205        assert_eq!(dv.dimensions(), UVec3::new(2, 2, 2));
206    }
207
208    #[test]
209    fn dyn_bytes_per_component() {
210        assert_eq!(make_i16_volume().bytes_per_component(), 2);
211    }
212
213    #[test]
214    fn from_volume_u8() {
215        let v = Volume::<u8>::from_data(
216            vec![0u8; 8],
217            UVec3::new(2, 2, 2),
218            DVec3::ONE,
219            DVec3::ZERO,
220            DMat3::IDENTITY,
221            1,
222        )
223        .unwrap();
224        let dv: DynVolume = v.into();
225        assert!(matches!(dv, DynVolume::U8(_)));
226    }
227}