pineapple_core/im/
view.rs

1// Copyright (c) 2025, Tom Ouellette
2// Licensed under the BSD 3-Clause License
3
4use std::ops::Deref;
5
6use num::{FromPrimitive, ToPrimitive};
7
8use crate::im::PineappleBuffer;
9use crate::impl_enum_dispatch;
10use crate::mp::{intensity, moments, texture, zernike};
11
12/// A wrapper around valid view types
13pub enum PineappleView<'a> {
14    U8(PineappleViewBuffer<'a, u8, Vec<u8>>),
15    U16(PineappleViewBuffer<'a, u16, Vec<u16>>),
16    U32(PineappleViewBuffer<'a, u32, Vec<u32>>),
17    U64(PineappleViewBuffer<'a, u64, Vec<u64>>),
18    I32(PineappleViewBuffer<'a, i32, Vec<i32>>),
19    I64(PineappleViewBuffer<'a, i64, Vec<i64>>),
20    F32(PineappleViewBuffer<'a, f32, Vec<f32>>),
21    F64(PineappleViewBuffer<'a, f64, Vec<f64>>),
22}
23
24// >>> PROPERTY METHODS
25
26impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; width(&'a self) -> usize);
27impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; height(&'a self) -> usize);
28impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; channels(&'a self) -> usize);
29
30// <<< PROPERTY METHODS
31
32// >>> MEASURE METHODS
33
34impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; intensity(&'a self) -> [f32; 7]);
35impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; moments(&'a self) -> [f32; 24]);
36impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; texture(&'a self) -> [f32; 13]);
37impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; zernike(&'a self) -> [f32; 30]);
38impl_enum_dispatch!(PineappleView<'a>, U8, U16, U32, U64, I32, I64, F32, F64; descriptors(&'a self) -> Vec<f32>);
39
40// <<< MEASURE METHODS
41
42/// A row-major buffer that defines an image view/crop/subregion
43///
44/// The cropped object represents a zero-copy reference to a larger
45/// PineappleBuffer. To enable zero-copy, the full image has to share
46/// the same lifetime as the cropped object. This should generally
47/// always be the case since we perform operations on cropped objects
48/// iteratively across all segmented objects from the same image.
49///
50/// # Examples
51///
52/// ```
53/// use pineapple_core::im::{PineappleBuffer, PineappleViewBuffer};
54///
55/// let width = 3;
56/// let height = 3;
57/// let channels = 1;
58/// let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
59///
60/// let buffer = PineappleBuffer::<u8, Vec<u8>>::new(width, height, channels, data).unwrap();
61/// let crop = PineappleViewBuffer::new(1, 1, 2, 2, &buffer);
62///
63/// for subpixel in crop.iter() {
64///     let _ = subpixel;
65/// }
66/// ```
67#[derive(Clone)]
68pub struct PineappleViewBuffer<'a, T, Container> {
69    buffer: &'a PineappleBuffer<T, Container>,
70    width: usize,    // Full image width
71    channels: usize, // Full image channels
72    x: usize,        // Minimum x-value of crop
73    y: usize,        // Minimum y-value of crop
74    w: usize,        // Width of crop
75    h: usize,        // Height of crop
76}
77
78impl<'a, T, Container> PineappleViewBuffer<'a, T, Container>
79where
80    T: ToPrimitive + FromPrimitive,
81    Container: Deref<Target = [T]>,
82{
83    /// Initialize a copy-free object specifying a cropped/subregion of an image
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use pineapple_core::im::{PineappleBuffer, PineappleViewBuffer};
89    ///
90    /// let width = 3;
91    /// let height = 3;
92    /// let channels = 1;
93    /// let data: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
94    ///
95    /// let buffer = PineappleBuffer::<u8, Vec<u8>>::new(width, height, channels, data).unwrap();
96    /// let crop = PineappleViewBuffer::new(1, 1, 2, 2, &buffer);
97    /// ```
98    pub fn new(x: u32, y: u32, w: u32, h: u32, buffer: &'a PineappleBuffer<T, Container>) -> Self {
99        let x = std::cmp::min(x, buffer.width());
100        let y = std::cmp::min(y, buffer.height());
101        let h = std::cmp::min(h, buffer.height() - y);
102        let w = std::cmp::min(w, buffer.width() - x);
103
104        PineappleViewBuffer {
105            buffer,
106            width: buffer.width() as usize,
107            channels: buffer.channels() as usize,
108            x: x as usize,
109            y: y as usize,
110            w: w as usize,
111            h: h as usize,
112        }
113    }
114}
115
116// >>> PROPERTY METHODS
117
118impl<T, Container> PineappleViewBuffer<'_, T, Container>
119where
120    T: ToPrimitive + FromPrimitive,
121    Container: Deref<Target = [T]>,
122{
123    /// Length of cropped buffer
124    pub fn len(&self) -> usize {
125        self.w * self.h * self.channels
126    }
127
128    /// Check if buffer is empty
129    pub fn is_empty(&self) -> bool {
130        self.len() == 0
131    }
132
133    /// Width of object
134    #[allow(clippy::all)]
135    pub fn width(&self) -> usize {
136        self.w
137    }
138
139    /// Height of object
140    #[allow(clippy::all)]
141    pub fn height(&self) -> usize {
142        self.h
143    }
144
145    /// Number of channels
146    pub fn channels(&self) -> usize {
147        self.channels
148    }
149}
150
151// <<< PROPERTY METHODS
152
153// >>> MEASURE METHODS
154
155impl<'a, T, Container> PineappleViewBuffer<'a, T, Container>
156where
157    T: ToPrimitive + FromPrimitive,
158    Container: Deref<Target = [T]>,
159{
160    /// Compute the intensity descriptors for the object
161    #[allow(clippy::identity_op, clippy::erasing_op)]
162    pub fn intensity(&'a self) -> [f32; 7] {
163        let results = intensity::objects(self);
164
165        let c = self.channels();
166        let rc = 1f32 / c as f32;
167        let len = results.len();
168
169        // We average over channel values to avoid variable
170        //sized outputs in variable channel experiments
171        let mut average: [f32; 7] = [0f32; 7];
172
173        average[5] = results[len - 2];
174        average[6] = results[len - 1];
175
176        for i in 0..c {
177            average[0] = results[i + 0 * c] * rc;
178            average[1] = results[i + 1 * c] * rc;
179            average[2] = results[i + 2 * c] * rc;
180            average[3] = results[i + 3 * c] * rc;
181            average[4] = results[i + 3 * c] * rc;
182        }
183
184        average
185    }
186
187    /// Compute the image moments for the object
188    pub fn moments(&'a self) -> [f32; 24] {
189        moments::objects(self)
190    }
191
192    /// Compute the texture descriptors for the object
193    pub fn texture(&'a self) -> [f32; 13] {
194        texture::objects(self)
195    }
196
197    /// Compute the zernike moments for the object
198    pub fn zernike(&'a self) -> [f32; 30] {
199        zernike::objects(self)
200    }
201
202    /// Compute all view descriptors
203    pub fn descriptors(&'a self) -> Vec<f32> {
204        self.intensity()
205            .into_iter()
206            .chain(self.moments())
207            .chain(self.texture())
208            .chain(self.zernike())
209            .collect()
210    }
211}
212
213// <<< MEASURE METHODS
214
215// >>> ITERATOR METHODS
216
217impl<'a, T, Container> PineappleViewBuffer<'a, T, Container>
218where
219    T: ToPrimitive + FromPrimitive,
220    Container: Deref<Target = [T]>,
221{
222    /// Return an iterator over pixels containing all channels
223    pub fn iter(&'a self) -> SubpixelIterator<'a, T, Container> {
224        SubpixelIterator {
225            buffer: self.buffer,
226            width: self.width,
227            channels: self.channels,
228            x: self.x,
229            y: self.y,
230            w: self.w,
231            h: self.h,
232            i: self.y,
233            j: self.w * self.channels,
234        }
235    }
236
237    /// Return an iterator over pixels containing all channels
238    pub fn iter_pixels(&'a self) -> PixelIterator<'a, T, Container> {
239        PixelIterator {
240            buffer: self.buffer,
241            width: self.width,
242            channels: self.channels,
243            x: self.x,
244            y: self.y,
245            w: self.w,
246            h: self.h,
247            i: 0,
248            j: 0,
249        }
250    }
251}
252
253// <<< ITERATOR METHODS
254
255/// An iterator over subpixels
256pub struct SubpixelIterator<'a, T, Container>
257where
258    T: ToPrimitive + FromPrimitive,
259    Container: Deref<Target = [T]>,
260{
261    buffer: &'a PineappleBuffer<T, Container>,
262    width: usize,
263    channels: usize,
264    x: usize,
265    y: usize,
266    w: usize,
267    h: usize,
268    i: usize,
269    j: usize,
270}
271
272impl<'a, T, Container> Iterator for SubpixelIterator<'a, T, Container>
273where
274    T: ToPrimitive + FromPrimitive,
275    Container: Deref<Target = [T]>,
276{
277    type Item = &'a T;
278
279    fn next(&mut self) -> Option<Self::Item> {
280        if self.i >= self.y + self.h {
281            return None;
282        }
283
284        let j_mod = self.j % (self.w * self.channels);
285
286        let idx = self.i * (self.width * self.channels) + (self.x * self.channels) + j_mod;
287
288        if j_mod == (self.w * self.channels) - 1 {
289            self.i += 1;
290        }
291
292        self.j += 1;
293
294        Some(&self.buffer.as_raw()[idx])
295    }
296}
297
298/// An iterator over pixels containing all channels
299pub struct PixelIterator<'a, T, Container>
300where
301    T: ToPrimitive + FromPrimitive,
302    Container: Deref<Target = [T]>,
303{
304    buffer: &'a PineappleBuffer<T, Container>,
305    width: usize,
306    channels: usize,
307    x: usize,
308    y: usize,
309    w: usize,
310    h: usize,
311    i: usize,
312    j: usize,
313}
314
315impl<'a, T, Container> Iterator for PixelIterator<'a, T, Container>
316where
317    T: ToPrimitive + FromPrimitive,
318    Container: Deref<Target = [T]>,
319{
320    type Item = &'a [T];
321
322    fn next(&mut self) -> Option<Self::Item> {
323        if self.j >= self.h {
324            return None;
325        }
326
327        let idx = ((self.y + self.j) * self.width + (self.x + self.i)) * self.channels;
328
329        self.i += 1;
330
331        if self.i >= self.w {
332            self.i = 0;
333            self.j += 1;
334        }
335
336        Some(&self.buffer.as_raw()[idx..idx + self.channels])
337    }
338}
339
340#[cfg(test)]
341mod test {
342
343    use super::*;
344
345    #[test]
346    fn test_crop_in_bounds() {
347        let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
348        let buffer = PineappleBuffer::<u8, Vec<u8>>::new(3, 3, 1, data).unwrap();
349
350        let crop = PineappleViewBuffer::new(1, 1, 2, 2, &buffer);
351        let mut step = crop.iter();
352        assert_eq!(step.next().unwrap(), &4);
353        assert_eq!(step.next().unwrap(), &5);
354        assert_eq!(step.next().unwrap(), &7);
355        assert_eq!(step.next().unwrap(), &8);
356    }
357
358    #[test]
359    fn test_crop_in_bounds_multichannel() {
360        let data = vec![0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8];
361        let buffer = PineappleBuffer::<u8, Vec<u8>>::new(3, 3, 2, data).unwrap();
362
363        let crop = PineappleViewBuffer::new(1, 1, 2, 2, &buffer);
364        let mut step = crop.iter();
365        assert_eq!(step.next().unwrap(), &4);
366        assert_eq!(step.next().unwrap(), &4);
367        assert_eq!(step.next().unwrap(), &5);
368        assert_eq!(step.next().unwrap(), &5);
369        assert_eq!(step.next().unwrap(), &7);
370        assert_eq!(step.next().unwrap(), &7);
371        assert_eq!(step.next().unwrap(), &8);
372        assert_eq!(step.next().unwrap(), &8);
373    }
374
375    #[test]
376    fn test_crop_out_bounds() {
377        let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
378        let buffer = PineappleBuffer::<u8, Vec<u8>>::new(3, 3, 1, data).unwrap();
379
380        let crop = PineappleViewBuffer::new(2, 2, 4, 4, &buffer);
381        let mut step = crop.iter();
382        assert_eq!(step.next().unwrap(), &8);
383        assert_eq!(step.next(), None);
384    }
385
386    #[test]
387    fn test_crop_iter() {
388        let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
389        let buffer = PineappleBuffer::<u8, Vec<u8>>::new(3, 3, 1, data).unwrap();
390
391        let crop = PineappleViewBuffer::new(0, 0, 3, 3, &buffer);
392        for (i, c) in crop.iter().enumerate() {
393            let h = i / 3;
394            let w = i % 3;
395            assert_eq!(*c, (3 * h + w) as u8);
396        }
397    }
398
399    #[test]
400    fn test_crop_iter_pixels() {
401        let data = vec![0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8];
402        let buffer = PineappleBuffer::<u8, Vec<u8>>::new(3, 3, 2, data).unwrap();
403
404        let crop = PineappleViewBuffer::new(0, 0, 3, 3, &buffer);
405
406        for (i, pixel) in crop.iter_pixels().enumerate() {
407            assert_eq!(pixel, &[i as u8, i as u8]);
408        }
409    }
410
411    #[test]
412    fn test_iter_consistency() {
413        let size_32 = PineappleBuffer::<u8, Vec<u8>>::new(3, 2, 1, vec![0, 0, 0, 1, 1, 1]).unwrap();
414        let size_23 = PineappleBuffer::<u8, Vec<u8>>::new(2, 3, 1, vec![0, 0, 1, 1, 2, 2]).unwrap();
415
416        let size_32_crop = PineappleViewBuffer::new(0, 0, 3, 2, &size_32);
417        let size_23_crop = PineappleViewBuffer::new(0, 0, 2, 3, &size_23);
418
419        assert_eq!(
420            size_32_crop.iter().count(),
421            size_32_crop.iter_pixels().count()
422        );
423
424        assert_eq!(
425            size_23_crop.iter().count(),
426            size_23_crop.iter_pixels().count()
427        );
428    }
429}