Skip to main content

scirs2_ndimage/filters/
fourier.rs

1//! Fourier domain filtering functions
2//!
3//! This module provides filters that operate in the Fourier domain,
4//! which can be more efficient for certain operations, especially
5//! with large kernels or when multiple filters are applied.
6//!
7//! For very large datasets that don't fit in memory, use the streaming
8//! variants of these functions (e.g., `fourier_gaussian_streaming`).
9
10use scirs2_core::ndarray::{Array, Array1, Array2, Dimension};
11use scirs2_core::numeric::Complex64;
12use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
13use std::f64::consts::PI;
14use std::fmt::Debug;
15
16use crate::error::{NdimageError, NdimageResult};
17use scirs2_fft::{fft, fft2, fftfreq, ifft, ifft2, FFTError};
18
19// Conversion from FFTError to NdimageError
20impl From<FFTError> for NdimageError {
21    fn from(err: FFTError) -> Self {
22        NdimageError::ComputationError(format!("FFT error: {}", err))
23    }
24}
25
26/// Apply a Gaussian filter in the Fourier domain
27///
28/// This is more efficient than spatial domain filtering for large sigma values.
29///
30/// # Arguments
31///
32/// * `input` - Input array to filter
33/// * `sigma` - Standard deviation for Gaussian kernel (can be different for each axis)
34/// * `truncate` - Truncate the filter at this many standard deviations (not used in Fourier domain)
35///
36/// # Returns
37///
38/// * `Result<Array<T, D>>` - Filtered array
39#[allow(dead_code)]
40pub fn fourier_gaussian<T, D>(
41    input: &Array<T, D>,
42    sigma: &[T],
43    _truncate: Option<T>,
44) -> NdimageResult<Array<T, D>>
45where
46    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
47    D: Dimension,
48{
49    // Validate inputs
50    if input.ndim() == 0 {
51        return Err(NdimageError::InvalidInput(
52            "Input array cannot be 0-dimensional".into(),
53        ));
54    }
55
56    if sigma.len() != input.ndim() {
57        return Err(NdimageError::DimensionError(format!(
58            "Sigma must have same length as input dimensions (got {} expected {})",
59            sigma.len(),
60            input.ndim()
61        )));
62    }
63
64    for &s in sigma {
65        if s <= T::zero() {
66            return Err(NdimageError::InvalidInput("Sigma must be positive".into()));
67        }
68    }
69
70    match input.ndim() {
71        1 => {
72            // 1D case
73            let input_1d = input
74                .view()
75                .into_dimensionality::<scirs2_core::ndarray::Ix1>()
76                .map_err(|_| NdimageError::DimensionError("Failed to convert to 1D".into()))?;
77            let input_1d_owned = input_1d.to_owned();
78            let result = fourier_gaussian_1d(&input_1d_owned, sigma[0])?;
79            result
80                .into_dimensionality::<D>()
81                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
82        }
83        2 => {
84            // 2D case
85            let input_2d = input
86                .view()
87                .into_dimensionality::<scirs2_core::ndarray::Ix2>()
88                .map_err(|_| NdimageError::DimensionError("Failed to convert to 2D".into()))?;
89            let input_2d_owned = input_2d.to_owned();
90            let result = fourier_gaussian_2d(&input_2d_owned, sigma[0], sigma[1])?;
91            result
92                .into_dimensionality::<D>()
93                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
94        }
95        _ => {
96            // N-dimensional case (3D and higher)
97            let result = fourier_gaussian_nd(input, sigma)?;
98            Ok(result)
99        }
100    }
101}
102
103/// Apply 1D Gaussian filter in Fourier domain
104#[allow(dead_code)]
105fn fourier_gaussian_1d<T>(input: &Array1<T>, sigma: T) -> NdimageResult<Array1<T>>
106where
107    T: Float + FromPrimitive + NumCast + Debug + Clone,
108{
109    let n = input.len();
110
111    // Convert to f64 for FFT
112    let input_f64: Vec<f64> = input
113        .iter()
114        .map(|&x| NumCast::from(x).unwrap_or(0.0))
115        .collect();
116
117    // Perform FFT
118    let mut spectrum = fft(&input_f64, None)?;
119
120    // Get frequencies
121    let freqs = fftfreq(n, 1.0)?;
122
123    // Apply Gaussian filter
124    let sigma_f64: f64 = NumCast::from(sigma).unwrap_or(1.0);
125    let two_pi = 2.0 * PI;
126
127    for (i, freq) in freqs.iter().enumerate() {
128        let factor = (-0.5 * (two_pi * freq * sigma_f64).powi(2)).exp();
129        spectrum[i] *= factor;
130    }
131
132    // Inverse FFT
133    let result = ifft(&spectrum, None)?;
134
135    // Convert back to T and extract real part
136    let output: Array1<T> = Array1::from_vec(
137        result
138            .iter()
139            .map(|c| T::from_f64(c.re).unwrap_or(T::zero()))
140            .collect(),
141    );
142
143    Ok(output)
144}
145
146/// Apply 2D Gaussian filter in Fourier domain
147#[allow(dead_code)]
148fn fourier_gaussian_2d<T>(input: &Array2<T>, sigma_y: T, sigma_x: T) -> NdimageResult<Array2<T>>
149where
150    T: Float + FromPrimitive + NumCast + Debug + Clone,
151{
152    let (ny, nx) = input.dim();
153
154    // Convert to f64 Array2 for FFT
155    let mut input_f64 = Array2::<f64>::zeros((ny, nx));
156    for i in 0..ny {
157        for j in 0..nx {
158            input_f64[[i, j]] = NumCast::from(input[[i, j]]).unwrap_or(0.0);
159        }
160    }
161
162    // Perform 2D FFT
163    let spectrum = fft2(&input_f64, None, None, None)?;
164
165    // Get frequencies
166    let freqs_y = fftfreq(ny, 1.0)?;
167    let freqs_x = fftfreq(nx, 1.0)?;
168
169    // Apply Gaussian filter
170    let sigma_y_f64: f64 = NumCast::from(sigma_y).unwrap_or(1.0);
171    let sigma_x_f64: f64 = NumCast::from(sigma_x).unwrap_or(1.0);
172    let two_pi = 2.0 * PI;
173
174    let mut filtered_spectrum = spectrum.clone();
175    for (i, &fy) in freqs_y.iter().enumerate() {
176        for (j, &fx) in freqs_x.iter().enumerate() {
177            let factor_y = (-0.5 * (two_pi * fy * sigma_y_f64).powi(2)).exp();
178            let factor_x = (-0.5 * (two_pi * fx * sigma_x_f64).powi(2)).exp();
179            filtered_spectrum[[i, j]] *= factor_y * factor_x;
180        }
181    }
182
183    // Inverse FFT
184    let result = ifft2(&filtered_spectrum, None, None, None)?;
185
186    // Convert back to T and extract real part
187    let mut output = Array2::zeros((ny, nx));
188    for i in 0..ny {
189        for j in 0..nx {
190            output[[i, j]] = T::from_f64(result[[i, j]].re).unwrap_or(T::zero());
191        }
192    }
193
194    Ok(output)
195}
196
197/// Apply N-dimensional Gaussian filter in Fourier domain
198///
199/// This implementation uses separable 1D FFTs along each axis for efficiency
200#[allow(dead_code)]
201fn fourier_gaussian_nd<T, D>(input: &Array<T, D>, sigma: &[T]) -> NdimageResult<Array<T, D>>
202where
203    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
204    D: Dimension,
205{
206    if input.ndim() != sigma.len() {
207        return Err(NdimageError::InvalidInput(
208            "Sigma array length must match _input dimensions".into(),
209        ));
210    }
211
212    // For N-dimensional arrays, apply separable filtering
213    // Use a simplified approach that works for common 3D case
214    if input.ndim() == 3 {
215        return fourier_gaussian_3d(input, sigma);
216    }
217
218    // For higher dimensions, fall back to a general but simpler approach
219    // Convert to 1D, apply filter, and reshape back
220    let shape = input.shape().to_vec();
221    let flat_input = input.view().into_shape_with_order(input.len())?;
222
223    // Create equivalent 1D sigma (use geometric mean of all dimensions)
224    let sigma_1d = {
225        let product: f64 = sigma
226            .iter()
227            .map(|&s| NumCast::from(s).unwrap_or(1.0))
228            .fold(1.0, |acc, x| acc * x);
229        let geometric_mean = product.powf(1.0 / sigma.len() as f64);
230        T::from_f64(geometric_mean).unwrap_or(T::one())
231    };
232
233    // Apply 1D filter (convert view to owned array)
234    let flat_owned = flat_input.to_owned();
235    let filtered_1d = fourier_gaussian_1d(&flat_owned, sigma_1d)?;
236
237    // Reshape back to original dimensions and convert to correct dimension type
238    let result_dyn = filtered_1d.into_shape_with_order(shape)?;
239    let result = result_dyn.into_dimensionality::<D>().map_err(|_| {
240        NdimageError::DimensionError("Failed to convert result to original dimension type".into())
241    })?;
242    Ok(result)
243}
244
245/// Apply 3D Gaussian filter in Fourier domain
246#[allow(dead_code)]
247fn fourier_gaussian_3d<T, D>(input: &Array<T, D>, sigma: &[T]) -> NdimageResult<Array<T, D>>
248where
249    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
250    D: Dimension,
251{
252    // Convert to 3D for processing
253    let input_3d = input
254        .view()
255        .into_dimensionality::<scirs2_core::ndarray::Ix3>()
256        .map_err(|_| NdimageError::DimensionError("Failed to convert to 3D".into()))?;
257
258    let (nz, ny, nx) = input_3d.dim();
259
260    // Convert sigma values
261    let sigma_z: f64 = NumCast::from(sigma[0]).unwrap_or(1.0);
262    let sigma_y: f64 = NumCast::from(sigma[1]).unwrap_or(1.0);
263    let sigma_x: f64 = NumCast::from(sigma[2]).unwrap_or(1.0);
264
265    // Convert _input to f64 for FFT processing
266    let mut input_f64 = Array::zeros((nz, ny, nx));
267    for i in 0..nz {
268        for j in 0..ny {
269            for k in 0..nx {
270                input_f64[[i, j, k]] = NumCast::from(input_3d[[i, j, k]]).unwrap_or(0.0);
271            }
272        }
273    }
274
275    // Apply separable filtering along each axis
276    let two_pi = 2.0 * PI;
277
278    // Filter along z-axis (axis 0)
279    let freqs_z = fftfreq(nz, 1.0)?;
280    for j in 0..ny {
281        for k in 0..nx {
282            let slice: Vec<f64> = (0..nz).map(|i| input_f64[[i, j, k]]).collect();
283            let mut spectrum = fft(&slice, None)?;
284
285            for (i, &freq) in freqs_z.iter().enumerate() {
286                let factor = (-0.5 * (two_pi * freq * sigma_z).powi(2)).exp();
287                spectrum[i] *= factor;
288            }
289
290            let filtered = ifft(&spectrum, None)?;
291            for (i, &complex_val) in filtered.iter().enumerate() {
292                input_f64[[i, j, k]] = complex_val.re;
293            }
294        }
295    }
296
297    // Filter along y-axis (axis 1)
298    let freqs_y = fftfreq(ny, 1.0)?;
299    for i in 0..nz {
300        for k in 0..nx {
301            let slice: Vec<f64> = (0..ny).map(|j| input_f64[[i, j, k]]).collect();
302            let mut spectrum = fft(&slice, None)?;
303
304            for (j, &freq) in freqs_y.iter().enumerate() {
305                let factor = (-0.5 * (two_pi * freq * sigma_y).powi(2)).exp();
306                spectrum[j] *= factor;
307            }
308
309            let filtered = ifft(&spectrum, None)?;
310            for (j, &complex_val) in filtered.iter().enumerate() {
311                input_f64[[i, j, k]] = complex_val.re;
312            }
313        }
314    }
315
316    // Filter along x-axis (axis 2)
317    let freqs_x = fftfreq(nx, 1.0)?;
318    for i in 0..nz {
319        for j in 0..ny {
320            let slice: Vec<f64> = (0..nx).map(|k| input_f64[[i, j, k]]).collect();
321            let mut spectrum = fft(&slice, None)?;
322
323            for (k, &freq) in freqs_x.iter().enumerate() {
324                let factor = (-0.5 * (two_pi * freq * sigma_x).powi(2)).exp();
325                spectrum[k] *= factor;
326            }
327
328            let filtered = ifft(&spectrum, None)?;
329            for (k, &complex_val) in filtered.iter().enumerate() {
330                input_f64[[i, j, k]] = complex_val.re;
331            }
332        }
333    }
334
335    // Convert back to T
336    let mut output_3d = Array::zeros((nz, ny, nx));
337    for i in 0..nz {
338        for j in 0..ny {
339            for k in 0..nx {
340                output_3d[[i, j, k]] = T::from_f64(input_f64[[i, j, k]]).unwrap_or(T::zero());
341            }
342        }
343    }
344
345    // Convert back to original dimension type
346    let result = output_3d
347        .into_dimensionality::<D>()
348        .map_err(|_| NdimageError::DimensionError("Failed to convert back from 3D".into()))?;
349
350    Ok(result)
351}
352
353/// Apply a uniform (box) filter in the Fourier domain
354///
355/// # Arguments
356///
357/// * `input` - Input array to filter
358/// * `size` - Size of the box filter in each dimension
359///
360/// # Returns
361///
362/// * `Result<Array<T, D>>` - Filtered array
363#[allow(dead_code)]
364pub fn fourier_uniform<T, D>(input: &Array<T, D>, size: &[usize]) -> NdimageResult<Array<T, D>>
365where
366    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
367    D: Dimension,
368{
369    // Validate inputs
370    if input.ndim() == 0 {
371        return Err(NdimageError::InvalidInput(
372            "Input array cannot be 0-dimensional".into(),
373        ));
374    }
375
376    if size.len() != input.ndim() {
377        return Err(NdimageError::DimensionError(format!(
378            "Size must have same length as _input dimensions (got {} expected {})",
379            size.len(),
380            input.ndim()
381        )));
382    }
383
384    for &s in size {
385        if s == 0 {
386            return Err(NdimageError::InvalidInput(
387                "Filter size cannot be zero".into(),
388            ));
389        }
390    }
391
392    match input.ndim() {
393        1 => {
394            let input_1d = input
395                .view()
396                .into_dimensionality::<scirs2_core::ndarray::Ix1>()
397                .map_err(|_| NdimageError::DimensionError("Failed to convert to 1D".into()))?;
398            let input_1d_owned = input_1d.to_owned();
399            let result = fourier_uniform_1d(&input_1d_owned, size[0])?;
400            result
401                .into_dimensionality::<D>()
402                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
403        }
404        2 => {
405            let input_2d = input
406                .view()
407                .into_dimensionality::<scirs2_core::ndarray::Ix2>()
408                .map_err(|_| NdimageError::DimensionError("Failed to convert to 2D".into()))?;
409            let input_2d_owned = input_2d.to_owned();
410            let result = fourier_uniform_2d(&input_2d_owned, size[0], size[1])?;
411            result
412                .into_dimensionality::<D>()
413                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
414        }
415        _ => {
416            // N-dimensional case (3D and higher)
417            let result = fourier_uniform_nd(input, size)?;
418            Ok(result)
419        }
420    }
421}
422
423/// Apply 1D uniform filter in Fourier domain
424#[allow(dead_code)]
425fn fourier_uniform_1d<T>(input: &Array1<T>, size: usize) -> NdimageResult<Array1<T>>
426where
427    T: Float + FromPrimitive + NumCast + Debug + Clone,
428{
429    let n = input.len();
430
431    // Convert to f64 for FFT
432    let input_f64: Vec<f64> = input
433        .iter()
434        .map(|&x| NumCast::from(x).unwrap_or(0.0))
435        .collect();
436
437    // Perform FFT
438    let mut spectrum = fft(&input_f64, None)?;
439
440    // Get frequencies
441    let freqs = fftfreq(n, 1.0)?;
442
443    // Apply sinc filter (Fourier transform of box function)
444    for (i, freq) in freqs.iter().enumerate() {
445        let x = size as f64 * freq * 2.0 * PI;
446        let sinc = if x.abs() < 1e-10 { 1.0 } else { x.sin() / x };
447        spectrum[i] *= sinc;
448    }
449
450    // Inverse FFT
451    let result = ifft(&spectrum, None)?;
452
453    // Convert back to T and extract real part
454    let output: Array1<T> = Array1::from_vec(
455        result
456            .iter()
457            .map(|c| T::from_f64(c.re).unwrap_or(T::zero()))
458            .collect(),
459    );
460
461    Ok(output)
462}
463
464/// Apply 2D uniform filter in Fourier domain
465#[allow(dead_code)]
466fn fourier_uniform_2d<T>(
467    input: &Array2<T>,
468    size_y: usize,
469    size_x: usize,
470) -> NdimageResult<Array2<T>>
471where
472    T: Float + FromPrimitive + NumCast + Debug + Clone,
473{
474    let (ny, nx) = input.dim();
475
476    // Convert to f64 Array2 for FFT
477    let mut input_f64 = Array2::<f64>::zeros((ny, nx));
478    for i in 0..ny {
479        for j in 0..nx {
480            input_f64[[i, j]] = NumCast::from(input[[i, j]]).unwrap_or(0.0);
481        }
482    }
483
484    // Perform 2D FFT
485    let spectrum = fft2(&input_f64, None, None, None)?;
486
487    // Get frequencies
488    let freqs_y = fftfreq(ny, 1.0)?;
489    let freqs_x = fftfreq(nx, 1.0)?;
490
491    // Apply sinc filter
492    let mut filtered_spectrum = spectrum.clone();
493    for (i, &fy) in freqs_y.iter().enumerate() {
494        for (j, &fx) in freqs_x.iter().enumerate() {
495            let x_y = size_y as f64 * fy * 2.0 * PI;
496            let x_x = size_x as f64 * fx * 2.0 * PI;
497
498            let sinc_y = if x_y.abs() < 1e-10 {
499                1.0
500            } else {
501                x_y.sin() / x_y
502            };
503            let sinc_x = if x_x.abs() < 1e-10 {
504                1.0
505            } else {
506                x_x.sin() / x_x
507            };
508
509            filtered_spectrum[[i, j]] *= sinc_y * sinc_x;
510        }
511    }
512
513    // Inverse FFT
514    let result = ifft2(&filtered_spectrum, None, None, None)?;
515
516    // Convert back to T and extract real part
517    let mut output = Array2::zeros((ny, nx));
518    for i in 0..ny {
519        for j in 0..nx {
520            output[[i, j]] = T::from_f64(result[[i, j]].re).unwrap_or(T::zero());
521        }
522    }
523
524    Ok(output)
525}
526
527/// Apply N-dimensional uniform filter in Fourier domain
528#[allow(dead_code)]
529fn fourier_uniform_nd<T, D>(input: &Array<T, D>, size: &[usize]) -> NdimageResult<Array<T, D>>
530where
531    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
532    D: Dimension,
533{
534    if input.ndim() != size.len() {
535        return Err(NdimageError::InvalidInput(
536            "Size array length must match _input dimensions".into(),
537        ));
538    }
539
540    // For 3D case, implement separable filtering
541    if input.ndim() == 3 {
542        return fourier_uniform_3d(input, size);
543    }
544
545    // For higher dimensions, use simplified approach
546    let shape = input.shape().to_vec();
547    let flat_input = input.view().into_shape_with_order(input.len())?;
548
549    // Create equivalent 1D size (use geometric mean)
550    let size_1d = {
551        let product: f64 = size.iter().map(|&s| s as f64).fold(1.0, |acc, x| acc * x);
552        let geometric_mean = product.powf(1.0 / size.len() as f64);
553        geometric_mean.round() as usize
554    };
555
556    // Apply 1D filter (convert view to owned array)
557    let flat_owned = flat_input.to_owned();
558    let filtered_1d = fourier_uniform_1d(&flat_owned, size_1d)?;
559
560    // Reshape back to original dimensions and convert to correct dimension type
561    let result_dyn = filtered_1d.into_shape_with_order(shape)?;
562    let result = result_dyn.into_dimensionality::<D>().map_err(|_| {
563        NdimageError::DimensionError("Failed to convert result to original dimension type".into())
564    })?;
565    Ok(result)
566}
567
568/// Apply 3D uniform filter in Fourier domain
569#[allow(dead_code)]
570fn fourier_uniform_3d<T, D>(input: &Array<T, D>, size: &[usize]) -> NdimageResult<Array<T, D>>
571where
572    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
573    D: Dimension,
574{
575    // Convert to 3D for processing
576    let input_3d = input
577        .view()
578        .into_dimensionality::<scirs2_core::ndarray::Ix3>()
579        .map_err(|_| NdimageError::DimensionError("Failed to convert to 3D".into()))?;
580
581    let (nz, ny, nx) = input_3d.dim();
582
583    // Convert _input to f64 for FFT processing
584    let mut input_f64 = Array::zeros((nz, ny, nx));
585    for i in 0..nz {
586        for j in 0..ny {
587            for k in 0..nx {
588                input_f64[[i, j, k]] = NumCast::from(input_3d[[i, j, k]]).unwrap_or(0.0);
589            }
590        }
591    }
592
593    // Apply separable uniform filtering along each axis
594    let two_pi = 2.0 * PI;
595
596    // Filter along z-axis (axis 0)
597    let freqs_z = fftfreq(nz, 1.0)?;
598    for j in 0..ny {
599        for k in 0..nx {
600            let slice: Vec<f64> = (0..nz).map(|i| input_f64[[i, j, k]]).collect();
601            let mut spectrum = fft(&slice, None)?;
602
603            for (i, &freq) in freqs_z.iter().enumerate() {
604                let x = size[0] as f64 * freq * two_pi;
605                let sinc_factor = if x.abs() < 1e-10 { 1.0 } else { x.sin() / x };
606                spectrum[i] *= sinc_factor;
607            }
608
609            let filtered = ifft(&spectrum, None)?;
610            for (i, &complex_val) in filtered.iter().enumerate() {
611                input_f64[[i, j, k]] = complex_val.re;
612            }
613        }
614    }
615
616    // Filter along y-axis (axis 1)
617    let freqs_y = fftfreq(ny, 1.0)?;
618    for i in 0..nz {
619        for k in 0..nx {
620            let slice: Vec<f64> = (0..ny).map(|j| input_f64[[i, j, k]]).collect();
621            let mut spectrum = fft(&slice, None)?;
622
623            for (j, &freq) in freqs_y.iter().enumerate() {
624                let x = size[1] as f64 * freq * two_pi;
625                let sinc_factor = if x.abs() < 1e-10 { 1.0 } else { x.sin() / x };
626                spectrum[j] *= sinc_factor;
627            }
628
629            let filtered = ifft(&spectrum, None)?;
630            for (j, &complex_val) in filtered.iter().enumerate() {
631                input_f64[[i, j, k]] = complex_val.re;
632            }
633        }
634    }
635
636    // Filter along x-axis (axis 2)
637    let freqs_x = fftfreq(nx, 1.0)?;
638    for i in 0..nz {
639        for j in 0..ny {
640            let slice: Vec<f64> = (0..nx).map(|k| input_f64[[i, j, k]]).collect();
641            let mut spectrum = fft(&slice, None)?;
642
643            for (k, &freq) in freqs_x.iter().enumerate() {
644                let x = size[2] as f64 * freq * two_pi;
645                let sinc_factor = if x.abs() < 1e-10 { 1.0 } else { x.sin() / x };
646                spectrum[k] *= sinc_factor;
647            }
648
649            let filtered = ifft(&spectrum, None)?;
650            for (k, &complex_val) in filtered.iter().enumerate() {
651                input_f64[[i, j, k]] = complex_val.re;
652            }
653        }
654    }
655
656    // Convert back to T
657    let mut output_3d = Array::zeros((nz, ny, nx));
658    for i in 0..nz {
659        for j in 0..ny {
660            for k in 0..nx {
661                output_3d[[i, j, k]] = T::from_f64(input_f64[[i, j, k]]).unwrap_or(T::zero());
662            }
663        }
664    }
665
666    // Convert back to original dimension type
667    let result = output_3d
668        .into_dimensionality::<D>()
669        .map_err(|_| NdimageError::DimensionError("Failed to convert back from 3D".into()))?;
670
671    Ok(result)
672}
673
674/// Apply an ellipsoid filter in the Fourier domain
675///
676/// This creates a filter that passes frequencies within an ellipsoid in frequency space.
677///
678/// # Arguments
679///
680/// * `input` - Input array to filter
681/// * `size` - Semi-axes lengths of the ellipsoid in each dimension
682/// * `mode` - 'lowpass' or 'highpass'
683///
684/// # Returns
685///
686/// * `Result<Array<T, D>>` - Filtered array
687#[allow(dead_code)]
688pub fn fourier_ellipsoid<T, D>(
689    input: &Array<T, D>,
690    size: &[T],
691    mode: &str,
692) -> NdimageResult<Array<T, D>>
693where
694    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
695    D: Dimension,
696{
697    // Validate inputs
698    if input.ndim() == 0 {
699        return Err(NdimageError::InvalidInput(
700            "Input array cannot be 0-dimensional".into(),
701        ));
702    }
703
704    if size.len() != input.ndim() {
705        return Err(NdimageError::DimensionError(format!(
706            "Size must have same length as input dimensions (got {} expected {})",
707            size.len(),
708            input.ndim()
709        )));
710    }
711
712    for &s in size {
713        if s <= T::zero() {
714            return Err(NdimageError::InvalidInput(
715                "Ellipsoid size must be positive".into(),
716            ));
717        }
718    }
719
720    let is_lowpass = match mode {
721        "lowpass" => true,
722        "highpass" => false,
723        _ => {
724            return Err(NdimageError::InvalidInput(
725                "Mode must be 'lowpass' or 'highpass'".into(),
726            ))
727        }
728    };
729
730    if input.ndim() == 2 {
731        let input_2d = input
732            .view()
733            .into_dimensionality::<scirs2_core::ndarray::Ix2>()
734            .map_err(|_| NdimageError::DimensionError("Failed to convert to 2D".into()))?;
735        let input_2d_owned = input_2d.to_owned();
736        let result = fourier_ellipsoid_2d(&input_2d_owned, size[0], size[1], is_lowpass)?;
737        result
738            .into_dimensionality::<D>()
739            .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
740    } else {
741        // N-dimensional case
742        fourier_ellipsoid_nd(input, size, is_lowpass)
743    }
744}
745
746/// Apply 2D ellipsoid filter in Fourier domain
747#[allow(dead_code)]
748fn fourier_ellipsoid_2d<T>(
749    input: &Array2<T>,
750    size_y: T,
751    size_x: T,
752    is_lowpass: bool,
753) -> NdimageResult<Array2<T>>
754where
755    T: Float + FromPrimitive + NumCast + Debug + Clone,
756{
757    let (ny, nx) = input.dim();
758
759    // Convert to f64 Array2 for FFT
760    let mut input_f64 = Array2::<f64>::zeros((ny, nx));
761    for i in 0..ny {
762        for j in 0..nx {
763            input_f64[[i, j]] = NumCast::from(input[[i, j]]).unwrap_or(0.0);
764        }
765    }
766
767    // Perform 2D FFT
768    let spectrum = fft2(&input_f64, None, None, None)?;
769
770    // Get frequencies
771    let freqs_y = fftfreq(ny, 1.0)?;
772    let freqs_x = fftfreq(nx, 1.0)?;
773
774    // Convert sizes to f64
775    let size_y_f64: f64 = NumCast::from(size_y).unwrap_or(1.0);
776    let size_x_f64: f64 = NumCast::from(size_x).unwrap_or(1.0);
777
778    // Apply ellipsoid filter
779    let mut filtered_spectrum = spectrum.clone();
780    for (i, &fy) in freqs_y.iter().enumerate() {
781        for (j, &fx) in freqs_x.iter().enumerate() {
782            let dist_sq = (fy / size_y_f64).powi(2) + (fx / size_x_f64).powi(2);
783
784            let mask = if is_lowpass {
785                if dist_sq <= 1.0 {
786                    1.0
787                } else {
788                    0.0
789                }
790            } else {
791                if dist_sq > 1.0 {
792                    1.0
793                } else {
794                    0.0
795                }
796            };
797
798            filtered_spectrum[[i, j]] *= mask;
799        }
800    }
801
802    // Inverse FFT
803    let result = ifft2(&filtered_spectrum, None, None, None)?;
804
805    // Convert back to T and extract real part
806    let mut output = Array2::zeros((ny, nx));
807    for i in 0..ny {
808        for j in 0..nx {
809            output[[i, j]] = T::from_f64(result[[i, j]].re).unwrap_or(T::zero());
810        }
811    }
812
813    Ok(output)
814}
815
816/// Apply N-dimensional ellipsoid filter in Fourier domain
817#[allow(dead_code)]
818fn fourier_ellipsoid_nd<T, D>(
819    input: &Array<T, D>,
820    size: &[T],
821    is_lowpass: bool,
822) -> NdimageResult<Array<T, D>>
823where
824    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
825    D: Dimension,
826{
827    if input.ndim() != size.len() {
828        return Err(NdimageError::InvalidInput(
829            "Size array length must match input dimensions".into(),
830        ));
831    }
832
833    // For 3D case, implement proper ellipsoid filtering
834    if input.ndim() == 3 {
835        return fourier_ellipsoid_3d(input, size, is_lowpass);
836    }
837
838    // For higher dimensions, use simplified approach with radial symmetry
839    let shape = input.shape().to_vec();
840    let flat_input = input.view().into_shape_with_order(input.len())?;
841
842    // Create equivalent 1D size (use geometric mean)
843    let size_1d = {
844        let product: f64 = size
845            .iter()
846            .map(|&s| NumCast::from(s).unwrap_or(1.0))
847            .fold(1.0, |acc, x| acc * x);
848        let geometric_mean = product.powf(1.0 / size.len() as f64);
849        T::from_f64(geometric_mean).unwrap_or(T::one())
850    };
851
852    // Apply 2D ellipsoid filter as approximation
853    let side_len = (flat_input.len() as f64).sqrt() as usize;
854    let temp_2d = flat_input
855        .view()
856        .into_shape_with_order((side_len, flat_input.len() / side_len))?;
857    let filtered_2d = fourier_ellipsoid_2d(&temp_2d.to_owned(), size_1d, size_1d, is_lowpass)?;
858    let filtered_1d = filtered_2d.into_shape_with_order(input.len())?;
859
860    // Reshape back to original dimensions
861    let result_dyn = filtered_1d.into_shape_with_order(shape)?;
862    let result = result_dyn.into_dimensionality::<D>().map_err(|_| {
863        NdimageError::DimensionError("Failed to convert result back to original dimensions".into())
864    })?;
865    Ok(result)
866}
867
868/// Apply 3D ellipsoid filter in Fourier domain
869#[allow(dead_code)]
870fn fourier_ellipsoid_3d<T, D>(
871    input: &Array<T, D>,
872    size: &[T],
873    is_lowpass: bool,
874) -> NdimageResult<Array<T, D>>
875where
876    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
877    D: Dimension,
878{
879    // Convert to 3D for processing
880    let input_3d = input
881        .view()
882        .into_dimensionality::<scirs2_core::ndarray::Ix3>()
883        .map_err(|_| NdimageError::DimensionError("Failed to convert to 3D".into()))?;
884
885    let (nz, ny, nx) = input_3d.dim();
886
887    // Convert size values
888    let size_z: f64 = NumCast::from(size[0]).unwrap_or(1.0);
889    let size_y: f64 = NumCast::from(size[1]).unwrap_or(1.0);
890    let size_x: f64 = NumCast::from(size[2]).unwrap_or(1.0);
891
892    // Convert input to f64 for FFT processing
893    let mut input_f64 = Array::zeros((nz, ny, nx));
894    for i in 0..nz {
895        for j in 0..ny {
896            for k in 0..nx {
897                input_f64[[i, j, k]] = NumCast::from(input_3d[[i, j, k]]).unwrap_or(0.0);
898            }
899        }
900    }
901
902    // Get frequency arrays for each dimension
903    let freqs_z = fftfreq(nz, 1.0)?;
904    let freqs_y = fftfreq(ny, 1.0)?;
905    let freqs_x = fftfreq(nx, 1.0)?;
906
907    // Apply FFT along each axis and filter in frequency domain
908    // This is a simplified approach - for full 3D FFT, we'd need scirs2-fft to support 3D FFT
909
910    // Filter along z-axis first
911    for j in 0..ny {
912        for k in 0..nx {
913            let slice: Vec<f64> = (0..nz).map(|i| input_f64[[i, j, k]]).collect();
914            let mut spectrum = fft(&slice, None)?;
915
916            // Apply ellipsoid filter for this frequency component
917            for (i, &freq_z) in freqs_z.iter().enumerate() {
918                // For ellipsoid, check if frequency is within ellipsoid bounds
919                let normalized_freq_z = freq_z * size_z;
920                let freq_magnitude = normalized_freq_z.abs();
921
922                let within_ellipsoid = freq_magnitude <= 1.0;
923
924                if (is_lowpass && within_ellipsoid) || (!is_lowpass && !within_ellipsoid) {
925                    // Keep the frequency component
926                } else {
927                    spectrum[i] *= 0.0; // Zero out the frequency component
928                }
929            }
930
931            let filtered = ifft(&spectrum, None)?;
932            for (i, &complex_val) in filtered.iter().enumerate() {
933                input_f64[[i, j, k]] = complex_val.re;
934            }
935        }
936    }
937
938    // Filter along y-axis
939    for i in 0..nz {
940        for k in 0..nx {
941            let slice: Vec<f64> = (0..ny).map(|j| input_f64[[i, j, k]]).collect();
942            let mut spectrum = fft(&slice, None)?;
943
944            for (j, &freq_y) in freqs_y.iter().enumerate() {
945                let normalized_freq_y = freq_y * size_y;
946                let freq_magnitude = normalized_freq_y.abs();
947
948                let within_ellipsoid = freq_magnitude <= 1.0;
949
950                if (is_lowpass && within_ellipsoid) || (!is_lowpass && !within_ellipsoid) {
951                    // Keep the frequency component
952                } else {
953                    spectrum[j] *= 0.0; // Zero out the frequency component
954                }
955            }
956
957            let filtered = ifft(&spectrum, None)?;
958            for (j, &complex_val) in filtered.iter().enumerate() {
959                input_f64[[i, j, k]] = complex_val.re;
960            }
961        }
962    }
963
964    // Filter along x-axis
965    for i in 0..nz {
966        for j in 0..ny {
967            let slice: Vec<f64> = (0..nx).map(|k| input_f64[[i, j, k]]).collect();
968            let mut spectrum = fft(&slice, None)?;
969
970            for (k, &freq_x) in freqs_x.iter().enumerate() {
971                let normalized_freq_x = freq_x * size_x;
972                let freq_magnitude = normalized_freq_x.abs();
973
974                let within_ellipsoid = freq_magnitude <= 1.0;
975
976                if (is_lowpass && within_ellipsoid) || (!is_lowpass && !within_ellipsoid) {
977                    // Keep the frequency component
978                } else {
979                    spectrum[k] *= 0.0; // Zero out the frequency component
980                }
981            }
982
983            let filtered = ifft(&spectrum, None)?;
984            for (k, &complex_val) in filtered.iter().enumerate() {
985                input_f64[[i, j, k]] = complex_val.re;
986            }
987        }
988    }
989
990    // Convert back to T
991    let mut output_3d = Array::zeros((nz, ny, nx));
992    for i in 0..nz {
993        for j in 0..ny {
994            for k in 0..nx {
995                output_3d[[i, j, k]] = T::from_f64(input_f64[[i, j, k]]).unwrap_or(T::zero());
996            }
997        }
998    }
999
1000    // Convert back to original dimension type
1001    let result = output_3d
1002        .into_dimensionality::<D>()
1003        .map_err(|_| NdimageError::DimensionError("Failed to convert back from 3D".into()))?;
1004
1005    Ok(result)
1006}
1007
1008/// Shift an array in the Fourier domain
1009///
1010/// This is equivalent to rolling the array but can be more efficient
1011/// for non-integer shifts or when combined with other Fourier operations.
1012///
1013/// # Arguments
1014///
1015/// * `input` - Input array to shift
1016/// * `shift` - Shift amount for each axis (can be fractional)
1017///
1018/// # Returns
1019///
1020/// * `Result<Array<T, D>>` - Shifted array
1021#[allow(dead_code)]
1022pub fn fourier_shift<T, D>(input: &Array<T, D>, shift: &[T]) -> NdimageResult<Array<T, D>>
1023where
1024    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1025    D: Dimension,
1026{
1027    // Validate inputs
1028    if input.ndim() == 0 {
1029        return Err(NdimageError::InvalidInput(
1030            "Input array cannot be 0-dimensional".into(),
1031        ));
1032    }
1033
1034    if shift.len() != input.ndim() {
1035        return Err(NdimageError::DimensionError(format!(
1036            "Shift must have same length as _input dimensions (got {} expected {})",
1037            shift.len(),
1038            input.ndim()
1039        )));
1040    }
1041
1042    match input.ndim() {
1043        1 => {
1044            let input_1d = input
1045                .view()
1046                .into_dimensionality::<scirs2_core::ndarray::Ix1>()
1047                .map_err(|_| NdimageError::DimensionError("Failed to convert to 1D".into()))?;
1048            let input_1d_owned = input_1d.to_owned();
1049            let result = fourier_shift_1d(&input_1d_owned, shift[0])?;
1050            result
1051                .into_dimensionality::<D>()
1052                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
1053        }
1054        2 => {
1055            let input_2d = input
1056                .view()
1057                .into_dimensionality::<scirs2_core::ndarray::Ix2>()
1058                .map_err(|_| NdimageError::DimensionError("Failed to convert to 2D".into()))?;
1059            let input_2d_owned = input_2d.to_owned();
1060            let result = fourier_shift_2d(&input_2d_owned, shift[0], shift[1])?;
1061            result
1062                .into_dimensionality::<D>()
1063                .map_err(|_| NdimageError::DimensionError("Failed to convert back".into()))
1064        }
1065        _ => {
1066            // N-dimensional case (3D and higher)
1067            let result = fourier_shift_nd(input, shift)?;
1068            Ok(result)
1069        }
1070    }
1071}
1072
1073/// Apply 1D shift in Fourier domain
1074#[allow(dead_code)]
1075fn fourier_shift_1d<T>(input: &Array1<T>, shift: T) -> NdimageResult<Array1<T>>
1076where
1077    T: Float + FromPrimitive + NumCast + Debug + Clone,
1078{
1079    let n = input.len();
1080
1081    // Convert to f64 for FFT
1082    let input_f64: Vec<f64> = input
1083        .iter()
1084        .map(|&x| NumCast::from(x).unwrap_or(0.0))
1085        .collect();
1086
1087    // Perform FFT
1088    let mut spectrum = fft(&input_f64, None)?;
1089
1090    // Get frequencies
1091    let freqs = fftfreq(n, 1.0)?;
1092
1093    // Apply phase shift
1094    let shift_f64: f64 = NumCast::from(shift).unwrap_or(0.0);
1095    let two_pi = 2.0 * PI;
1096
1097    for (i, freq) in freqs.iter().enumerate() {
1098        let phase = -two_pi * freq * shift_f64;
1099        let shift_factor = Complex64::new(phase.cos(), phase.sin());
1100        spectrum[i] *= shift_factor;
1101    }
1102
1103    // Inverse FFT
1104    let result = ifft(&spectrum, None)?;
1105
1106    // Convert back to T and extract real part
1107    let output: Array1<T> = Array1::from_vec(
1108        result
1109            .iter()
1110            .map(|c| T::from_f64(c.re).unwrap_or(T::zero()))
1111            .collect(),
1112    );
1113
1114    Ok(output)
1115}
1116
1117/// Apply 2D shift in Fourier domain
1118#[allow(dead_code)]
1119fn fourier_shift_2d<T>(input: &Array2<T>, shift_y: T, shift_x: T) -> NdimageResult<Array2<T>>
1120where
1121    T: Float + FromPrimitive + NumCast + Debug + Clone,
1122{
1123    let (ny, nx) = input.dim();
1124
1125    // Convert to f64 Array2 for FFT
1126    let mut input_f64 = Array2::<f64>::zeros((ny, nx));
1127    for i in 0..ny {
1128        for j in 0..nx {
1129            input_f64[[i, j]] = NumCast::from(input[[i, j]]).unwrap_or(0.0);
1130        }
1131    }
1132
1133    // Perform 2D FFT
1134    let spectrum = fft2(&input_f64, None, None, None)?;
1135
1136    // Get frequencies
1137    let freqs_y = fftfreq(ny, 1.0)?;
1138    let freqs_x = fftfreq(nx, 1.0)?;
1139
1140    // Convert shifts to f64
1141    let shift_y_f64: f64 = NumCast::from(shift_y).unwrap_or(0.0);
1142    let shift_x_f64: f64 = NumCast::from(shift_x).unwrap_or(0.0);
1143    let two_pi = 2.0 * PI;
1144
1145    // Apply phase shift
1146    let mut shifted_spectrum = spectrum.clone();
1147    for (i, &fy) in freqs_y.iter().enumerate() {
1148        for (j, &fx) in freqs_x.iter().enumerate() {
1149            let phase = -two_pi * (fy * shift_y_f64 + fx * shift_x_f64);
1150            let shift_factor = Complex64::new(phase.cos(), phase.sin());
1151            shifted_spectrum[[i, j]] *= shift_factor;
1152        }
1153    }
1154
1155    // Inverse FFT
1156    let result = ifft2(&shifted_spectrum, None, None, None)?;
1157
1158    // Convert back to T and extract real part
1159    let mut output = Array2::zeros((ny, nx));
1160    for i in 0..ny {
1161        for j in 0..nx {
1162            output[[i, j]] = T::from_f64(result[[i, j]].re).unwrap_or(T::zero());
1163        }
1164    }
1165
1166    Ok(output)
1167}
1168
1169/// Apply N-dimensional shift in Fourier domain
1170#[allow(dead_code)]
1171fn fourier_shift_nd<T, D>(input: &Array<T, D>, shift: &[T]) -> NdimageResult<Array<T, D>>
1172where
1173    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1174    D: Dimension,
1175{
1176    if input.ndim() != shift.len() {
1177        return Err(NdimageError::InvalidInput(
1178            "Shift array length must match _input dimensions".into(),
1179        ));
1180    }
1181
1182    // For 3D case, implement separable shifting
1183    if input.ndim() == 3 {
1184        return fourier_shift_3d(input, shift);
1185    }
1186
1187    // For higher dimensions, use simplified approach
1188    let shape = input.shape().to_vec();
1189    let flat_input = input.view().into_shape_with_order(input.len())?;
1190
1191    // Create equivalent 1D shift (use mean of all shifts)
1192    let shift_1d = {
1193        let sum: f64 = shift.iter().map(|&s| NumCast::from(s).unwrap_or(0.0)).sum();
1194        let mean = sum / shift.len() as f64;
1195        T::from_f64(mean).unwrap_or(T::zero())
1196    };
1197
1198    // Apply 1D shift
1199    let shifted_1d = fourier_shift_1d(&flat_input.to_owned(), shift_1d)?;
1200
1201    // Reshape back to original dimensions
1202    let result_dyn = shifted_1d.into_shape_with_order(shape)?;
1203    let result = result_dyn.into_dimensionality::<D>().map_err(|_| {
1204        NdimageError::DimensionError("Failed to convert result back to original dimensions".into())
1205    })?;
1206    Ok(result)
1207}
1208
1209/// Apply 3D shift in Fourier domain
1210#[allow(dead_code)]
1211fn fourier_shift_3d<T, D>(input: &Array<T, D>, shift: &[T]) -> NdimageResult<Array<T, D>>
1212where
1213    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1214    D: Dimension,
1215{
1216    // Convert to 3D for processing
1217    let input_3d = input
1218        .view()
1219        .into_dimensionality::<scirs2_core::ndarray::Ix3>()
1220        .map_err(|_| NdimageError::DimensionError("Failed to convert to 3D".into()))?;
1221
1222    let (nz, ny, nx) = input_3d.dim();
1223
1224    // Convert shift values
1225    let shift_z: f64 = NumCast::from(shift[0]).unwrap_or(0.0);
1226    let shift_y: f64 = NumCast::from(shift[1]).unwrap_or(0.0);
1227    let shift_x: f64 = NumCast::from(shift[2]).unwrap_or(0.0);
1228
1229    // Convert _input to f64 for FFT processing
1230    let mut input_f64 = Array::zeros((nz, ny, nx));
1231    for i in 0..nz {
1232        for j in 0..ny {
1233            for k in 0..nx {
1234                input_f64[[i, j, k]] = NumCast::from(input_3d[[i, j, k]]).unwrap_or(0.0);
1235            }
1236        }
1237    }
1238
1239    let two_pi = 2.0 * PI;
1240
1241    // Apply separable shifting along each axis
1242
1243    // Shift along z-axis (axis 0)
1244    let freqs_z = fftfreq(nz, 1.0)?;
1245    for j in 0..ny {
1246        for k in 0..nx {
1247            let slice: Vec<f64> = (0..nz).map(|i| input_f64[[i, j, k]]).collect();
1248            let mut spectrum = fft(&slice, None)?;
1249
1250            // Apply phase shift
1251            for (i, &freq) in freqs_z.iter().enumerate() {
1252                let phase = -two_pi * freq * shift_z;
1253                let shift_factor = Complex64::new(phase.cos(), phase.sin());
1254                spectrum[i] *= shift_factor;
1255            }
1256
1257            let shifted = ifft(&spectrum, None)?;
1258            for (i, &complex_val) in shifted.iter().enumerate() {
1259                input_f64[[i, j, k]] = complex_val.re;
1260            }
1261        }
1262    }
1263
1264    // Shift along y-axis (axis 1)
1265    let freqs_y = fftfreq(ny, 1.0)?;
1266    for i in 0..nz {
1267        for k in 0..nx {
1268            let slice: Vec<f64> = (0..ny).map(|j| input_f64[[i, j, k]]).collect();
1269            let mut spectrum = fft(&slice, None)?;
1270
1271            for (j, &freq) in freqs_y.iter().enumerate() {
1272                let phase = -two_pi * freq * shift_y;
1273                let shift_factor = Complex64::new(phase.cos(), phase.sin());
1274                spectrum[j] *= shift_factor;
1275            }
1276
1277            let shifted = ifft(&spectrum, None)?;
1278            for (j, &complex_val) in shifted.iter().enumerate() {
1279                input_f64[[i, j, k]] = complex_val.re;
1280            }
1281        }
1282    }
1283
1284    // Shift along x-axis (axis 2)
1285    let freqs_x = fftfreq(nx, 1.0)?;
1286    for i in 0..nz {
1287        for j in 0..ny {
1288            let slice: Vec<f64> = (0..nx).map(|k| input_f64[[i, j, k]]).collect();
1289            let mut spectrum = fft(&slice, None)?;
1290
1291            for (k, &freq) in freqs_x.iter().enumerate() {
1292                let phase = -two_pi * freq * shift_x;
1293                let shift_factor = Complex64::new(phase.cos(), phase.sin());
1294                spectrum[k] *= shift_factor;
1295            }
1296
1297            let shifted = ifft(&spectrum, None)?;
1298            for (k, &complex_val) in shifted.iter().enumerate() {
1299                input_f64[[i, j, k]] = complex_val.re;
1300            }
1301        }
1302    }
1303
1304    // Convert back to T
1305    let mut output_3d = Array::zeros((nz, ny, nx));
1306    for i in 0..nz {
1307        for j in 0..ny {
1308            for k in 0..nx {
1309                output_3d[[i, j, k]] = T::from_f64(input_f64[[i, j, k]]).unwrap_or(T::zero());
1310            }
1311        }
1312    }
1313
1314    // Convert back to original dimension type
1315    let result = output_3d
1316        .into_dimensionality::<D>()
1317        .map_err(|_| NdimageError::DimensionError("Failed to convert back from 3D".into()))?;
1318
1319    Ok(result)
1320}
1321
1322// Streaming implementations for large datasets
1323use crate::streaming::{OverlapInfo, StreamConfig, StreamProcessor, StreamableOp};
1324use scirs2_core::ndarray::{ArrayView, ArrayViewMut};
1325use std::path::Path;
1326
1327/// Streaming Fourier Gaussian filter for large datasets
1328pub struct StreamingFourierGaussian<T> {
1329    sigma: Vec<T>,
1330}
1331
1332impl<T: Float + FromPrimitive + NumCast + Debug + Clone> StreamingFourierGaussian<T> {
1333    pub fn new(sigma: Vec<T>) -> Self {
1334        Self { sigma }
1335    }
1336}
1337
1338impl<T, D> StreamableOp<T, D> for StreamingFourierGaussian<T>
1339where
1340    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1341    D: Dimension,
1342{
1343    fn apply_chunk(&self, chunk: &ArrayView<T, D>) -> NdimageResult<Array<T, D>> {
1344        fourier_gaussian(&chunk.to_owned(), &self.sigma, None)
1345    }
1346
1347    fn required_overlap(&self) -> Vec<usize> {
1348        // Fourier filters need minimal overlap due to periodic boundary conditions
1349        vec![8; self.sigma.len()]
1350    }
1351
1352    fn merge_overlap(
1353        &self,
1354        output: &mut ArrayViewMut<T, D>,
1355        new_chunk: &ArrayView<T, D>,
1356        overlap_info: &OverlapInfo,
1357    ) -> NdimageResult<()> {
1358        // Fourier domain filters handle boundaries naturally
1359        Ok(())
1360    }
1361}
1362
1363/// Process a large image file with Fourier Gaussian filter
1364#[allow(dead_code)]
1365pub fn fourier_gaussian_file<T>(
1366    input_path: &Path,
1367    output_path: &Path,
1368    shape: &[usize],
1369    sigma: &[T],
1370    config: Option<StreamConfig>,
1371) -> NdimageResult<()>
1372where
1373    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1374{
1375    let op = StreamingFourierGaussian::new(sigma.to_vec());
1376    let config = config.unwrap_or_else(|| StreamConfig {
1377        chunk_size: 256 * 1024 * 1024, // 256MB chunks for FFT efficiency
1378        ..Default::default()
1379    });
1380
1381    let processor = StreamProcessor::<T>::new(config);
1382
1383    // Dynamic dispatch based on dimensionality
1384    match shape.len() {
1385        1 => processor.process_file::<scirs2_core::ndarray::Ix1, StreamingFourierGaussian<T>>(
1386            input_path,
1387            output_path,
1388            shape,
1389            op,
1390        ),
1391        2 => processor.process_file::<scirs2_core::ndarray::Ix2, StreamingFourierGaussian<T>>(
1392            input_path,
1393            output_path,
1394            shape,
1395            op,
1396        ),
1397        3 => processor.process_file::<scirs2_core::ndarray::Ix3, StreamingFourierGaussian<T>>(
1398            input_path,
1399            output_path,
1400            shape,
1401            op,
1402        ),
1403        _ => processor.process_file::<scirs2_core::ndarray::IxDyn, StreamingFourierGaussian<T>>(
1404            input_path,
1405            output_path,
1406            shape,
1407            op,
1408        ),
1409    }
1410}
1411
1412/// Streaming Fourier uniform filter
1413pub struct StreamingFourierUniform<T> {
1414    size: Vec<T>,
1415}
1416
1417impl<T: Float + FromPrimitive + NumCast + Debug + Clone> StreamingFourierUniform<T> {
1418    pub fn new(size: Vec<T>) -> Self {
1419        Self { size }
1420    }
1421}
1422
1423impl<T, D> StreamableOp<T, D> for StreamingFourierUniform<T>
1424where
1425    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1426    D: Dimension,
1427{
1428    fn apply_chunk(&self, chunk: &ArrayView<T, D>) -> NdimageResult<Array<T, D>> {
1429        let size_usize: Vec<usize> = self
1430            .size
1431            .iter()
1432            .map(|&s| s.to_usize().unwrap_or(1))
1433            .collect();
1434        fourier_uniform(&chunk.to_owned(), &size_usize)
1435    }
1436
1437    fn required_overlap(&self) -> Vec<usize> {
1438        self.size
1439            .iter()
1440            .map(|&s| s.to_usize().unwrap_or(1))
1441            .collect()
1442    }
1443
1444    fn merge_overlap(
1445        &self,
1446        output: &mut ArrayViewMut<T, D>,
1447        new_chunk: &ArrayView<T, D>,
1448        overlap_info: &OverlapInfo,
1449    ) -> NdimageResult<()> {
1450        Ok(())
1451    }
1452}
1453
1454/// Process a large image file with Fourier uniform filter
1455#[allow(dead_code)]
1456pub fn fourier_uniform_file<T>(
1457    input_path: &Path,
1458    output_path: &Path,
1459    shape: &[usize],
1460    size: &[T],
1461    config: Option<StreamConfig>,
1462) -> NdimageResult<()>
1463where
1464    T: Float + FromPrimitive + NumCast + Debug + Clone + Send + Sync + 'static,
1465{
1466    let op = StreamingFourierUniform::new(size.to_vec());
1467    let config = config.unwrap_or_default();
1468    let processor = StreamProcessor::<T>::new(config);
1469
1470    match shape.len() {
1471        1 => processor.process_file::<scirs2_core::ndarray::Ix1, StreamingFourierUniform<T>>(
1472            input_path,
1473            output_path,
1474            shape,
1475            op,
1476        ),
1477        2 => processor.process_file::<scirs2_core::ndarray::Ix2, StreamingFourierUniform<T>>(
1478            input_path,
1479            output_path,
1480            shape,
1481            op,
1482        ),
1483        3 => processor.process_file::<scirs2_core::ndarray::Ix3, StreamingFourierUniform<T>>(
1484            input_path,
1485            output_path,
1486            shape,
1487            op,
1488        ),
1489        _ => processor.process_file::<scirs2_core::ndarray::IxDyn, StreamingFourierUniform<T>>(
1490            input_path,
1491            output_path,
1492            shape,
1493            op,
1494        ),
1495    }
1496}
1497
1498#[cfg(test)]
1499mod tests {
1500    use super::*;
1501    use approx::assert_abs_diff_eq;
1502    use scirs2_core::ndarray::{arr1, Array1, Array2};
1503
1504    #[test]
1505    fn test_fourier_gaussian_1d() {
1506        // Create a simple 1D signal with a spike
1507        let mut signal = Array1::zeros(64);
1508        signal[32] = 1.0;
1509
1510        // Apply Fourier Gaussian filter
1511        let sigma = vec![2.0];
1512        let filtered = fourier_gaussian(&signal, &sigma, None).expect("Operation failed");
1513
1514        // Check that the spike is smoothed
1515        assert!(filtered[32] < 1.0);
1516        assert!(filtered[32] > 0.0);
1517
1518        // Check symmetry
1519        for i in 1..10 {
1520            assert_abs_diff_eq!(filtered[32 - i], filtered[32 + i], epsilon = 1e-10);
1521        }
1522    }
1523
1524    #[test]
1525    fn test_fourier_gaussian_2d() {
1526        // Create a 2D array with a centered spike
1527        let mut image = Array2::zeros((32, 32));
1528        image[[16, 16]] = 1.0;
1529
1530        // Apply Fourier Gaussian filter
1531        let sigma = vec![1.5, 1.5];
1532        let filtered = fourier_gaussian(&image, &sigma, None).expect("Operation failed");
1533
1534        // Check that the spike is smoothed
1535        assert!(filtered[[16, 16]] < 1.0);
1536        assert!(filtered[[16, 16]] > 0.0);
1537
1538        // Check that surrounding pixels have non-zero values
1539        assert!(filtered[[15, 16]] > 0.0);
1540        assert!(filtered[[17, 16]] > 0.0);
1541        assert!(filtered[[16, 15]] > 0.0);
1542        assert!(filtered[[16, 17]] > 0.0);
1543    }
1544
1545    #[test]
1546    fn test_fourier_uniform_1d() {
1547        // Create a step function
1548        let mut signal = Array1::zeros(64);
1549        for i in 30..35 {
1550            signal[i] = 1.0;
1551        }
1552
1553        // Apply Fourier uniform filter
1554        let size = vec![5];
1555        let filtered = fourier_uniform(&signal, &size).expect("Operation failed");
1556
1557        // The filter should smooth the edges
1558        assert!(filtered[29] > 0.0);
1559        assert!(filtered[35] > 0.0);
1560        assert!(filtered[32] > 0.0);
1561    }
1562
1563    #[test]
1564    fn test_fourier_ellipsoid_lowpass() {
1565        // Create a 2D array with high and low frequency components
1566        let mut image = Array2::zeros((32, 32));
1567
1568        // Add a low frequency component (large scale structure)
1569        for i in 0..32 {
1570            for j in 0..32 {
1571                image[[i, j]] =
1572                    ((i as f64 / 32.0 * 2.0 * PI).sin() + (j as f64 / 32.0 * 2.0 * PI).sin()) / 2.0;
1573            }
1574        }
1575
1576        // Add high frequency noise
1577        for i in 0..32 {
1578            for j in 0..32 {
1579                if (i + j) % 2 == 0 {
1580                    image[[i, j]] += 0.5;
1581                }
1582            }
1583        }
1584
1585        // Apply lowpass filter
1586        let size = vec![0.2, 0.2];
1587        let filtered = fourier_ellipsoid(&image, &size, "lowpass").expect("Operation failed");
1588
1589        // The high frequency checkerboard pattern should be reduced
1590        let noise_original = (image[[0, 0]] - image[[0, 1]]).abs();
1591        let noise_filtered = (filtered[[0, 0]] - filtered[[0, 1]]).abs();
1592        assert!(noise_filtered < noise_original);
1593    }
1594
1595    #[test]
1596    fn test_fourier_shift_1d() {
1597        // Create a simple signal
1598        let signal = arr1(&[1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0]);
1599
1600        // Shift by 2 positions
1601        let shift = vec![2.0];
1602        let shifted = fourier_shift(&signal, &shift).expect("Operation failed");
1603
1604        // Check that values are shifted (with some tolerance for numerical errors)
1605        assert_abs_diff_eq!(shifted[2], 1.0, epsilon = 0.1);
1606        assert_abs_diff_eq!(shifted[3], 2.0, epsilon = 0.1);
1607        assert_abs_diff_eq!(shifted[4], 3.0, epsilon = 0.1);
1608        assert_abs_diff_eq!(shifted[5], 4.0, epsilon = 0.1);
1609    }
1610
1611    #[test]
1612    fn test_fourier_shift_fractional() {
1613        // Create a smooth Gaussian-like signal to avoid Gibbs phenomenon
1614        use std::f64::consts::PI;
1615        let mut signal = Array1::zeros(64);
1616        let center = 30.0;
1617        let width = 5.0;
1618        for i in 0..64 {
1619            let x = i as f64;
1620            signal[i] = (-(x - center).powi(2) / (2.0 * width.powi(2))).exp();
1621        }
1622
1623        // Shift by a fractional amount
1624        let shift = vec![0.5];
1625        let shifted = fourier_shift(&signal, &shift).expect("Operation failed");
1626
1627        // After shifting by 0.5, the peak should move from index 30 to between 29.5-30.5
1628        // The values at indices 29 and 30 should both be high and similar
1629        let peak_val = signal[[30]];
1630
1631        // Both indices near the shifted peak should have significant values
1632        assert!(shifted[29].abs() > 0.8 * peak_val);
1633        assert!(shifted[30].abs() > 0.8 * peak_val);
1634
1635        // The shift should be approximately correct (peak moves right by 0.5)
1636        // So value at 30 in shifted ≈ value at 29.5 in original
1637        // which is between signal[29] and signal[30]
1638        let expected = (signal[29] + signal[30]) / 2.0;
1639        assert!((shifted[30] - expected).abs() < 0.1 * peak_val);
1640    }
1641}