1use scirs2_core::ndarray::Array2;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
16pub enum CVKernelError {
17 #[error("Dimension mismatch: expected {expected}, got {actual}")]
18 DimensionMismatch { expected: usize, actual: usize },
19 #[error("Invalid histogram: negative values not allowed")]
20 InvalidHistogram,
21 #[error("Empty histogram")]
22 EmptyHistogram,
23 #[error("Invalid kernel parameters: {message}")]
24 InvalidParameters { message: String },
25}
26
27#[derive(Debug, Clone, PartialEq)]
29pub enum CVKernelType {
30 HistogramIntersection,
32 ChiSquare { gamma: f64 },
34 SpatialPyramid { levels: usize, weights: Vec<f64> },
36 HOG { bins: usize, cell_size: usize },
38 LBP { radius: f64, neighbors: usize },
40 EMD { distance_matrix: Array2<f64> },
42 AdditiveChiSquare,
44 JensenShannon,
46 Bhattacharyya,
48 Hellinger,
50}
51
52#[derive(Debug, Clone)]
54pub struct CVKernelFunction {
55 pub kernel_type: CVKernelType,
56}
57
58impl CVKernelFunction {
59 pub fn new(kernel_type: CVKernelType) -> Self {
61 Self { kernel_type }
62 }
63
64 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
66 if x.len() != y.len() {
67 return Err(CVKernelError::DimensionMismatch {
68 expected: x.len(),
69 actual: y.len(),
70 });
71 }
72
73 match &self.kernel_type {
74 CVKernelType::HistogramIntersection => {
75 self.validate_histogram(x)?;
76 self.validate_histogram(y)?;
77 Ok(self.histogram_intersection(x, y))
78 }
79 CVKernelType::ChiSquare { gamma } => {
80 self.validate_histogram(x)?;
81 self.validate_histogram(y)?;
82 Ok(self.chi_square_kernel(x, y, *gamma))
83 }
84 CVKernelType::SpatialPyramid { levels, weights } => {
85 self.spatial_pyramid_kernel(x, y, *levels, weights)
86 }
87 CVKernelType::HOG { bins, cell_size } => self.hog_kernel(x, y, *bins, *cell_size),
88 CVKernelType::LBP { radius, neighbors } => self.lbp_kernel(x, y, *radius, *neighbors),
89 CVKernelType::EMD { distance_matrix } => self.emd_kernel(x, y, distance_matrix),
90 CVKernelType::AdditiveChiSquare => {
91 self.validate_histogram(x)?;
92 self.validate_histogram(y)?;
93 Ok(self.additive_chi_square_kernel(x, y))
94 }
95 CVKernelType::JensenShannon => {
96 self.validate_histogram(x)?;
97 self.validate_histogram(y)?;
98 Ok(self.jensen_shannon_kernel(x, y))
99 }
100 CVKernelType::Bhattacharyya => {
101 self.validate_histogram(x)?;
102 self.validate_histogram(y)?;
103 Ok(self.bhattacharyya_kernel(x, y))
104 }
105 CVKernelType::Hellinger => {
106 self.validate_histogram(x)?;
107 self.validate_histogram(y)?;
108 Ok(self.hellinger_kernel(x, y))
109 }
110 }
111 }
112
113 pub fn compute_matrix(
115 &self,
116 x: &Array2<f64>,
117 y: &Array2<f64>,
118 ) -> Result<Array2<f64>, CVKernelError> {
119 let (n_x, n_features_x) = x.dim();
120 let (n_y, n_features_y) = y.dim();
121
122 if n_features_x != n_features_y {
123 return Err(CVKernelError::DimensionMismatch {
124 expected: n_features_x,
125 actual: n_features_y,
126 });
127 }
128
129 let mut kernel_matrix = Array2::zeros((n_x, n_y));
130
131 for i in 0..n_x {
132 for j in 0..n_y {
133 let x_row = x.row(i).to_vec();
134 let y_row = y.row(j).to_vec();
135 kernel_matrix[[i, j]] = self.compute(&x_row, &y_row)?;
136 }
137 }
138
139 Ok(kernel_matrix)
140 }
141
142 fn validate_histogram(&self, hist: &[f64]) -> Result<(), CVKernelError> {
144 if hist.is_empty() {
145 return Err(CVKernelError::EmptyHistogram);
146 }
147
148 for &value in hist {
149 if value < 0.0 {
150 return Err(CVKernelError::InvalidHistogram);
151 }
152 }
153
154 Ok(())
155 }
156
157 fn histogram_intersection(&self, x: &[f64], y: &[f64]) -> f64 {
159 x.iter().zip(y.iter()).map(|(a, b)| a.min(*b)).sum()
160 }
161
162 fn chi_square_kernel(&self, x: &[f64], y: &[f64], gamma: f64) -> f64 {
164 let chi_square_distance = x
165 .iter()
166 .zip(y.iter())
167 .map(|(a, b)| {
168 if a + b > 0.0 {
169 (a - b).powi(2) / (a + b)
170 } else {
171 0.0
172 }
173 })
174 .sum::<f64>();
175
176 (-gamma * chi_square_distance).exp()
177 }
178
179 fn spatial_pyramid_kernel(
181 &self,
182 x: &[f64],
183 y: &[f64],
184 levels: usize,
185 weights: &[f64],
186 ) -> Result<f64, CVKernelError> {
187 if weights.len() != levels + 1 {
188 return Err(CVKernelError::InvalidParameters {
189 message: format!("Expected {} weights for {} levels", levels + 1, levels),
190 });
191 }
192
193 let total_cells: usize = (0..=levels).map(|lvl| 1usize << (2 * lvl)).sum();
194
195 if x.len() != y.len() {
196 return Err(CVKernelError::DimensionMismatch {
197 expected: x.len(),
198 actual: y.len(),
199 });
200 }
201
202 if !x.len().is_multiple_of(total_cells) {
203 return Err(CVKernelError::InvalidParameters {
204 message: format!(
205 "Feature vector length {} is not divisible by spatial pyramid cell count {}",
206 x.len(),
207 total_cells
208 ),
209 });
210 }
211
212 let cell_hist_size = x.len() / total_cells;
213
214 let mut kernel_value = 0.0;
215 let mut offset = 0;
216
217 for (level, weight) in weights.iter().enumerate().take(levels + 1) {
218 let grid_size = 1 << (2 * level); let level_span = grid_size * cell_hist_size;
220 debug_assert!(offset + level_span <= x.len());
221
222 let mut level_intersection = 0.0;
223 for cell in 0..grid_size {
224 let start = offset + cell * cell_hist_size;
225 let end = start + cell_hist_size;
226
227 let x_cell = &x[start..end];
228 let y_cell = &y[start..end];
229
230 level_intersection += self.histogram_intersection(x_cell, y_cell);
231 }
232
233 kernel_value += weight * level_intersection;
234 offset += level_span;
235 }
236
237 Ok(kernel_value)
238 }
239
240 fn hog_kernel(
242 &self,
243 x: &[f64],
244 y: &[f64],
245 bins: usize,
246 cell_size: usize,
247 ) -> Result<f64, CVKernelError> {
248 if !x.len().is_multiple_of(bins * cell_size) {
249 return Err(CVKernelError::InvalidParameters {
250 message: "Feature vector length must be divisible by bins * cell_size".to_string(),
251 });
252 }
253
254 let num_cells = x.len() / (bins * cell_size);
255 let mut total_intersection = 0.0;
256
257 for cell in 0..num_cells {
258 let start = cell * bins * cell_size;
259 let end = start + bins * cell_size;
260
261 let x_cell = &x[start..end];
262 let y_cell = &y[start..end];
263
264 total_intersection += self.histogram_intersection(x_cell, y_cell);
265 }
266
267 Ok(total_intersection / num_cells as f64)
268 }
269
270 fn lbp_kernel(
272 &self,
273 x: &[f64],
274 y: &[f64],
275 _radius: f64,
276 neighbors: usize,
277 ) -> Result<f64, CVKernelError> {
278 let expected_bins = 2_usize.pow(neighbors as u32);
281
282 if x.len() != expected_bins || y.len() != expected_bins {
283 return Err(CVKernelError::InvalidParameters {
284 message: format!(
285 "Expected {} bins for {} neighbors",
286 expected_bins, neighbors
287 ),
288 });
289 }
290
291 let x_sum: f64 = x.iter().sum();
293 let y_sum: f64 = y.iter().sum();
294
295 if x_sum == 0.0 || y_sum == 0.0 {
296 return Ok(0.0);
297 }
298
299 let x_normalized: Vec<f64> = x.iter().map(|v| v / x_sum).collect();
300 let y_normalized: Vec<f64> = y.iter().map(|v| v / y_sum).collect();
301
302 Ok(self.histogram_intersection(&x_normalized, &y_normalized))
303 }
304
305 fn emd_kernel(
307 &self,
308 x: &[f64],
309 y: &[f64],
310 distance_matrix: &Array2<f64>,
311 ) -> Result<f64, CVKernelError> {
312 if distance_matrix.nrows() != x.len() || distance_matrix.ncols() != y.len() {
313 return Err(CVKernelError::InvalidParameters {
314 message: "Distance matrix dimensions don't match feature vectors".to_string(),
315 });
316 }
317
318 let mut emd_distance = 0.0;
321 let x_sum: f64 = x.iter().sum();
322 let y_sum: f64 = y.iter().sum();
323
324 if x_sum == 0.0 || y_sum == 0.0 {
325 return Ok(0.0);
326 }
327
328 let x_normalized: Vec<f64> = x.iter().map(|v| v / x_sum).collect();
330 let y_normalized: Vec<f64> = y.iter().map(|v| v / y_sum).collect();
331
332 for i in 0..x.len() {
334 for j in 0..y.len() {
335 let flow = x_normalized[i].min(y_normalized[j]);
336 emd_distance += flow * distance_matrix[[i, j]];
337 }
338 }
339
340 Ok((-emd_distance).exp())
342 }
343
344 fn additive_chi_square_kernel(&self, x: &[f64], y: &[f64]) -> f64 {
346 x.iter()
347 .zip(y.iter())
348 .map(|(a, b)| {
349 if a + b > 0.0 {
350 2.0 * a * b / (a + b)
351 } else {
352 0.0
353 }
354 })
355 .sum()
356 }
357
358 fn jensen_shannon_kernel(&self, x: &[f64], y: &[f64]) -> f64 {
360 let x_sum: f64 = x.iter().sum();
361 let y_sum: f64 = y.iter().sum();
362
363 if x_sum == 0.0 || y_sum == 0.0 {
364 return 0.0;
365 }
366
367 let x_normalized: Vec<f64> = x.iter().map(|v| v / x_sum).collect();
368 let y_normalized: Vec<f64> = y.iter().map(|v| v / y_sum).collect();
369
370 let mut js_divergence = 0.0;
371
372 for i in 0..x.len() {
373 let p = x_normalized[i];
374 let q = y_normalized[i];
375 let m = (p + q) / 2.0;
376
377 if p > 0.0 && m > 0.0 {
378 js_divergence += p * (p / m).ln();
379 }
380 if q > 0.0 && m > 0.0 {
381 js_divergence += q * (q / m).ln();
382 }
383 }
384
385 js_divergence /= 2.0;
386
387 (-js_divergence).exp()
389 }
390
391 fn bhattacharyya_kernel(&self, x: &[f64], y: &[f64]) -> f64 {
393 let x_sum: f64 = x.iter().sum();
394 let y_sum: f64 = y.iter().sum();
395
396 if x_sum == 0.0 || y_sum == 0.0 {
397 return 0.0;
398 }
399
400 let x_normalized: Vec<f64> = x.iter().map(|v| v / x_sum).collect();
401 let y_normalized: Vec<f64> = y.iter().map(|v| v / y_sum).collect();
402
403 x_normalized
404 .iter()
405 .zip(y_normalized.iter())
406 .map(|(a, b)| (a * b).sqrt())
407 .sum()
408 }
409
410 fn hellinger_kernel(&self, x: &[f64], y: &[f64]) -> f64 {
412 let bhattacharyya = self.bhattacharyya_kernel(x, y);
413 bhattacharyya.sqrt()
414 }
415}
416
417pub mod cv_utils {
419 use super::*;
420
421 pub fn create_pyramid_weights(levels: usize) -> Vec<f64> {
423 let mut weights = Vec::with_capacity(levels + 1);
424
425 for level in 0..=levels {
426 if level == 0 {
427 weights.push(1.0);
428 } else {
429 weights.push(0.5 / (1 << (level - 1)) as f64);
430 }
431 }
432
433 weights
434 }
435
436 pub fn create_distance_matrix(size: usize, distance_type: &str) -> Array2<f64> {
438 let mut matrix = Array2::zeros((size, size));
439
440 match distance_type {
441 "euclidean" => {
442 for i in 0..size {
443 for j in 0..size {
444 matrix[[i, j]] = ((i as f64 - j as f64).powi(2)).sqrt();
445 }
446 }
447 }
448 "manhattan" => {
449 for i in 0..size {
450 for j in 0..size {
451 matrix[[i, j]] = (i as f64 - j as f64).abs();
452 }
453 }
454 }
455 "grid" => {
456 let grid_size = (size as f64).sqrt() as usize;
458 for i in 0..size {
459 for j in 0..size {
460 let i_x = i % grid_size;
461 let i_y = i / grid_size;
462 let j_x = j % grid_size;
463 let j_y = j / grid_size;
464
465 matrix[[i, j]] = ((i_x as f64 - j_x as f64).powi(2)
466 + (i_y as f64 - j_y as f64).powi(2))
467 .sqrt();
468 }
469 }
470 }
471 _ => {
472 for i in 0..size {
474 matrix[[i, i]] = 1.0;
475 }
476 }
477 }
478
479 matrix
480 }
481
482 pub fn normalize_histogram(hist: &[f64]) -> Vec<f64> {
484 let sum: f64 = hist.iter().sum();
485 if sum == 0.0 {
486 return vec![0.0; hist.len()];
487 }
488 hist.iter().map(|v| v / sum).collect()
489 }
490
491 pub fn fast_histogram_intersection(x: &[f64], y: &[f64]) -> f64 {
493 x.iter().zip(y.iter()).map(|(a, b)| a.min(*b)).sum()
494 }
495
496 pub fn chi_square_distance(x: &[f64], y: &[f64]) -> f64 {
498 x.iter()
499 .zip(y.iter())
500 .map(|(a, b)| {
501 if a + b > 0.0 {
502 (a - b).powi(2) / (a + b)
503 } else {
504 0.0
505 }
506 })
507 .sum()
508 }
509}
510
511pub mod specialized_cv_kernels {
513 use super::*;
514
515 pub struct SIFTKernel {
517 pub sigma: f64,
518 pub use_normalization: bool,
519 pub matching_method: DescriptorMatchingMethod,
520 }
521
522 #[derive(Debug, Clone, PartialEq)]
524 pub enum DescriptorMatchingMethod {
525 RBF,
527 CosineSimilarity,
529 Laplacian,
531 ChiSquare,
533 }
534
535 impl SIFTKernel {
536 pub fn new(sigma: f64, use_normalization: bool) -> Self {
537 Self {
538 sigma,
539 use_normalization,
540 matching_method: DescriptorMatchingMethod::RBF,
541 }
542 }
543
544 pub fn with_matching_method(mut self, method: DescriptorMatchingMethod) -> Self {
545 self.matching_method = method;
546 self
547 }
548
549 pub fn compute(&self, x: &[f64], y: &[f64]) -> f64 {
550 if x.len() != y.len() || x.len() != 128 {
551 return 0.0; }
553
554 let mut x_norm = x.to_vec();
555 let mut y_norm = y.to_vec();
556
557 if self.use_normalization {
558 let x_magnitude = x.iter().map(|v| v * v).sum::<f64>().sqrt();
559 let y_magnitude = y.iter().map(|v| v * v).sum::<f64>().sqrt();
560
561 if x_magnitude > 0.0 {
562 x_norm.iter_mut().for_each(|v| *v /= x_magnitude);
563 }
564 if y_magnitude > 0.0 {
565 y_norm.iter_mut().for_each(|v| *v /= y_magnitude);
566 }
567 }
568
569 match self.matching_method {
570 DescriptorMatchingMethod::RBF => {
571 let squared_distance: f64 = x_norm
573 .iter()
574 .zip(y_norm.iter())
575 .map(|(a, b)| (a - b).powi(2))
576 .sum();
577 (-squared_distance / (2.0 * self.sigma.powi(2))).exp()
578 }
579 DescriptorMatchingMethod::CosineSimilarity => {
580 let dot_product: f64 =
581 x_norm.iter().zip(y_norm.iter()).map(|(a, b)| a * b).sum();
582 let x_norm_sq: f64 = x_norm.iter().map(|v| v * v).sum::<f64>().sqrt();
583 let y_norm_sq: f64 = y_norm.iter().map(|v| v * v).sum::<f64>().sqrt();
584 if x_norm_sq > 0.0 && y_norm_sq > 0.0 {
585 dot_product / (x_norm_sq * y_norm_sq)
586 } else {
587 0.0
588 }
589 }
590 DescriptorMatchingMethod::Laplacian => {
591 let l1_distance: f64 = x_norm
592 .iter()
593 .zip(y_norm.iter())
594 .map(|(a, b)| (a - b).abs())
595 .sum();
596 (-l1_distance / self.sigma).exp()
597 }
598 DescriptorMatchingMethod::ChiSquare => {
599 let chi_square: f64 = x_norm
600 .iter()
601 .zip(y_norm.iter())
602 .map(|(a, b)| {
603 if a + b > 0.0 {
604 (a - b).powi(2) / (a + b)
605 } else {
606 0.0
607 }
608 })
609 .sum();
610 (-chi_square / (2.0 * self.sigma)).exp()
611 }
612 }
613 }
614 }
615
616 pub struct SURFKernel {
618 pub sigma: f64,
619 pub use_normalization: bool,
620 pub extended: bool, }
622
623 impl SURFKernel {
624 pub fn new(sigma: f64, use_normalization: bool, extended: bool) -> Self {
625 Self {
626 sigma,
627 use_normalization,
628 extended,
629 }
630 }
631
632 pub fn compute(&self, x: &[f64], y: &[f64]) -> f64 {
633 let expected_dim = if self.extended { 128 } else { 64 };
634 if x.len() != y.len() || x.len() != expected_dim {
635 return 0.0;
636 }
637
638 let mut x_norm = x.to_vec();
639 let mut y_norm = y.to_vec();
640
641 if self.use_normalization {
642 let x_magnitude = x.iter().map(|v| v * v).sum::<f64>().sqrt();
643 let y_magnitude = y.iter().map(|v| v * v).sum::<f64>().sqrt();
644
645 if x_magnitude > 0.0 {
646 x_norm.iter_mut().for_each(|v| *v /= x_magnitude);
647 }
648 if y_magnitude > 0.0 {
649 y_norm.iter_mut().for_each(|v| *v /= y_magnitude);
650 }
651 }
652
653 let squared_distance: f64 = x_norm
655 .iter()
656 .zip(y_norm.iter())
657 .map(|(a, b)| (a - b).powi(2))
658 .sum();
659
660 (-squared_distance / (2.0 * self.sigma.powi(2))).exp()
661 }
662 }
663
664 pub struct ColorHistogramKernel {
666 pub bins_per_channel: usize,
667 pub num_channels: usize,
668 pub kernel_type: CVKernelType,
669 }
670
671 impl ColorHistogramKernel {
672 pub fn new(
673 bins_per_channel: usize,
674 num_channels: usize,
675 kernel_type: CVKernelType,
676 ) -> Self {
677 Self {
678 bins_per_channel,
679 num_channels,
680 kernel_type,
681 }
682 }
683
684 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
685 let expected_size = self.bins_per_channel * self.num_channels;
686 if x.len() != expected_size || y.len() != expected_size {
687 return Err(CVKernelError::DimensionMismatch {
688 expected: expected_size,
689 actual: x.len(),
690 });
691 }
692
693 let cv_kernel = CVKernelFunction::new(self.kernel_type.clone());
694 cv_kernel.compute(x, y)
695 }
696 }
697
698 pub struct TextureKernel {
700 pub radius: f64,
701 pub neighbors: usize,
702 pub uniform_patterns_only: bool,
703 }
704
705 impl TextureKernel {
706 pub fn new(radius: f64, neighbors: usize, uniform_patterns_only: bool) -> Self {
707 Self {
708 radius,
709 neighbors,
710 uniform_patterns_only,
711 }
712 }
713
714 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
715 let expected_bins = if self.uniform_patterns_only {
716 self.neighbors + 2 } else {
718 2_usize.pow(self.neighbors as u32)
719 };
720
721 if x.len() != expected_bins || y.len() != expected_bins {
722 return Err(CVKernelError::DimensionMismatch {
723 expected: expected_bins,
724 actual: x.len(),
725 });
726 }
727
728 let cv_kernel = CVKernelFunction::new(CVKernelType::LBP {
729 radius: self.radius,
730 neighbors: self.neighbors,
731 });
732 cv_kernel.compute(x, y)
733 }
734 }
735
736 pub struct DeepFeatureKernel {
739 pub feature_dimension: usize,
740 pub kernel_type: DeepFeatureKernelType,
741 pub normalize: bool,
742 }
743
744 #[derive(Debug, Clone, PartialEq)]
746 pub enum DeepFeatureKernelType {
747 Linear,
749 RBF { gamma: f64 },
751 Cosine,
753 Polynomial { degree: f64, gamma: f64, coef0: f64 },
755 }
756
757 impl DeepFeatureKernel {
758 pub fn new(feature_dimension: usize, kernel_type: DeepFeatureKernelType) -> Self {
759 Self {
760 feature_dimension,
761 kernel_type,
762 normalize: true,
763 }
764 }
765
766 pub fn with_normalization(mut self, normalize: bool) -> Self {
767 self.normalize = normalize;
768 self
769 }
770
771 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
772 if x.len() != self.feature_dimension || y.len() != self.feature_dimension {
773 return Err(CVKernelError::DimensionMismatch {
774 expected: self.feature_dimension,
775 actual: x.len(),
776 });
777 }
778
779 let mut x_proc = x.to_vec();
780 let mut y_proc = y.to_vec();
781
782 if self.normalize {
783 let x_norm = x.iter().map(|v| v * v).sum::<f64>().sqrt();
784 let y_norm = y.iter().map(|v| v * v).sum::<f64>().sqrt();
785
786 if x_norm > 0.0 {
787 x_proc.iter_mut().for_each(|v| *v /= x_norm);
788 }
789 if y_norm > 0.0 {
790 y_proc.iter_mut().for_each(|v| *v /= y_norm);
791 }
792 }
793
794 let kernel_value = match &self.kernel_type {
795 DeepFeatureKernelType::Linear => {
796 x_proc.iter().zip(y_proc.iter()).map(|(a, b)| a * b).sum()
797 }
798 DeepFeatureKernelType::RBF { gamma } => {
799 let squared_dist: f64 = x_proc
800 .iter()
801 .zip(y_proc.iter())
802 .map(|(a, b)| (a - b).powi(2))
803 .sum();
804 (-gamma * squared_dist).exp()
805 }
806 DeepFeatureKernelType::Cosine => {
807 let dot_product: f64 =
808 x_proc.iter().zip(y_proc.iter()).map(|(a, b)| a * b).sum();
809 let x_magnitude = x_proc.iter().map(|v| v * v).sum::<f64>().sqrt();
810 let y_magnitude = y_proc.iter().map(|v| v * v).sum::<f64>().sqrt();
811 if x_magnitude > 0.0 && y_magnitude > 0.0 {
812 dot_product / (x_magnitude * y_magnitude)
813 } else {
814 0.0
815 }
816 }
817 DeepFeatureKernelType::Polynomial {
818 degree,
819 gamma,
820 coef0,
821 } => {
822 let dot_product: f64 =
823 x_proc.iter().zip(y_proc.iter()).map(|(a, b)| a * b).sum();
824 (gamma * dot_product + coef0).powf(*degree)
825 }
826 };
827
828 Ok(kernel_value)
829 }
830 }
831
832 pub struct BagOfFeaturesKernel {
835 pub vocabulary_size: usize,
836 pub kernel_type: CVKernelType,
837 }
838
839 impl BagOfFeaturesKernel {
840 pub fn new(vocabulary_size: usize, kernel_type: CVKernelType) -> Self {
841 Self {
842 vocabulary_size,
843 kernel_type,
844 }
845 }
846
847 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
849 if x.len() != self.vocabulary_size || y.len() != self.vocabulary_size {
850 return Err(CVKernelError::DimensionMismatch {
851 expected: self.vocabulary_size,
852 actual: x.len(),
853 });
854 }
855
856 let cv_kernel = CVKernelFunction::new(self.kernel_type.clone());
857 cv_kernel.compute(x, y)
858 }
859 }
860
861 pub struct FisherVectorKernel {
864 pub n_components: usize,
865 pub descriptor_dim: usize,
866 pub sigma: f64,
867 }
868
869 impl FisherVectorKernel {
870 pub fn new(n_components: usize, descriptor_dim: usize, sigma: f64) -> Self {
871 Self {
872 n_components,
873 descriptor_dim,
874 sigma,
875 }
876 }
877
878 pub fn fisher_vector_dim(&self) -> usize {
880 2 * self.n_components * self.descriptor_dim
881 }
882
883 pub fn compute(&self, x: &[f64], y: &[f64]) -> Result<f64, CVKernelError> {
884 let expected_dim = self.fisher_vector_dim();
885 if x.len() != expected_dim || y.len() != expected_dim {
886 return Err(CVKernelError::DimensionMismatch {
887 expected: expected_dim,
888 actual: x.len(),
889 });
890 }
891
892 let x_power: Vec<f64> = x.iter().map(|v| v.signum() * v.abs().sqrt()).collect();
894 let y_power: Vec<f64> = y.iter().map(|v| v.signum() * v.abs().sqrt()).collect();
895
896 let x_norm = x_power.iter().map(|v| v * v).sum::<f64>().sqrt();
897 let y_norm = y_power.iter().map(|v| v * v).sum::<f64>().sqrt();
898
899 let x_normalized: Vec<f64> = if x_norm > 0.0 {
900 x_power.iter().map(|v| v / x_norm).collect()
901 } else {
902 x_power
903 };
904
905 let y_normalized: Vec<f64> = if y_norm > 0.0 {
906 y_power.iter().map(|v| v / y_norm).collect()
907 } else {
908 y_power
909 };
910
911 let kernel_value: f64 = x_normalized
913 .iter()
914 .zip(y_normalized.iter())
915 .map(|(a, b)| a * b)
916 .sum();
917
918 Ok(kernel_value)
919 }
920 }
921}
922
923#[allow(non_snake_case)]
924#[cfg(test)]
925mod tests {
926 use super::*;
927 use approx::assert_abs_diff_eq;
928
929 #[test]
930 fn test_histogram_intersection_kernel() {
931 let kernel = CVKernelFunction::new(CVKernelType::HistogramIntersection);
932
933 let x = vec![1.0, 2.0, 3.0, 4.0];
934 let y = vec![2.0, 1.0, 4.0, 3.0];
935
936 let result = kernel.compute(&x, &y).expect("computation should succeed");
937 assert_abs_diff_eq!(result, 8.0, epsilon = 1e-10);
938 }
939
940 #[test]
941 fn test_chi_square_kernel() {
942 let kernel = CVKernelFunction::new(CVKernelType::ChiSquare { gamma: 1.0 });
943
944 let x = vec![1.0, 2.0, 3.0, 4.0];
945 let y = vec![1.0, 2.0, 3.0, 4.0];
946
947 let result = kernel.compute(&x, &y).expect("computation should succeed");
948 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
949 }
950
951 #[test]
952 fn test_bhattacharyya_kernel() {
953 let kernel = CVKernelFunction::new(CVKernelType::Bhattacharyya);
954
955 let x = vec![1.0, 2.0, 3.0, 4.0];
956 let y = vec![1.0, 2.0, 3.0, 4.0];
957
958 let result = kernel.compute(&x, &y).expect("computation should succeed");
959 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
960 }
961
962 #[test]
963 fn test_additive_chi_square_kernel() {
964 let kernel = CVKernelFunction::new(CVKernelType::AdditiveChiSquare);
965
966 let x = vec![1.0, 2.0, 3.0, 4.0];
967 let y = vec![2.0, 1.0, 4.0, 3.0];
968
969 let result = kernel.compute(&x, &y).expect("computation should succeed");
970 assert!(result > 0.0);
971 }
972
973 #[test]
974 fn test_spatial_pyramid_kernel() {
975 let weights = cv_utils::create_pyramid_weights(2);
976 let kernel = CVKernelFunction::new(CVKernelType::SpatialPyramid { levels: 2, weights });
977
978 let x = vec![
980 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
981 17.0, 18.0, 19.0, 20.0, 21.0,
982 ];
983 let y = vec![
984 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
985 17.0, 18.0, 19.0, 20.0, 21.0,
986 ];
987
988 let result = kernel.compute(&x, &y).expect("computation should succeed");
989 assert!(result > 0.0);
990 }
991
992 #[test]
993 fn test_kernel_matrix_computation() {
994 let kernel = CVKernelFunction::new(CVKernelType::HistogramIntersection);
995
996 let X_var = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
997 .expect("array shape mismatch");
998 let Y_var = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
999 .expect("array shape mismatch");
1000
1001 let result = kernel
1002 .compute_matrix(&X_var, &Y_var)
1003 .expect("matrix computation should succeed");
1004 assert_eq!(result.dim(), (2, 2));
1005 }
1006
1007 #[test]
1008 fn test_cv_utils() {
1009 let weights = cv_utils::create_pyramid_weights(2);
1010 assert_eq!(weights.len(), 3);
1011 assert_eq!(weights[0], 1.0);
1012
1013 let distance_matrix = cv_utils::create_distance_matrix(3, "euclidean");
1014 assert_eq!(distance_matrix.dim(), (3, 3));
1015
1016 let hist = vec![1.0, 2.0, 3.0, 4.0];
1017 let normalized = cv_utils::normalize_histogram(&hist);
1018 let sum: f64 = normalized.iter().sum();
1019 assert_abs_diff_eq!(sum, 1.0, epsilon = 1e-10);
1020 }
1021
1022 #[test]
1023 fn test_specialized_kernels() {
1024 let sift_kernel = specialized_cv_kernels::SIFTKernel::new(1.0, true);
1025 let x = vec![1.0; 128];
1026 let y = vec![1.0; 128];
1027 let result = sift_kernel.compute(&x, &y);
1028 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1029
1030 let color_kernel = specialized_cv_kernels::ColorHistogramKernel::new(
1031 8,
1032 3,
1033 CVKernelType::HistogramIntersection,
1034 );
1035 let x = vec![1.0; 24];
1036 let y = vec![1.0; 24];
1037 let result = color_kernel
1038 .compute(&x, &y)
1039 .expect("computation should succeed");
1040 assert_eq!(result, 24.0);
1041 }
1042
1043 #[test]
1044 fn test_sift_kernel_advanced() {
1045 use specialized_cv_kernels::{DescriptorMatchingMethod, SIFTKernel};
1046
1047 let sift_rbf = SIFTKernel::new(1.0, true);
1049 let x = vec![1.0; 128];
1050 let y = vec![1.0; 128];
1051 let result = sift_rbf.compute(&x, &y);
1052 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1053
1054 let sift_cosine = SIFTKernel::new(1.0, true)
1056 .with_matching_method(DescriptorMatchingMethod::CosineSimilarity);
1057 let result = sift_cosine.compute(&x, &y);
1058 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1059
1060 let sift_laplacian =
1062 SIFTKernel::new(1.0, true).with_matching_method(DescriptorMatchingMethod::Laplacian);
1063 let result = sift_laplacian.compute(&x, &y);
1064 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1065
1066 let x = vec![1.0; 128];
1068 let mut y = vec![1.0; 128];
1069 y[0] = 0.5; let result = sift_rbf.compute(&x, &y);
1071 assert!(result < 1.0 && result > 0.0);
1072 }
1073
1074 #[test]
1075 fn test_surf_kernel() {
1076 use specialized_cv_kernels::SURFKernel;
1077
1078 let surf_64 = SURFKernel::new(1.0, true, false);
1080 let x = vec![1.0; 64];
1081 let y = vec![1.0; 64];
1082 let result = surf_64.compute(&x, &y);
1083 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1084
1085 let surf_128 = SURFKernel::new(1.0, true, true);
1087 let x = vec![1.0; 128];
1088 let y = vec![1.0; 128];
1089 let result = surf_128.compute(&x, &y);
1090 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1091
1092 let x = vec![1.0; 32];
1094 let result = surf_64.compute(&x, &x);
1095 assert_eq!(result, 0.0);
1096 }
1097
1098 #[test]
1099 fn test_deep_feature_kernel() {
1100 use specialized_cv_kernels::{DeepFeatureKernel, DeepFeatureKernelType};
1101
1102 let deep_linear = DeepFeatureKernel::new(512, DeepFeatureKernelType::Linear);
1104 let x = vec![1.0; 512];
1105 let y = vec![1.0; 512];
1106 let result = deep_linear
1107 .compute(&x, &y)
1108 .expect("computation should succeed");
1109 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1110
1111 let deep_rbf = DeepFeatureKernel::new(512, DeepFeatureKernelType::RBF { gamma: 0.5 });
1113 let result = deep_rbf
1114 .compute(&x, &y)
1115 .expect("computation should succeed");
1116 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1117
1118 let deep_cosine = DeepFeatureKernel::new(512, DeepFeatureKernelType::Cosine);
1120 let result = deep_cosine
1121 .compute(&x, &y)
1122 .expect("computation should succeed");
1123 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1124
1125 let deep_poly = DeepFeatureKernel::new(
1127 512,
1128 DeepFeatureKernelType::Polynomial {
1129 degree: 2.0,
1130 gamma: 1.0,
1131 coef0: 1.0,
1132 },
1133 );
1134 let result = deep_poly
1135 .compute(&x, &y)
1136 .expect("computation should succeed");
1137 assert!(result > 1.0); let x = vec![1.0; 256];
1141 let result = deep_linear.compute(&x, &y);
1142 assert!(result.is_err());
1143 }
1144
1145 #[test]
1146 fn test_bag_of_features_kernel() {
1147 use specialized_cv_kernels::BagOfFeaturesKernel;
1148
1149 let bof_kernel = BagOfFeaturesKernel::new(100, CVKernelType::HistogramIntersection);
1150 let x = vec![1.0; 100];
1151 let y = vec![1.0; 100];
1152 let result = bof_kernel
1153 .compute(&x, &y)
1154 .expect("computation should succeed");
1155 assert_eq!(result, 100.0);
1156
1157 let bof_chi = BagOfFeaturesKernel::new(100, CVKernelType::ChiSquare { gamma: 1.0 });
1159 let result = bof_chi.compute(&x, &y).expect("computation should succeed");
1160 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
1161 }
1162
1163 #[test]
1164 fn test_fisher_vector_kernel() {
1165 use specialized_cv_kernels::FisherVectorKernel;
1166
1167 let fisher_kernel = FisherVectorKernel::new(16, 64, 1.0);
1168 let dim = fisher_kernel.fisher_vector_dim();
1169 assert_eq!(dim, 2 * 16 * 64);
1170
1171 let x = vec![1.0; dim];
1172 let y = vec![1.0; dim];
1173 let result = fisher_kernel
1174 .compute(&x, &y)
1175 .expect("computation should succeed");
1176 assert!(result > 0.0 && result <= 1.0);
1177
1178 let mut x_varied = vec![1.0; dim];
1180 x_varied[0] = 0.5; let result_varied = fisher_kernel
1182 .compute(&x_varied, &y)
1183 .expect("computation should succeed");
1184 assert!(result_varied < 1.0 && result_varied > 0.0);
1185 }
1186
1187 #[test]
1188 fn test_error_handling() {
1189 let kernel = CVKernelFunction::new(CVKernelType::HistogramIntersection);
1190
1191 let x = vec![1.0, 2.0, 3.0];
1193 let y = vec![1.0, 2.0];
1194
1195 let result = kernel.compute(&x, &y);
1196 assert!(result.is_err());
1197
1198 let x = vec![-1.0, 2.0, 3.0];
1200 let y = vec![1.0, 2.0, 3.0];
1201
1202 let result = kernel.compute(&x, &y);
1203 assert!(result.is_err());
1204 }
1205}