eorst/
filters.rs

1//! Image processing filters and OpenCV integration.
2//!
3//! This module provides the `Filters` trait for morphological operations
4//! and blur filters, backed by OpenCV when the `use_opencv` feature is enabled.
5//!
6//! ## Zero-Copy OpenCV Path
7//!
8//! When the `use_opencv` feature is enabled, all filter operations use a
9//! zero-copy approach:
10//! - **Input**: `Mat::new_rows_cols_with_data` creates a 2D Mat borrowing the
11//!   `Array2` data without copying
12//! - **Output**: `Mat::new_rows_cols_with_data_mut` writes directly into a
13//!   pre-allocated `Array2` without copying
14//!
15//! This eliminates 2 memory copies per operation (input + output), saving
16//! significant bandwidth for large raster blocks.
17
18use ndarray::Array2;
19
20#[cfg(feature = "use_opencv")]
21use crate::core_types::RasterType;
22#[cfg(feature = "use_opencv")]
23use opencv::boxed_ref::{BoxedRef, BoxedRefMut};
24#[cfg(feature = "use_opencv")]
25use opencv::core;
26#[cfg(feature = "use_opencv")]
27use opencv::imgproc;
28#[cfg(feature = "use_opencv")]
29use opencv::prelude::{MatTraitConst, MatTraitConstManual, MatTraitManual};
30#[cfg(feature = "use_opencv")]
31use opencv::Result;
32
33/// Trait for image processing filters (requires `use_opencv` feature).
34///
35/// All operations use zero-copy OpenCV bindings internally — the input
36/// `Array2` is borrowed (not copied) and the output is written in-place.
37pub trait Filters<T> {
38    /// Applies morphological erosion to the raster.
39    fn erode(&self, size: usize, kernel_size: i32) -> Array2<T>;
40    /// Applies morphological dilation to the raster.
41    fn dilate(&self, size: usize, kernel_size: i32) -> Array2<T>;
42    /// Applies median blur to the raster.
43    fn median_blur(&self, size: usize) -> Array2<T>;
44    /// Applies Gaussian blur to the raster.
45    fn gaussian(&self, kernel_size: usize, sigma: f64) -> Array2<T>;
46}
47
48/// Type alias for a 2D raster block slice.
49pub type RasterBlockSlice2<T> = Array2<T>;
50
51// ─── Zero-Copy OpenCV Helpers ───
52
53/// Creates a zero-copy immutable OpenCV `Mat` borrowing an `Array2` slice.
54///
55/// The returned `BoxedRef` reads from the `Array2`'s underlying storage
56/// without copying. When dropped, no deallocation occurs.
57#[cfg(feature = "use_opencv")]
58fn mat_borrow<'a, T>(
59    rows: i32,
60    cols: i32,
61    data: &'a [T],
62) -> Result<BoxedRef<'a, core::Mat>>
63where
64    T: opencv::prelude::DataType + 'static,
65{
66    core::Mat::new_rows_cols_with_data::<T>(rows, cols, data)
67}
68
69/// Creates a zero-copy mutable OpenCV `Mat` that writes into an `Array2` slice.
70///
71/// The returned `BoxedRefMut` writes directly into the `Array2`'s underlying
72/// storage. When dropped, no deallocation occurs — the `Array2` retains ownership.
73#[cfg(feature = "use_opencv")]
74fn mat_borrow_mut<'a, T>(
75    rows: i32,
76    cols: i32,
77    data: &'a mut [T],
78) -> Result<BoxedRefMut<'a, core::Mat>>
79where
80    T: opencv::prelude::DataType + 'static,
81{
82    core::Mat::new_rows_cols_with_data_mut::<T>(rows, cols, data)
83}
84
85// ─── Deprecated Copy-Based Helpers ───
86
87/// Converts an `ndarray::ArrayView2` into an OpenCV `Mat` by copying data.
88///
89/// **Deprecated**: The `Filters` trait now uses zero-copy internally.
90/// This function is retained only for backward compatibility.
91#[cfg(feature = "use_opencv")]
92#[deprecated(
93    since = "0.3.2",
94    note = "This function copies data. The Filters trait now uses zero-copy internally."
95)]
96pub fn arrayview2_to_mat<T>(data: ndarray::ArrayView2<'_, T>) -> Result<BoxedRef<'_, core::Mat>>
97where
98    T: opencv::prelude::DataType + 'static,
99{
100    let rows = data.dim().0 as i32;
101    let cols = data.dim().1 as i32;
102
103    let mut mat = unsafe {
104        core::Mat::new_rows_cols(
105            rows,
106            cols,
107            T::opencv_type(),
108        )?
109    };
110
111    mat.data_typed_mut::<T>()?
112        .copy_from_slice(data.as_slice().unwrap());
113
114    Ok(mat.into())
115}
116
117/// Converts an OpenCV `Mat` back into an `ndarray::Array2<T>` by copying data.
118///
119/// **Deprecated**: The `Filters` trait now uses zero-copy internally.
120/// This function is retained only for backward compatibility.
121#[cfg(feature = "use_opencv")]
122#[deprecated(
123    since = "0.3.2",
124    note = "This function copies data. The Filters trait now uses zero-copy internally."
125)]
126pub fn mat_to_array2<T>(mat: &core::Mat) -> Result<Array2<T>>
127where
128    T: opencv::prelude::DataType + Clone,
129{
130    let rows = mat.rows() as usize;
131    let cols = mat.cols() as usize;
132
133    let mat_slice: &[T] = mat.data_typed()?;
134
135    let array = ndarray::ArrayView2::from_shape((rows, cols), mat_slice)
136        .unwrap()
137        .to_owned();
138
139    Ok(array)
140}
141
142#[cfg(feature = "use_opencv")]
143impl<T> Filters<T> for RasterBlockSlice2<T>
144where
145    T: RasterType + opencv::prelude::DataType,
146{
147    fn erode(&self, size: usize, kernel_structure: i32) -> Array2<T> {
148        let (rows, cols) = self.dim();
149        let mut output = Array2::<T>::zeros((rows, cols));
150
151        // Zero-copy: src borrows self's data as 2D Mat, dst writes into output's data
152        let src = mat_borrow::<T>(rows as i32, cols as i32, self.as_slice().unwrap()).unwrap();
153        let mut dst = mat_borrow_mut::<T>(rows as i32, cols as i32, output.as_slice_mut().unwrap()).unwrap();
154        let kernel = imgproc::get_structuring_element(
155            kernel_structure,
156            core::Size::new(size as i32, size as i32),
157            core::Point::new(-1, -1),
158        )
159        .unwrap();
160
161        imgproc::erode(
162            &src,
163            &mut dst,
164            &kernel,
165            core::Point::new(-1, -1),
166            1,
167            core::BORDER_REFLECT,
168            core::Scalar::all(0.0),
169        )
170        .unwrap();
171
172        // BoxedRef and BoxedRefMut drop without deallocating — output now holds results
173        output
174    }
175
176    fn dilate(&self, size: usize, kernel_structure: i32) -> Array2<T> {
177        let (rows, cols) = self.dim();
178        let mut output = Array2::<T>::zeros((rows, cols));
179
180        let src = mat_borrow::<T>(rows as i32, cols as i32, self.as_slice().unwrap()).unwrap();
181        let mut dst = mat_borrow_mut::<T>(rows as i32, cols as i32, output.as_slice_mut().unwrap()).unwrap();
182        let kernel = imgproc::get_structuring_element(
183            kernel_structure,
184            core::Size::new(size as i32, size as i32),
185            core::Point::new(-1, -1),
186        )
187        .unwrap();
188
189        imgproc::dilate(
190            &src,
191            &mut dst,
192            &kernel,
193            core::Point::new(-1, -1),
194            1,
195            core::BORDER_REFLECT,
196            core::Scalar::all(0.0),
197        )
198        .unwrap();
199
200        output
201    }
202
203    fn median_blur(&self, size: usize) -> Array2<T> {
204        let (rows, cols) = self.dim();
205        let mut output = Array2::<T>::zeros((rows, cols));
206
207        let src = mat_borrow::<T>(rows as i32, cols as i32, self.as_slice().unwrap()).unwrap();
208        let mut dst = mat_borrow_mut::<T>(rows as i32, cols as i32, output.as_slice_mut().unwrap()).unwrap();
209
210        imgproc::median_blur(&src, &mut dst, size.try_into().unwrap()).unwrap();
211
212        output
213    }
214
215    fn gaussian(&self, kernel_size: usize, sigma: f64) -> Array2<T> {
216        let (rows, cols) = self.dim();
217        let mut output = Array2::<T>::zeros((rows, cols));
218
219        let src = mat_borrow::<T>(rows as i32, cols as i32, self.as_slice().unwrap()).unwrap();
220        let mut dst = mat_borrow_mut::<T>(rows as i32, cols as i32, output.as_slice_mut().unwrap()).unwrap();
221
222        let ksize = core::Size::new(kernel_size as i32, kernel_size as i32);
223        let border_type = core::BORDER_DEFAULT;
224        let hint = core::AlgorithmHint::ALGO_HINT_DEFAULT;
225        imgproc::gaussian_blur(&src, &mut dst, ksize, sigma, sigma, border_type, hint).unwrap();
226
227        output
228    }
229}