1use crate::metadata::VolumeGeometry;
4use glam::{DMat3, DVec3, UVec3};
5use thiserror::Error;
6use volren_core::{DynVolume, Volume, VolumeError, VolumeInfo};
7
8#[derive(Debug, Error, PartialEq)]
10pub enum IncrementalVolumeError {
11 #[error("invalid geometry dimensions: {dimensions:?}")]
13 InvalidGeometry {
14 dimensions: UVec3,
16 },
17 #[error("slice {z_index} has {actual} voxels, expected {expected}")]
19 SliceLengthMismatch {
20 z_index: u32,
22 expected: usize,
24 actual: usize,
26 },
27 #[error("slice index {z_index} is out of bounds for depth {depth}")]
29 SliceOutOfBounds {
30 z_index: u32,
32 depth: u32,
34 },
35 #[error(transparent)]
37 Volume(#[from] VolumeError),
38}
39
40#[derive(Debug, Clone)]
42pub struct IncrementalVolume {
43 geometry: VolumeGeometry,
44 voxels: Vec<i16>,
45 loaded_slices: Vec<bool>,
46 loaded_count: usize,
47 scalar_range: Option<(i16, i16)>,
48}
49
50impl IncrementalVolume {
51 pub fn new(geometry: VolumeGeometry) -> Result<Self, IncrementalVolumeError> {
53 if geometry.dimensions.x == 0 || geometry.dimensions.y == 0 || geometry.dimensions.z == 0 {
54 return Err(IncrementalVolumeError::InvalidGeometry {
55 dimensions: geometry.dimensions,
56 });
57 }
58
59 Ok(Self {
60 geometry,
61 voxels: vec![0; geometry.voxel_count()],
62 loaded_slices: vec![false; geometry.dimensions.z as usize],
63 loaded_count: 0,
64 scalar_range: None,
65 })
66 }
67
68 pub fn insert_slice(
70 &mut self,
71 z_index: u32,
72 pixels: &[i16],
73 ) -> Result<(), IncrementalVolumeError> {
74 if z_index >= self.geometry.dimensions.z {
75 return Err(IncrementalVolumeError::SliceOutOfBounds {
76 z_index,
77 depth: self.geometry.dimensions.z,
78 });
79 }
80
81 let expected = self.geometry.slice_len();
82 if pixels.len() != expected {
83 return Err(IncrementalVolumeError::SliceLengthMismatch {
84 z_index,
85 expected,
86 actual: pixels.len(),
87 });
88 }
89
90 let start = z_index as usize * expected;
91 let end = start + expected;
92 self.voxels[start..end].copy_from_slice(pixels);
93
94 let was_loaded = std::mem::replace(&mut self.loaded_slices[z_index as usize], true);
95 if !was_loaded {
96 self.loaded_count += 1;
97 }
98 self.scalar_range = self.compute_scalar_range();
99 Ok(())
100 }
101
102 #[must_use]
104 pub fn geometry(&self) -> VolumeGeometry {
105 self.geometry
106 }
107
108 #[must_use]
110 pub fn is_complete(&self) -> bool {
111 self.loaded_count == self.loaded_slices.len()
112 }
113
114 #[must_use]
116 pub fn loaded_count(&self) -> usize {
117 self.loaded_count
118 }
119
120 #[must_use]
122 pub fn loaded_mask(&self) -> &[bool] {
123 &self.loaded_slices
124 }
125
126 #[must_use]
128 pub fn loading_progress(&self) -> f64 {
129 self.loaded_count as f64 / self.loaded_slices.len() as f64
130 }
131
132 #[must_use]
134 pub fn scalar_range(&self) -> Option<(i16, i16)> {
135 self.scalar_range
136 }
137
138 pub fn as_volume(&self) -> Result<Volume<i16>, IncrementalVolumeError> {
140 Ok(Volume::from_data(
141 self.voxels.clone(),
142 self.geometry.dimensions,
143 self.geometry.spacing,
144 self.geometry.origin,
145 self.geometry.direction,
146 1,
147 )?)
148 }
149
150 pub fn as_dyn_volume(&self) -> Result<DynVolume, IncrementalVolumeError> {
152 Ok(self.as_volume()?.into())
153 }
154
155 fn compute_scalar_range(&self) -> Option<(i16, i16)> {
156 let slice_len = self.geometry.slice_len();
157 let mut min_value = i16::MAX;
158 let mut max_value = i16::MIN;
159 let mut seen = false;
160
161 for (z_index, loaded) in self.loaded_slices.iter().copied().enumerate() {
162 if !loaded {
163 continue;
164 }
165 for &value in &self.voxels[z_index * slice_len..(z_index + 1) * slice_len] {
166 min_value = min_value.min(value);
167 max_value = max_value.max(value);
168 seen = true;
169 }
170 }
171
172 seen.then_some((min_value, max_value))
173 }
174}
175
176impl From<Volume<i16>> for VolumeGeometry {
177 fn from(volume: Volume<i16>) -> Self {
178 Self {
179 dimensions: volume.dimensions(),
180 spacing: volume.spacing(),
181 origin: volume.origin(),
182 direction: volume.direction(),
183 }
184 }
185}
186
187impl VolumeGeometry {
188 #[must_use]
190 pub fn new(dimensions: UVec3, spacing: DVec3, origin: DVec3, direction: DMat3) -> Self {
191 Self {
192 dimensions,
193 spacing,
194 origin,
195 direction,
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 fn geometry() -> VolumeGeometry {
205 VolumeGeometry::new(
206 UVec3::new(2, 2, 3),
207 DVec3::ONE,
208 DVec3::ZERO,
209 DMat3::IDENTITY,
210 )
211 }
212
213 #[test]
214 fn inserts_slices_in_any_order() {
215 let mut volume = IncrementalVolume::new(geometry()).unwrap();
216 volume.insert_slice(2, &[9, 10, 11, 12]).unwrap();
217 volume.insert_slice(0, &[1, 2, 3, 4]).unwrap();
218 volume.insert_slice(1, &[5, 6, 7, 8]).unwrap();
219
220 let typed = volume.as_volume().unwrap();
221 assert_eq!(typed.get(0, 0, 0), Some(1));
222 assert_eq!(typed.get(1, 1, 1), Some(8));
223 assert_eq!(typed.get(0, 0, 2), Some(9));
224 assert!(volume.is_complete());
225 assert_eq!(volume.scalar_range(), Some((1, 12)));
226 }
227
228 #[test]
229 fn duplicate_insert_recomputes_scalar_range_without_double_counting() {
230 let mut volume = IncrementalVolume::new(geometry()).unwrap();
231 volume.insert_slice(0, &[1, 2, 3, 4]).unwrap();
232 volume.insert_slice(0, &[10, 20, 30, 40]).unwrap();
233
234 assert_eq!(volume.loaded_count(), 1);
235 assert_eq!(volume.scalar_range(), Some((10, 40)));
236 }
237
238 #[test]
239 fn rejects_bad_slice_length() {
240 let err = IncrementalVolume::new(geometry())
241 .unwrap()
242 .insert_slice(0, &[1, 2, 3])
243 .unwrap_err();
244 assert!(matches!(
245 err,
246 IncrementalVolumeError::SliceLengthMismatch {
247 expected: 4,
248 actual: 3,
249 ..
250 }
251 ));
252 }
253
254 #[test]
255 fn rejects_out_of_bounds_z() {
256 let err = IncrementalVolume::new(geometry())
257 .unwrap()
258 .insert_slice(3, &[1, 2, 3, 4])
259 .unwrap_err();
260 assert!(matches!(
261 err,
262 IncrementalVolumeError::SliceOutOfBounds {
263 z_index: 3,
264 depth: 3
265 }
266 ));
267 }
268}