1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
10use scirs2_core::numeric::{Float, FromPrimitive};
11use scirs2_core::parallel_ops::*;
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::sync::{Arc, RwLock};
15
16use super::{BoundaryMode, InterpolationOrder};
17use crate::error::{NdimageError, NdimageResult};
18
19#[allow(dead_code)]
21fn safe_i32_to_float<T: Float + FromPrimitive>(value: i32) -> NdimageResult<T> {
22 T::from_i32(value).ok_or_else(|| {
23 NdimageError::ComputationError(format!("Failed to convert i32 {} to float type", value))
24 })
25}
26
27#[allow(dead_code)]
29fn safe_to_usize<T: Float>(value: T) -> NdimageResult<usize> {
30 value.to_usize().ok_or_else(|| {
31 NdimageError::ComputationError("Failed to convert value to usize".to_string())
32 })
33}
34
35#[allow(dead_code)]
37fn safe_to_isize<T: Float>(value: T) -> NdimageResult<isize> {
38 value.to_isize().ok_or_else(|| {
39 NdimageError::ComputationError("Failed to convert value to isize".to_string())
40 })
41}
42
43#[allow(dead_code)]
45fn safe_to_i32<T: Float>(value: T) -> NdimageResult<i32> {
46 value
47 .to_i32()
48 .ok_or_else(|| NdimageError::ComputationError("Failed to convert value to i32".to_string()))
49}
50
51#[allow(dead_code)]
53fn safe_usize_to_float<T: Float + FromPrimitive>(value: usize) -> NdimageResult<T> {
54 T::from_usize(value).ok_or_else(|| {
55 NdimageError::ComputationError(format!("Failed to convert usize {} to float type", value))
56 })
57}
58
59pub struct CoefficientCache<T> {
61 cache: Arc<RwLock<HashMap<CacheKey, Vec<T>>>>,
62 max_entries: usize,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66struct CacheKey {
67 order: u8,
68 offset: i32, }
70
71impl<
72 T: Float + FromPrimitive + Debug + Clone + std::ops::AddAssign + std::ops::DivAssign + 'static,
73 > CoefficientCache<T>
74{
75 pub fn new(max_entries: usize) -> Self {
76 Self {
77 cache: Arc::new(RwLock::new(HashMap::new())),
78 max_entries,
79 }
80 }
81
82 pub fn get_coefficients(&self, order: InterpolationOrder, offset: T) -> NdimageResult<Vec<T>> {
84 let offset_quantized = (offset * safe_i32_to_float(1000)?)
86 .to_i32()
87 .ok_or_else(|| {
88 NdimageError::ComputationError("Failed to quantize offset for caching".to_string())
89 })?;
90 let key = CacheKey {
91 order: order as u8,
92 offset: offset_quantized,
93 };
94
95 {
97 let cache = self.cache.read().map_err(|_| {
98 NdimageError::ComputationError(
99 "Failed to acquire read lock on coefficient cache".to_string(),
100 )
101 })?;
102 if let Some(coeffs) = cache.get(&key) {
103 return Ok(coeffs.clone());
104 }
105 }
106
107 let coeffs = compute_interpolation_coefficients(order, offset)?;
109
110 {
112 let mut cache = self.cache.write().map_err(|_| {
113 NdimageError::ComputationError(
114 "Failed to acquire write lock on coefficient cache".to_string(),
115 )
116 })?;
117 if cache.len() < self.max_entries {
118 cache.insert(key, coeffs.clone());
119 }
120 }
121
122 Ok(coeffs)
123 }
124
125 pub fn clear(&self) -> NdimageResult<()> {
127 self.cache
128 .write()
129 .map_err(|_| {
130 NdimageError::ComputationError(
131 "Failed to acquire write lock to clear cache".to_string(),
132 )
133 })?
134 .clear();
135 Ok(())
136 }
137}
138
139#[allow(dead_code)]
141fn compute_interpolation_coefficients<T>(
142 order: InterpolationOrder,
143 offset: T,
144) -> NdimageResult<Vec<T>>
145where
146 T: Float + FromPrimitive + std::ops::AddAssign + std::ops::DivAssign + 'static,
147{
148 match order {
149 InterpolationOrder::Nearest => Ok(vec![T::one()]),
150 InterpolationOrder::Linear => Ok(vec![T::one() - offset, offset]),
151 InterpolationOrder::Cubic => {
152 let t = offset;
154 let t2 = t * t;
155 let t3 = t2 * t;
156
157 let neg_half: T = crate::utils::safe_f64_to_float::<T>(-0.5)?;
158 let half: T = crate::utils::safe_f64_to_float::<T>(0.5)?;
159 let one_half: T = crate::utils::safe_f64_to_float::<T>(1.5)?;
160 let two_half: T = crate::utils::safe_f64_to_float::<T>(2.5)?;
161 let two: T = crate::utils::safe_f64_to_float::<T>(2.0)?;
162
163 Ok(vec![
164 neg_half * t3 + t2 - half * t,
165 one_half * t3 - two_half * t2 + T::one(),
166 -one_half * t3 + two * t2 + half * t,
167 half * t3 - half * t2,
168 ])
169 }
170 InterpolationOrder::Spline => {
171 compute_bspline_coefficients(5, offset)
173 }
174 }
175}
176
177#[allow(dead_code)]
179fn compute_bspline_coefficients<T>(order: usize, offset: T) -> NdimageResult<Vec<T>>
180where
181 T: Float + FromPrimitive + std::ops::AddAssign + std::ops::DivAssign + 'static,
182{
183 let mut coeffs = vec![T::zero(); order + 1];
184
185 if order == 5 {
187 let t = offset;
188 let t2 = t * t;
189 let t3 = t2 * t;
190 let t4 = t3 * t;
191 let t5 = t4 * t;
192
193 let c120: T = crate::utils::safe_f64_to_float::<T>(1.0 / 120.0)?;
195 let c24: T = crate::utils::safe_f64_to_float::<T>(1.0 / 24.0)?;
196 let c12: T = crate::utils::safe_f64_to_float::<T>(1.0 / 12.0)?;
197 let c2: T = crate::utils::safe_f64_to_float::<T>(2.0)?;
198 let c3: T = crate::utils::safe_f64_to_float::<T>(3.0)?;
199 let c4: T = crate::utils::safe_f64_to_float::<T>(4.0)?;
200 let c5: T = crate::utils::safe_f64_to_float::<T>(5.0)?;
201 let c6: T = crate::utils::safe_f64_to_float::<T>(6.0)?;
202 let c10: T = crate::utils::safe_f64_to_float::<T>(10.0)?;
203
204 coeffs[0] = c120 * (-t5 + c5 * t4 - c10 * t3 + c10 * t2 - c5 * t + T::one());
205 coeffs[1] = c24 * (t5 - c2 * t4 - c3 * t3 + c6 * t2 + c4 * t + T::one());
206 coeffs[2] = c12 * (-t5 + t4 + c3 * t3 + c3 * t2 - c3 * t + T::one());
207 coeffs[3] = c12 * (t5 - t4 - c3 * t3 + c3 * t2 + c3 * t + T::one());
208 coeffs[4] = c24 * (-t5 + c2 * t4 + c3 * t3 + c6 * t2 - c4 * t + T::one());
209 coeffs[5] = c120 * (t5 + c5 * t4 + c10 * t3 + c10 * t2 + c5 * t + T::one());
210 }
211
212 Ok(coeffs)
213}
214
215pub struct Interpolator1D<T> {
217 cache: CoefficientCache<T>,
218 order: InterpolationOrder,
219}
220
221impl<
222 T: Float + FromPrimitive + Debug + Clone + std::ops::AddAssign + std::ops::DivAssign + 'static,
223 > Interpolator1D<T>
224{
225 pub fn new(order: InterpolationOrder) -> Self {
226 Self {
227 cache: CoefficientCache::new(1000),
228 order,
229 }
230 }
231
232 #[inline]
234 pub fn interpolate(
235 &self,
236 data: &ArrayView1<T>,
237 position: T,
238 mode: BoundaryMode,
239 cval: T,
240 ) -> NdimageResult<T> {
241 let _n = data.len();
242 let idx = position.floor();
243 let offset = position - idx;
244
245 let coeffs = self.cache.get_coefficients(self.order, offset)?;
247 let num_coeffs = coeffs.len();
248
249 let mut result = T::zero();
251 let base_idx = safe_to_isize(idx)? - ((num_coeffs / 2) as isize - 1);
252
253 for (i, &coeff) in coeffs.iter().enumerate() {
254 let sample_idx = base_idx + i as isize;
255 let sample_val = get_boundary_value_1d(data, sample_idx, mode, cval);
256 result = result + coeff * sample_val;
257 }
258
259 Ok(result)
260 }
261}
262
263#[inline]
265#[allow(dead_code)]
266fn get_boundary_value_1d<T>(data: &ArrayView1<T>, idx: isize, mode: BoundaryMode, cval: T) -> T
267where
268 T: Float + FromPrimitive + Clone + std::ops::AddAssign + std::ops::DivAssign + 'static,
269{
270 let n = data.len() as isize;
271
272 let valid_idx = match mode {
273 BoundaryMode::Constant => {
274 if idx < 0 || idx >= n {
275 return cval;
276 }
277 idx as usize
278 }
279 BoundaryMode::Nearest => idx.clamp(0, n - 1) as usize,
280 BoundaryMode::Reflect => {
281 let mut i = idx;
282 if i < 0 {
283 i = -i;
284 }
285 if i >= n {
286 i = 2 * n - i - 2;
287 }
288 i.clamp(0, n - 1) as usize
289 }
290 BoundaryMode::Mirror => {
291 let mut i = idx;
292 while i < 0 {
293 i = -i - 1;
294 }
295 while i >= n {
296 i = 2 * n - i - 1;
297 }
298 i as usize
299 }
300 BoundaryMode::Wrap => ((idx % n + n) % n) as usize,
301 };
302
303 data[valid_idx]
304}
305
306pub struct Interpolator2D<T> {
308 cache: CoefficientCache<T>,
309 order: InterpolationOrder,
310}
311
312impl<
313 T: Float
314 + FromPrimitive
315 + Debug
316 + Clone
317 + Send
318 + Sync
319 + 'static
320 + std::ops::AddAssign
321 + std::ops::DivAssign,
322 > Interpolator2D<T>
323{
324 pub fn new(order: InterpolationOrder) -> Self {
325 Self {
326 cache: CoefficientCache::new(2000),
327 order,
328 }
329 }
330
331 pub fn interpolate_batch(
333 &self,
334 data: &Array2<T>,
335 positions: &[(T, T)],
336 mode: BoundaryMode,
337 cval: T,
338 ) -> NdimageResult<Vec<T>> {
339 let (h, w) = data.dim();
340
341 if positions.len() > 1000 && num_threads() > 1 {
342 let chunks: Vec<&[(T, T)]> = positions.chunks(256).collect();
344
345 let process_chunk = |chunk: &&[(T, T)]| -> Result<Vec<T>, scirs2_core::CoreError> {
346 let mut results = Vec::with_capacity(chunk.len());
347
348 for &(y, x) in chunk.iter() {
349 let val = self
350 .interpolate_single(data.view(), y, x, mode, cval)
351 .map_err(|e| {
352 scirs2_core::CoreError::ComputationError(
353 scirs2_core::ErrorContext::new(format!(
354 "interpolation error: {:?}",
355 e
356 )),
357 )
358 })?;
359 results.push(val);
360 }
361
362 Ok(results)
363 };
364
365 let chunk_results = parallel_map_result(&chunks, process_chunk)?;
366
367 Ok(chunk_results.into_iter().flatten().collect())
369 } else {
370 let mut results = Vec::with_capacity(positions.len());
372
373 for &(y, x) in positions {
374 let val = self.interpolate_single(data.view(), y, x, mode, cval)?;
375 results.push(val);
376 }
377
378 Ok(results)
379 }
380 }
381
382 pub fn interpolate_single(
384 &self,
385 data: ArrayView2<T>,
386 y: T,
387 x: T,
388 mode: BoundaryMode,
389 cval: T,
390 ) -> NdimageResult<T> {
391 let _h_w = data.dim();
392
393 let yi = y.floor();
395 let xi = x.floor();
396 let yf = y - yi;
397 let xf = x - xi;
398
399 let y_coeffs = self.cache.get_coefficients(self.order, yf)?;
401 let x_coeffs = self.cache.get_coefficients(self.order, xf)?;
402
403 let ny = y_coeffs.len();
404 let nx = x_coeffs.len();
405
406 let base_y = safe_to_isize(yi)? - ((ny / 2) as isize - 1);
408 let base_x = safe_to_isize(xi)? - ((nx / 2) as isize - 1);
409
410 let mut result = T::zero();
412
413 for (iy, &cy) in y_coeffs.iter().enumerate() {
414 let mut row_sum = T::zero();
415 let sample_y = base_y + iy as isize;
416
417 for (ix, &cx) in x_coeffs.iter().enumerate() {
418 let sample_x = base_x + ix as isize;
419 let val = get_boundary_value_2d(&data, sample_y, sample_x, mode, cval);
420 row_sum = row_sum + cx * val;
421 }
422
423 result = result + cy * row_sum;
424 }
425
426 Ok(result)
427 }
428}
429
430#[inline]
432#[allow(dead_code)]
433fn get_boundary_value_2d<T>(
434 data: &ArrayView2<T>,
435 y: isize,
436 x: isize,
437 mode: BoundaryMode,
438 cval: T,
439) -> T
440where
441 T: Float + FromPrimitive + Clone + std::ops::AddAssign + std::ops::DivAssign + 'static,
442{
443 let (h, w) = data.dim();
444 let h = h as isize;
445 let w = w as isize;
446
447 let (valid_y, valid_x) = match mode {
448 BoundaryMode::Constant => {
449 if y < 0 || y >= h || x < 0 || x >= w {
450 return cval;
451 }
452 (y as usize, x as usize)
453 }
454 BoundaryMode::Nearest => (y.clamp(0, h - 1) as usize, x.clamp(0, w - 1) as usize),
455 BoundaryMode::Reflect => {
456 let mut yi = y;
457 let mut xi = x;
458
459 if yi < 0 {
460 yi = -yi;
461 }
462 if yi >= h {
463 yi = 2 * h - yi - 2;
464 }
465
466 if xi < 0 {
467 xi = -xi;
468 }
469 if xi >= w {
470 xi = 2 * w - xi - 2;
471 }
472
473 (yi.clamp(0, h - 1) as usize, xi.clamp(0, w - 1) as usize)
474 }
475 BoundaryMode::Mirror => {
476 let mut yi = y;
477 let mut xi = x;
478
479 while yi < 0 {
480 yi = -yi - 1;
481 }
482 while yi >= h {
483 yi = 2 * h - yi - 1;
484 }
485
486 while xi < 0 {
487 xi = -xi - 1;
488 }
489 while xi >= w {
490 xi = 2 * w - xi - 1;
491 }
492
493 (yi as usize, xi as usize)
494 }
495 BoundaryMode::Wrap => (((y % h + h) % h) as usize, ((x % w + w) % w) as usize),
496 };
497
498 data[[valid_y, valid_x]]
499}
500
501#[allow(dead_code)]
503pub fn map_coordinates_optimized<T>(
504 input: &Array2<T>,
505 coordinates: &[Array1<T>],
506 order: Option<InterpolationOrder>,
507 mode: Option<BoundaryMode>,
508 cval: Option<T>,
509) -> NdimageResult<Array1<T>>
510where
511 T: Float
512 + FromPrimitive
513 + Debug
514 + Clone
515 + Send
516 + Sync
517 + 'static
518 + std::ops::AddAssign
519 + std::ops::DivAssign,
520{
521 if coordinates.len() != 2 {
522 return Err(NdimageError::InvalidInput(
523 "Coordinates must have length 2 for 2D input".into(),
524 ));
525 }
526
527 let order = order.unwrap_or(InterpolationOrder::Cubic);
528 let mode = mode.unwrap_or(BoundaryMode::Constant);
529 let cval = cval.unwrap_or_else(T::zero);
530
531 let n_points = coordinates[0].len();
532 if coordinates[1].len() != n_points {
533 return Err(NdimageError::InvalidInput(
534 "All coordinate arrays must have the same length".into(),
535 ));
536 }
537
538 let interpolator = Interpolator2D::new(order);
540
541 let positions: Vec<(T, T)> = (0..n_points)
543 .map(|i| (coordinates[0][i], coordinates[1][i]))
544 .collect();
545
546 let results = interpolator.interpolate_batch(input, &positions, mode, cval)?;
548
549 Ok(Array1::from_vec(results))
550}
551
552#[allow(dead_code)]
554pub fn zoom_optimized<T>(
555 input: &Array2<T>,
556 zoom_factors: &[T],
557 order: Option<InterpolationOrder>,
558 mode: Option<BoundaryMode>,
559 cval: Option<T>,
560) -> NdimageResult<Array2<T>>
561where
562 T: Float
563 + FromPrimitive
564 + Debug
565 + Clone
566 + Send
567 + Sync
568 + 'static
569 + std::ops::AddAssign
570 + std::ops::DivAssign,
571{
572 if zoom_factors.len() != 2 {
573 return Err(NdimageError::InvalidInput(
574 "Zoom _factors must have length 2 for 2D input".into(),
575 ));
576 }
577
578 let order = order.unwrap_or(InterpolationOrder::Cubic);
579 let mode = mode.unwrap_or(BoundaryMode::Constant);
580 let cval = cval.unwrap_or_else(T::zero);
581
582 let (h, w) = input.dim();
583 let new_h: T = safe_usize_to_float::<T>(h)? * zoom_factors[0];
584 let new_h = safe_to_usize(new_h.round())?;
585 let new_w: T = safe_usize_to_float::<T>(w)? * zoom_factors[1];
586 let new_w = safe_to_usize(new_w.round())?;
587
588 let mut output = Array2::zeros((new_h, new_w));
589
590 let interpolator = Interpolator2D::new(order);
592
593 if new_h * new_w > 10000 && num_threads() > 1 {
595 let rows: Vec<usize> = (0..new_h).collect();
596
597 let process_row = |&row: &usize| -> Result<Vec<T>, scirs2_core::CoreError> {
598 let mut row_data = Vec::with_capacity(new_w);
599 let y = safe_usize_to_float::<T>(row).map_err(|e| {
600 scirs2_core::CoreError::ComputationError(scirs2_core::ErrorContext::new(format!(
601 "Failed to convert row to float: {}",
602 e
603 )))
604 })? / zoom_factors[0];
605
606 for col in 0..new_w {
607 let x = safe_usize_to_float::<T>(col).map_err(|e| {
608 scirs2_core::CoreError::ComputationError(scirs2_core::ErrorContext::new(
609 format!("Failed to convert col to float: {}", e),
610 ))
611 })? / zoom_factors[1];
612 let val = interpolator
613 .interpolate_single(input.view(), y, x, mode, cval)
614 .map_err(|e| {
615 scirs2_core::CoreError::ComputationError(scirs2_core::ErrorContext::new(
616 format!("interpolation error: {:?}", e),
617 ))
618 })?;
619 row_data.push(val);
620 }
621
622 Ok(row_data)
623 };
624
625 let results = parallel_map_result(&rows, process_row)?;
626
627 for (row, row_data) in results.into_iter().enumerate() {
629 for (col, val) in row_data.into_iter().enumerate() {
630 output[[row, col]] = val;
631 }
632 }
633 } else {
634 for row in 0..new_h {
636 let y = safe_usize_to_float::<T>(row)? / zoom_factors[0];
637
638 for col in 0..new_w {
639 let x = safe_usize_to_float::<T>(col)? / zoom_factors[1];
640 output[[row, col]] =
641 interpolator.interpolate_single(input.view(), y, x, mode, cval)?;
642 }
643 }
644 }
645
646 Ok(output)
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652 use scirs2_core::ndarray::{arr1, arr2};
653
654 #[test]
655 fn test_coefficient_cache() {
656 let cache: CoefficientCache<f64> = CoefficientCache::new(10);
657
658 let coeffs1 = cache
660 .get_coefficients(InterpolationOrder::Linear, 0.3)
661 .expect("Operation failed");
662 assert_eq!(coeffs1.len(), 2);
663 assert!((coeffs1[0] - 0.7).abs() < 1e-10);
664 assert!((coeffs1[1] - 0.3).abs() < 1e-10);
665
666 let coeffs2 = cache
668 .get_coefficients(InterpolationOrder::Linear, 0.3)
669 .expect("Operation failed");
670 assert_eq!(coeffs1, coeffs2);
671 }
672
673 #[test]
674 fn test_interpolator_1d() {
675 let data = arr1(&[1.0, 2.0, 3.0, 4.0]);
676 let interpolator = Interpolator1D::new(InterpolationOrder::Linear);
677
678 assert_eq!(
680 interpolator
681 .interpolate(&data.view(), 0.0, BoundaryMode::Constant, 0.0)
682 .expect("interpolation at exact position should succeed"),
683 1.0
684 );
685 assert_eq!(
686 interpolator
687 .interpolate(&data.view(), 1.0, BoundaryMode::Constant, 0.0)
688 .expect("interpolation at exact position should succeed"),
689 2.0
690 );
691
692 let result = interpolator
694 .interpolate(&data.view(), 1.5, BoundaryMode::Constant, 0.0)
695 .expect("interpolation should succeed");
696 assert!((result - 2.5).abs() < 1e-10);
697 }
698
699 #[test]
700 fn test_zoom_optimized() {
701 let input = arr2(&[[1.0f64, 2.0], [3.0, 4.0]]);
702 let result = zoom_optimized(
704 &input,
705 &[2.0, 2.0],
706 Some(InterpolationOrder::Linear),
707 None,
708 None,
709 )
710 .expect("zoom_optimized should succeed for test");
711
712 assert_eq!(result.shape(), &[4, 4]);
713
714 assert!(
717 (result[[0, 0]] - 1.0).abs() < 1e-6,
718 "result[0,0]={}",
719 result[[0, 0]]
720 );
721
722 assert!(
724 (result[[1, 0]] - 2.0).abs() < 1e-6,
725 "result[1,0]={}",
726 result[[1, 0]]
727 );
728
729 assert!(
731 (result[[0, 1]] - 1.5).abs() < 1e-6,
732 "result[0,1]={}",
733 result[[0, 1]]
734 );
735
736 assert!(
738 (result[[1, 1]] - 2.5).abs() < 1e-6,
739 "result[1,1]={}",
740 result[[1, 1]]
741 );
742 }
743}