Skip to main content

vtk_pure_rs/data/
image_data.rs

1use crate::types::BoundingBox;
2
3use crate::data::{DataSetAttributes, FieldData};
4use crate::data::traits::{DataObject, DataSet};
5
6/// Regular grid with implicit point coordinates computed from extent, spacing, and origin.
7///
8/// Analogous to VTK's `vtkImageData`. Points are not stored explicitly — their
9/// coordinates are computed as: `point(i,j,k) = origin + spacing * [i, j, k]`.
10///
11/// The extent is `[x_min, x_max, y_min, y_max, z_min, z_max]` in index space.
12///
13/// # Examples
14///
15/// ```
16/// use crate::data::ImageData;
17///
18/// // Create a 10x10x10 grid
19/// let img = ImageData::with_dimensions(10, 10, 10);
20/// assert_eq!(img.dimensions(), [10, 10, 10]);
21/// ```
22#[derive(Debug, Clone)]
23pub struct ImageData {
24    extent: [i64; 6],
25    spacing: [f64; 3],
26    origin: [f64; 3],
27    point_data: DataSetAttributes,
28    cell_data: DataSetAttributes,
29    field_data: FieldData,
30}
31
32impl Default for ImageData {
33    fn default() -> Self {
34        Self {
35            extent: [0, 0, 0, 0, 0, 0],
36            spacing: [1.0, 1.0, 1.0],
37            origin: [0.0, 0.0, 0.0],
38            point_data: DataSetAttributes::new(),
39            cell_data: DataSetAttributes::new(),
40            field_data: FieldData::new(),
41        }
42    }
43}
44
45impl ImageData {
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Create an ImageData with given dimensions (number of points in each direction).
51    pub fn with_dimensions(nx: usize, ny: usize, nz: usize) -> Self {
52        Self {
53            extent: [0, (nx as i64) - 1, 0, (ny as i64) - 1, 0, (nz as i64) - 1],
54            ..Default::default()
55        }
56    }
57
58    pub fn extent(&self) -> [i64; 6] {
59        self.extent
60    }
61
62    pub fn set_extent(&mut self, extent: [i64; 6]) {
63        self.extent = extent;
64    }
65
66    pub fn spacing(&self) -> [f64; 3] {
67        self.spacing
68    }
69
70    pub fn set_spacing(&mut self, spacing: [f64; 3]) {
71        self.spacing = spacing;
72    }
73
74    pub fn origin(&self) -> [f64; 3] {
75        self.origin
76    }
77
78    pub fn set_origin(&mut self, origin: [f64; 3]) {
79        self.origin = origin;
80    }
81
82    /// Number of points in each dimension.
83    pub fn dimensions(&self) -> [usize; 3] {
84        [
85            (self.extent[1] - self.extent[0] + 1).max(0) as usize,
86            (self.extent[3] - self.extent[2] + 1).max(0) as usize,
87            (self.extent[5] - self.extent[4] + 1).max(0) as usize,
88        ]
89    }
90
91    /// Compute the world-space position of a point given its (i, j, k) index.
92    pub fn point_from_ijk(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
93        [
94            self.origin[0] + (self.extent[0] as f64 + i as f64) * self.spacing[0],
95            self.origin[1] + (self.extent[2] as f64 + j as f64) * self.spacing[1],
96            self.origin[2] + (self.extent[4] as f64 + k as f64) * self.spacing[2],
97        ]
98    }
99
100    /// Convert a flat point index to (i, j, k).
101    pub fn ijk_from_index(&self, idx: usize) -> (usize, usize, usize) {
102        let dims = self.dimensions();
103        let k = idx / (dims[0] * dims[1]);
104        let remainder = idx % (dims[0] * dims[1]);
105        let j = remainder / dims[0];
106        let i = remainder % dims[0];
107        (i, j, k)
108    }
109
110    /// Get point coordinates by flat index (equivalent to `point_from_ijk(ijk_from_index(idx))`).
111    pub fn point_at(&self, idx: usize) -> [f64; 3] {
112        let (i, j, k) = self.ijk_from_index(idx);
113        self.point_from_ijk(i, j, k)
114    }
115
116    /// Convert (i, j, k) to a flat point index.
117    pub fn index_from_ijk(&self, i: usize, j: usize, k: usize) -> usize {
118        let dims = self.dimensions();
119        k * dims[0] * dims[1] + j * dims[0] + i
120    }
121
122    /// Get the active scalar value at grid position (i, j, k).
123    ///
124    /// Returns None if no active scalars are set.
125    pub fn scalar_at(&self, i: usize, j: usize, k: usize) -> Option<f64> {
126        let scalars = self.point_data.scalars()?;
127        let idx = self.index_from_ijk(i, j, k);
128        let mut buf = [0.0f64];
129        scalars.tuple_as_f64(idx, &mut buf);
130        Some(buf[0])
131    }
132
133    /// Iterate over all point coordinates in index order.
134    pub fn point_positions(&self) -> Vec<[f64; 3]> {
135        let n = self.num_points();
136        (0..n).map(|idx| self.point_at(idx)).collect()
137    }
138
139    /// Number of points.
140    pub fn num_points(&self) -> usize {
141        let d = self.dimensions();
142        d[0] * d[1] * d[2]
143    }
144
145    pub fn point_data(&self) -> &DataSetAttributes {
146        &self.point_data
147    }
148
149    pub fn point_data_mut(&mut self) -> &mut DataSetAttributes {
150        &mut self.point_data
151    }
152
153    pub fn cell_data(&self) -> &DataSetAttributes {
154        &self.cell_data
155    }
156
157    pub fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
158        &mut self.cell_data
159    }
160
161    /// Builder: set spacing.
162    pub fn with_spacing(mut self, spacing: [f64; 3]) -> Self {
163        self.spacing = spacing;
164        self
165    }
166
167    /// Builder: set origin.
168    pub fn with_origin(mut self, origin: [f64; 3]) -> Self {
169        self.origin = origin;
170        self
171    }
172
173    /// Create an ImageData with a scalar field generated from a function.
174    ///
175    /// The function receives `(x, y, z)` world coordinates and returns a scalar value.
176    /// The result is stored as point data with the given name.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use crate::data::ImageData;
182    ///
183    /// // Create a sphere distance field
184    /// let img = ImageData::from_function(
185    ///     [10, 10, 10], [0.1, 0.1, 0.1], [-0.5, -0.5, -0.5],
186    ///     "distance",
187    ///     |x, y, z| (x*x + y*y + z*z).sqrt(),
188    /// );
189    /// assert_eq!(img.dimensions(), [10, 10, 10]);
190    /// ```
191    pub fn from_function(
192        dims: [usize; 3],
193        spacing: [f64; 3],
194        origin: [f64; 3],
195        name: &str,
196        f: impl Fn(f64, f64, f64) -> f64,
197    ) -> Self {
198        let mut img = Self::with_dimensions(dims[0], dims[1], dims[2]);
199        img.set_spacing(spacing);
200        img.set_origin(origin);
201
202        let mut values = Vec::with_capacity(dims[0] * dims[1] * dims[2]);
203        for k in 0..dims[2] {
204            for j in 0..dims[1] {
205                for i in 0..dims[0] {
206                    let x = origin[0] + i as f64 * spacing[0];
207                    let y = origin[1] + j as f64 * spacing[1];
208                    let z = origin[2] + k as f64 * spacing[2];
209                    values.push(f(x, y, z));
210                }
211            }
212        }
213
214        let arr = crate::data::DataArray::from_vec(name, values, 1);
215        img.point_data.add_array(crate::data::AnyDataArray::F64(arr));
216        img.point_data.set_active_scalars(name);
217        img
218    }
219
220    /// Builder: add a point data array.
221    pub fn with_point_array(mut self, array: crate::data::AnyDataArray) -> Self {
222        let name = array.name().to_string();
223        self.point_data.add_array(array);
224        if self.point_data.scalars().is_none() {
225            self.point_data.set_active_scalars(&name);
226        }
227        self
228    }
229}
230
231impl DataObject for ImageData {
232    fn field_data(&self) -> &FieldData {
233        &self.field_data
234    }
235
236    fn field_data_mut(&mut self) -> &mut FieldData {
237        &mut self.field_data
238    }
239}
240
241impl DataSet for ImageData {
242    fn num_points(&self) -> usize {
243        let d = self.dimensions();
244        d[0] * d[1] * d[2]
245    }
246
247    fn num_cells(&self) -> usize {
248        let d = self.dimensions();
249        let cx = d[0].saturating_sub(1);
250        let cy = d[1].saturating_sub(1);
251        let cz = d[2].saturating_sub(1);
252        // For lower-dimensional data, treat missing dims as 1 cell
253        match (cx, cy, cz) {
254            (0, _, _) | (_, 0, _) | (_, _, 0) => cx.max(1) * cy.max(1) * cz.max(1),
255            _ => cx * cy * cz,
256        }
257    }
258
259    fn point(&self, idx: usize) -> [f64; 3] {
260        let (i, j, k) = self.ijk_from_index(idx);
261        self.point_from_ijk(i, j, k)
262    }
263
264    fn bounds(&self) -> BoundingBox {
265        let d = self.dimensions();
266        if d[0] == 0 || d[1] == 0 || d[2] == 0 {
267            return BoundingBox::empty();
268        }
269        let p0 = self.point_from_ijk(0, 0, 0);
270        let p1 = self.point_from_ijk(d[0] - 1, d[1] - 1, d[2] - 1);
271        let mut bb = BoundingBox::empty();
272        bb.expand(p0);
273        bb.expand(p1);
274        bb
275    }
276
277    fn point_data(&self) -> &DataSetAttributes {
278        &self.point_data
279    }
280
281    fn point_data_mut(&mut self) -> &mut DataSetAttributes {
282        &mut self.point_data
283    }
284
285    fn cell_data(&self) -> &DataSetAttributes {
286        &self.cell_data
287    }
288
289    fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
290        &mut self.cell_data
291    }
292}
293
294impl std::fmt::Display for ImageData {
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        let d = self.dimensions();
297        write!(f, "ImageData: {}x{}x{}, spacing {:?}, origin {:?}, {} point arrays",
298            d[0], d[1], d[2], self.spacing(), self.origin(), self.point_data.num_arrays())
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn dimensions_and_points() {
308        let img = ImageData::with_dimensions(3, 4, 5);
309        assert_eq!(img.dimensions(), [3, 4, 5]);
310        assert_eq!(img.num_points(), 60);
311        assert_eq!(img.point_from_ijk(0, 0, 0), [0.0, 0.0, 0.0]);
312        assert_eq!(img.point_from_ijk(2, 3, 4), [2.0, 3.0, 4.0]);
313    }
314
315    #[test]
316    fn custom_spacing_and_origin() {
317        let mut img = ImageData::with_dimensions(2, 2, 2);
318        img.set_spacing([0.5, 0.5, 0.5]);
319        img.set_origin([1.0, 2.0, 3.0]);
320        assert_eq!(img.point_from_ijk(1, 1, 1), [1.5, 2.5, 3.5]);
321    }
322
323    #[test]
324    fn index_roundtrip() {
325        let img = ImageData::with_dimensions(3, 4, 5);
326        for idx in 0..60 {
327            let (i, j, k) = img.ijk_from_index(idx);
328            assert_eq!(img.index_from_ijk(i, j, k), idx);
329        }
330    }
331
332    #[test]
333    fn bounds_computation() {
334        let mut img = ImageData::with_dimensions(10, 10, 10);
335        img.set_spacing([0.1, 0.1, 0.1]);
336        let bb = img.bounds();
337        assert!((bb.x_max - 0.9).abs() < 1e-10);
338        assert!((bb.y_max - 0.9).abs() < 1e-10);
339    }
340
341    #[test]
342    fn num_cells() {
343        let img = ImageData::with_dimensions(3, 4, 5);
344        assert_eq!(img.num_cells(), 2 * 3 * 4); // 24 cells
345    }
346
347    #[test]
348    fn from_function() {
349        let img = ImageData::from_function(
350            [5, 5, 5], [0.2, 0.2, 0.2], [0.0, 0.0, 0.0],
351            "dist",
352            |x, y, z| (x*x + y*y + z*z).sqrt(),
353        );
354        assert_eq!(img.dimensions(), [5, 5, 5]);
355        assert!(img.point_data().scalars().is_some());
356        assert_eq!(img.point_data().scalars().unwrap().name(), "dist");
357        assert_eq!(img.point_data().scalars().unwrap().num_tuples(), 125);
358    }
359
360    #[test]
361    fn builder_chain() {
362        let img = ImageData::with_dimensions(3, 3, 3)
363            .with_spacing([0.5, 0.5, 0.5])
364            .with_origin([1.0, 2.0, 3.0]);
365        assert_eq!(img.spacing(), [0.5, 0.5, 0.5]);
366        assert_eq!(img.origin(), [1.0, 2.0, 3.0]);
367    }
368
369    #[test]
370    fn scalar_at() {
371        let img = ImageData::from_function(
372            [3, 3, 3], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0],
373            "val", |x, _y, _z| x,
374        );
375        // At (0,0,0) x=0, at (2,0,0) x=2
376        assert!((img.scalar_at(0, 0, 0).unwrap()).abs() < 1e-10);
377        assert!((img.scalar_at(2, 0, 0).unwrap() - 2.0).abs() < 1e-10);
378    }
379
380    #[test]
381    fn point_positions() {
382        let img = ImageData::with_dimensions(2, 2, 1);
383        let positions = img.point_positions();
384        assert_eq!(positions.len(), 4);
385        assert_eq!(positions[0], [0.0, 0.0, 0.0]);
386        assert_eq!(positions[1], [1.0, 0.0, 0.0]);
387    }
388}