1use 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
19impl From<FFTError> for NdimageError {
21 fn from(err: FFTError) -> Self {
22 NdimageError::ComputationError(format!("FFT error: {}", err))
23 }
24}
25
26#[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 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 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 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 let result = fourier_gaussian_nd(input, sigma)?;
98 Ok(result)
99 }
100 }
101}
102
103#[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 let input_f64: Vec<f64> = input
113 .iter()
114 .map(|&x| NumCast::from(x).unwrap_or(0.0))
115 .collect();
116
117 let mut spectrum = fft(&input_f64, None)?;
119
120 let freqs = fftfreq(n, 1.0)?;
122
123 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 let result = ifft(&spectrum, None)?;
134
135 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#[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 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 let spectrum = fft2(&input_f64, None, None, None)?;
164
165 let freqs_y = fftfreq(ny, 1.0)?;
167 let freqs_x = fftfreq(nx, 1.0)?;
168
169 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 let result = ifft2(&filtered_spectrum, None, None, None)?;
185
186 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#[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 if input.ndim() == 3 {
215 return fourier_gaussian_3d(input, sigma);
216 }
217
218 let shape = input.shape().to_vec();
221 let flat_input = input.view().into_shape_with_order(input.len())?;
222
223 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 let flat_owned = flat_input.to_owned();
235 let filtered_1d = fourier_gaussian_1d(&flat_owned, sigma_1d)?;
236
237 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#[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 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 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 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 let two_pi = 2.0 * PI;
277
278 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 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 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 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 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#[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 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 let result = fourier_uniform_nd(input, size)?;
418 Ok(result)
419 }
420 }
421}
422
423#[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 let input_f64: Vec<f64> = input
433 .iter()
434 .map(|&x| NumCast::from(x).unwrap_or(0.0))
435 .collect();
436
437 let mut spectrum = fft(&input_f64, None)?;
439
440 let freqs = fftfreq(n, 1.0)?;
442
443 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 let result = ifft(&spectrum, None)?;
452
453 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#[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 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 let spectrum = fft2(&input_f64, None, None, None)?;
486
487 let freqs_y = fftfreq(ny, 1.0)?;
489 let freqs_x = fftfreq(nx, 1.0)?;
490
491 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 let result = ifft2(&filtered_spectrum, None, None, None)?;
515
516 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#[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 if input.ndim() == 3 {
542 return fourier_uniform_3d(input, size);
543 }
544
545 let shape = input.shape().to_vec();
547 let flat_input = input.view().into_shape_with_order(input.len())?;
548
549 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 let flat_owned = flat_input.to_owned();
558 let filtered_1d = fourier_uniform_1d(&flat_owned, size_1d)?;
559
560 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#[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 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 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 let two_pi = 2.0 * PI;
595
596 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 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 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 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 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#[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 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 fourier_ellipsoid_nd(input, size, is_lowpass)
743 }
744}
745
746#[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 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 let spectrum = fft2(&input_f64, None, None, None)?;
769
770 let freqs_y = fftfreq(ny, 1.0)?;
772 let freqs_x = fftfreq(nx, 1.0)?;
773
774 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 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 let result = ifft2(&filtered_spectrum, None, None, None)?;
804
805 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#[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 if input.ndim() == 3 {
835 return fourier_ellipsoid_3d(input, size, is_lowpass);
836 }
837
838 let shape = input.shape().to_vec();
840 let flat_input = input.view().into_shape_with_order(input.len())?;
841
842 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 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 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#[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 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 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 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 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 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 for (i, &freq_z) in freqs_z.iter().enumerate() {
918 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 } else {
927 spectrum[i] *= 0.0; }
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 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 } else {
953 spectrum[j] *= 0.0; }
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 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 } else {
979 spectrum[k] *= 0.0; }
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 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 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#[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 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 let result = fourier_shift_nd(input, shift)?;
1068 Ok(result)
1069 }
1070 }
1071}
1072
1073#[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 let input_f64: Vec<f64> = input
1083 .iter()
1084 .map(|&x| NumCast::from(x).unwrap_or(0.0))
1085 .collect();
1086
1087 let mut spectrum = fft(&input_f64, None)?;
1089
1090 let freqs = fftfreq(n, 1.0)?;
1092
1093 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 let result = ifft(&spectrum, None)?;
1105
1106 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#[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 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 let spectrum = fft2(&input_f64, None, None, None)?;
1135
1136 let freqs_y = fftfreq(ny, 1.0)?;
1138 let freqs_x = fftfreq(nx, 1.0)?;
1139
1140 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 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 let result = ifft2(&shifted_spectrum, None, None, None)?;
1157
1158 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#[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 if input.ndim() == 3 {
1184 return fourier_shift_3d(input, shift);
1185 }
1186
1187 let shape = input.shape().to_vec();
1189 let flat_input = input.view().into_shape_with_order(input.len())?;
1190
1191 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 let shifted_1d = fourier_shift_1d(&flat_input.to_owned(), shift_1d)?;
1200
1201 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#[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 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 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 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 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 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 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 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 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 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
1322use crate::streaming::{OverlapInfo, StreamConfig, StreamProcessor, StreamableOp};
1324use scirs2_core::ndarray::{ArrayView, ArrayViewMut};
1325use std::path::Path;
1326
1327pub 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 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 Ok(())
1360 }
1361}
1362
1363#[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, ..Default::default()
1379 });
1380
1381 let processor = StreamProcessor::<T>::new(config);
1382
1383 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
1412pub 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#[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 let mut signal = Array1::zeros(64);
1508 signal[32] = 1.0;
1509
1510 let sigma = vec![2.0];
1512 let filtered = fourier_gaussian(&signal, &sigma, None).expect("Operation failed");
1513
1514 assert!(filtered[32] < 1.0);
1516 assert!(filtered[32] > 0.0);
1517
1518 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 let mut image = Array2::zeros((32, 32));
1528 image[[16, 16]] = 1.0;
1529
1530 let sigma = vec![1.5, 1.5];
1532 let filtered = fourier_gaussian(&image, &sigma, None).expect("Operation failed");
1533
1534 assert!(filtered[[16, 16]] < 1.0);
1536 assert!(filtered[[16, 16]] > 0.0);
1537
1538 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 let mut signal = Array1::zeros(64);
1549 for i in 30..35 {
1550 signal[i] = 1.0;
1551 }
1552
1553 let size = vec![5];
1555 let filtered = fourier_uniform(&signal, &size).expect("Operation failed");
1556
1557 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 let mut image = Array2::zeros((32, 32));
1567
1568 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 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 let size = vec![0.2, 0.2];
1587 let filtered = fourier_ellipsoid(&image, &size, "lowpass").expect("Operation failed");
1588
1589 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 let signal = arr1(&[1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0]);
1599
1600 let shift = vec![2.0];
1602 let shifted = fourier_shift(&signal, &shift).expect("Operation failed");
1603
1604 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 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 let shift = vec![0.5];
1625 let shifted = fourier_shift(&signal, &shift).expect("Operation failed");
1626
1627 let peak_val = signal[[30]];
1630
1631 assert!(shifted[29].abs() > 0.8 * peak_val);
1633 assert!(shifted[30].abs() > 0.8 * peak_val);
1634
1635 let expected = (signal[29] + signal[30]) / 2.0;
1639 assert!((shifted[30] - expected).abs() < 0.1 * peak_val);
1640 }
1641}