1use yscv_tensor::Tensor;
2
3use super::super::ImgProcError;
4use super::super::shape::hwc_shape;
5
6#[derive(Debug, Clone, PartialEq)]
8pub struct Contour {
9 pub points: Vec<(usize, usize)>,
10}
11
12pub fn find_contours(input: &Tensor) -> Result<Vec<Contour>, ImgProcError> {
18 let (h, w, c) = hwc_shape(input)?;
19 if c != 1 {
20 return Err(ImgProcError::InvalidChannelCount {
21 expected: 1,
22 got: c,
23 });
24 }
25 let data = input.data();
26 let mut visited = vec![false; h * w];
27 let mut contours = Vec::new();
28
29 const DIRS: [(i32, i32); 8] = [
31 (1, 0),
32 (1, 1),
33 (0, 1),
34 (-1, 1),
35 (-1, 0),
36 (-1, -1),
37 (0, -1),
38 (1, -1),
39 ];
40
41 for y in 0..h {
42 for x in 0..w {
43 if data[y * w + x] <= 0.5 || visited[y * w + x] {
44 continue;
45 }
46 let is_border = x == 0
48 || x == w - 1
49 || y == 0
50 || y == h - 1
51 || data[y * w + x - 1] <= 0.5
52 || data[y * w + x + 1] <= 0.5
53 || data[(y - 1) * w + x] <= 0.5
54 || data[(y + 1) * w + x] <= 0.5;
55 if !is_border {
56 continue;
57 }
58
59 let mut contour_points = Vec::new();
61 let start = (x, y);
62 let mut cur = start;
63 let mut dir = 0usize; let max_steps = h * w * 2;
65 let mut steps = 0;
66
67 loop {
68 contour_points.push(cur);
69 visited[cur.1 * w + cur.0] = true;
70
71 let mut found = false;
72 let start_dir = (dir + 5) % 8; for i in 0..8 {
74 let d = (start_dir + i) % 8;
75 let (dx, dy) = DIRS[d];
76 let nx = cur.0 as i32 + dx;
77 let ny = cur.1 as i32 + dy;
78 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
79 let (ux, uy) = (nx as usize, ny as usize);
80 if data[uy * w + ux] > 0.5 {
81 cur = (ux, uy);
82 dir = d;
83 found = true;
84 break;
85 }
86 }
87 }
88
89 if !found || cur == start || steps > max_steps {
90 break;
91 }
92 steps += 1;
93 }
94
95 if contour_points.len() >= 2 {
96 contours.push(Contour {
97 points: contour_points,
98 });
99 }
100 }
101 }
102
103 Ok(contours)
104}
105
106pub fn convex_hull(points: &[(f32, f32)]) -> Vec<(f32, f32)> {
110 if points.len() < 3 {
111 return points.to_vec();
112 }
113
114 let mut pts: Vec<(f32, f32)> = points.to_vec();
115 pts.sort_unstable_by(|a, b| {
116 a.0.partial_cmp(&b.0)
117 .unwrap_or(std::cmp::Ordering::Equal)
118 .then(a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
119 });
120
121 let n = pts.len();
122 let mut hull: Vec<(f32, f32)> = Vec::with_capacity(2 * n);
123
124 for &p in &pts {
125 while hull.len() >= 2 && cross_2d(hull[hull.len() - 2], hull[hull.len() - 1], p) <= 0.0 {
126 hull.pop();
127 }
128 hull.push(p);
129 }
130
131 let lower_len = hull.len() + 1;
132 for &p in pts.iter().rev().skip(1) {
133 while hull.len() >= lower_len
134 && cross_2d(hull[hull.len() - 2], hull[hull.len() - 1], p) <= 0.0
135 {
136 hull.pop();
137 }
138 hull.push(p);
139 }
140 hull.pop();
141 hull
142}
143
144fn cross_2d(o: (f32, f32), a: (f32, f32), b: (f32, f32)) -> f32 {
145 (a.0 - o.0) * (b.1 - o.1) - (a.1 - o.1) * (b.0 - o.0)
146}
147
148pub fn min_area_rect(points: &[(f32, f32)]) -> Option<(f32, f32, f32, f32, f32)> {
153 let hull = convex_hull(points);
154 if hull.len() < 2 {
155 return hull.first().map(|p| (p.0, p.1, 0.0, 0.0, 0.0));
156 }
157
158 let n = hull.len();
159 let mut best_area = f32::MAX;
160 let mut best_rect = (0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32);
161
162 for i in 0..n {
163 let j = (i + 1) % n;
164 let edge_x = hull[j].0 - hull[i].0;
165 let edge_y = hull[j].1 - hull[i].1;
166 let edge_len = (edge_x * edge_x + edge_y * edge_y).sqrt();
167 if edge_len < 1e-12 {
168 continue;
169 }
170 let ux = edge_x / edge_len;
171 let uy = edge_y / edge_len;
172
173 let mut min_proj = f32::MAX;
174 let mut max_proj = f32::MIN;
175 let mut min_perp = f32::MAX;
176 let mut max_perp = f32::MIN;
177
178 for &p in &hull {
179 let dx = p.0 - hull[i].0;
180 let dy = p.1 - hull[i].1;
181 let proj = dx * ux + dy * uy;
182 let perp = -dx * uy + dy * ux;
183 min_proj = min_proj.min(proj);
184 max_proj = max_proj.max(proj);
185 min_perp = min_perp.min(perp);
186 max_perp = max_perp.max(perp);
187 }
188
189 let width = max_proj - min_proj;
190 let height = max_perp - min_perp;
191 let area = width * height;
192 if area < best_area {
193 best_area = area;
194 let mid_proj = (min_proj + max_proj) * 0.5;
195 let mid_perp = (min_perp + max_perp) * 0.5;
196 let cx = hull[i].0 + ux * mid_proj - uy * mid_perp;
197 let cy = hull[i].1 + uy * mid_proj + ux * mid_perp;
198 let angle = uy.atan2(ux);
199 best_rect = (cx, cy, width, height, angle);
200 }
201 }
202
203 Some(best_rect)
204}
205
206pub fn homography_4pt(
212 src: &[(f32, f32); 4],
213 dst: &[(f32, f32); 4],
214) -> Result<[f32; 9], ImgProcError> {
215 let mut a = [[0.0f64; 8]; 8];
216 let mut b = [0.0f64; 8];
217
218 for i in 0..4 {
219 let (sx, sy) = (src[i].0 as f64, src[i].1 as f64);
220 let (dx, dy) = (dst[i].0 as f64, dst[i].1 as f64);
221 let r = i * 2;
222 a[r] = [sx, sy, 1.0, 0.0, 0.0, 0.0, -dx * sx, -dx * sy];
223 b[r] = dx;
224 a[r + 1] = [0.0, 0.0, 0.0, sx, sy, 1.0, -dy * sx, -dy * sy];
225 b[r + 1] = dy;
226 }
227
228 let h =
229 solve_8x8(&a, &b).ok_or(ImgProcError::InvalidOutputDimensions { out_h: 0, out_w: 0 })?;
230
231 Ok([
232 h[0] as f32,
233 h[1] as f32,
234 h[2] as f32,
235 h[3] as f32,
236 h[4] as f32,
237 h[5] as f32,
238 h[6] as f32,
239 h[7] as f32,
240 1.0,
241 ])
242}
243
244#[allow(clippy::needless_range_loop)]
245fn solve_8x8(a: &[[f64; 8]; 8], b: &[f64; 8]) -> Option<[f64; 8]> {
246 let mut m = [[0.0f64; 9]; 8];
247 for i in 0..8 {
248 for j in 0..8 {
249 m[i][j] = a[i][j];
250 }
251 m[i][8] = b[i];
252 }
253
254 for col in 0..8 {
255 let mut pivot = col;
256 let mut max_val = m[col][col].abs();
257 for row in (col + 1)..8 {
258 if m[row][col].abs() > max_val {
259 max_val = m[row][col].abs();
260 pivot = row;
261 }
262 }
263 if max_val < 1e-12 {
264 return None;
265 }
266 if pivot != col {
267 m.swap(pivot, col);
268 }
269 let diag = m[col][col];
270 for j in col..9 {
271 m[col][j] /= diag;
272 }
273 for row in 0..8 {
274 if row != col {
275 let factor = m[row][col];
276 for j in col..9 {
277 m[row][j] -= factor * m[col][j];
278 }
279 }
280 }
281 }
282
283 let mut result = [0.0f64; 8];
284 for i in 0..8 {
285 result[i] = m[i][8];
286 }
287 Some(result)
288}
289
290pub fn ransac_homography(
296 src: &[(f32, f32)],
297 dst: &[(f32, f32)],
298 iterations: usize,
299 inlier_threshold: f32,
300 rng_seed: u64,
301) -> Option<([f32; 9], Vec<bool>)> {
302 if src.len() < 4 || src.len() != dst.len() {
303 return None;
304 }
305 let n = src.len();
306 let mut best_h = [0.0f32; 9];
307 let mut best_inliers: Vec<bool> = vec![false; n];
308 let mut best_count = 0usize;
309 let mut rng_state = rng_seed;
310
311 for _ in 0..iterations {
312 let mut indices = [0usize; 4];
313 for slot in &mut indices {
314 rng_state = rng_state
315 .wrapping_mul(6364136223846793005)
316 .wrapping_add(1442695040888963407);
317 *slot = (rng_state >> 33) as usize % n;
318 }
319 let src4: [(f32, f32); 4] = [
320 src[indices[0]],
321 src[indices[1]],
322 src[indices[2]],
323 src[indices[3]],
324 ];
325 let dst4: [(f32, f32); 4] = [
326 dst[indices[0]],
327 dst[indices[1]],
328 dst[indices[2]],
329 dst[indices[3]],
330 ];
331 let h = match homography_4pt(&src4, &dst4) {
332 Ok(h) => h,
333 Err(_) => continue,
334 };
335 let mut inliers = vec![false; n];
336 let mut count = 0;
337 for i in 0..n {
338 let (sx, sy) = src[i];
339 let denom = h[6] * sx + h[7] * sy + h[8];
340 if denom.abs() < 1e-12 {
341 continue;
342 }
343 let px = (h[0] * sx + h[1] * sy + h[2]) / denom;
344 let py = (h[3] * sx + h[4] * sy + h[5]) / denom;
345 let err = ((px - dst[i].0).powi(2) + (py - dst[i].1).powi(2)).sqrt();
346 if err < inlier_threshold {
347 inliers[i] = true;
348 count += 1;
349 }
350 }
351 if count > best_count {
352 best_count = count;
353 best_h = h;
354 best_inliers = inliers;
355 }
356 }
357
358 if best_count >= 4 {
359 Some((best_h, best_inliers))
360 } else {
361 None
362 }
363}
364
365pub fn fit_ellipse(points: &[(f32, f32)]) -> Option<(f32, f32, f32, f32, f32)> {
369 if points.len() < 5 {
370 return None;
371 }
372 let n = points.len() as f32;
373 let cx: f32 = points.iter().map(|p| p.0).sum::<f32>() / n;
374 let cy: f32 = points.iter().map(|p| p.1).sum::<f32>() / n;
375
376 let mut cov_xx = 0.0f32;
377 let mut cov_xy = 0.0f32;
378 let mut cov_yy = 0.0f32;
379 for &(x, y) in points {
380 let dx = x - cx;
381 let dy = y - cy;
382 cov_xx += dx * dx;
383 cov_xy += dx * dy;
384 cov_yy += dy * dy;
385 }
386 cov_xx /= n;
387 cov_xy /= n;
388 cov_yy /= n;
389
390 let trace = cov_xx + cov_yy;
391 let det = cov_xx * cov_yy - cov_xy * cov_xy;
392 let disc = (trace * trace / 4.0 - det).max(0.0).sqrt();
393 let lambda1 = trace / 2.0 + disc;
394 let lambda2 = (trace / 2.0 - disc).max(1e-12);
395
396 let angle = cov_xy.atan2(lambda1 - cov_yy);
397 let a = (2.0 * lambda1).sqrt();
398 let b = (2.0 * lambda2).sqrt();
399
400 Some((cx, cy, a, b, angle))
401}
402
403pub fn approx_poly_dp(contour: &[(f32, f32)], epsilon: f32) -> Vec<(f32, f32)> {
408 if contour.len() <= 2 {
409 return contour.to_vec();
410 }
411 let n = contour.len();
412 let (first, last) = (contour[0], contour[n - 1]);
413
414 let mut max_dist = 0.0f32;
415 let mut max_idx = 0;
416 for (i, &pt) in contour.iter().enumerate().skip(1).take(n - 2) {
417 let d = point_line_dist_f32(pt, first, last);
418 if d > max_dist {
419 max_dist = d;
420 max_idx = i;
421 }
422 }
423
424 if max_dist > epsilon {
425 let mut left = approx_poly_dp(&contour[..=max_idx], epsilon);
426 let right = approx_poly_dp(&contour[max_idx..], epsilon);
427 left.pop();
428 left.extend(right);
429 left
430 } else {
431 vec![first, last]
432 }
433}
434
435#[derive(Debug, Clone, PartialEq)]
437pub struct ComponentStats {
438 pub label: usize,
439 pub area: usize,
440 pub bbox: (usize, usize, usize, usize), pub centroid: (f32, f32), }
443
444#[derive(Debug, Clone, PartialEq)]
446pub struct RegionProp {
447 pub label: usize,
448 pub area: usize,
449 pub centroid: (f32, f32),
450 pub bbox: (usize, usize, usize, usize), pub perimeter: f32,
452}
453
454pub fn connected_components_with_stats(
460 img: &Tensor,
461) -> Result<(Tensor, Vec<ComponentStats>), ImgProcError> {
462 let (h, w, c) = hwc_shape(img)?;
463 if c != 1 {
464 return Err(ImgProcError::InvalidChannelCount {
465 expected: 1,
466 got: c,
467 });
468 }
469 let data = img.data();
470 let mut labels = vec![0u32; h * w];
471 let mut next_label = 1u32;
472 let mut stats_list: Vec<ComponentStats> = Vec::new();
473
474 for y in 0..h {
475 for x in 0..w {
476 let idx = y * w + x;
477 if data[idx] <= 0.5 || labels[idx] != 0 {
478 continue;
479 }
480 let current_label = next_label;
482 next_label += 1;
483 labels[idx] = current_label;
484
485 let mut queue = std::collections::VecDeque::new();
486 queue.push_back((x, y));
487
488 let mut area = 0usize;
489 let mut sum_x = 0.0f64;
490 let mut sum_y = 0.0f64;
491 let mut min_x = x;
492 let mut max_x = x;
493 let mut min_y = y;
494 let mut max_y = y;
495
496 while let Some((cx, cy)) = queue.pop_front() {
497 area += 1;
498 sum_x += cx as f64;
499 sum_y += cy as f64;
500 if cx < min_x {
501 min_x = cx;
502 }
503 if cx > max_x {
504 max_x = cx;
505 }
506 if cy < min_y {
507 min_y = cy;
508 }
509 if cy > max_y {
510 max_y = cy;
511 }
512
513 for &(dx, dy) in &[(0isize, -1isize), (0, 1), (-1, 0), (1, 0)] {
514 let nx = cx as isize + dx;
515 let ny = cy as isize + dy;
516 if nx >= 0 && nx < w as isize && ny >= 0 && ny < h as isize {
517 let nidx = ny as usize * w + nx as usize;
518 if data[nidx] > 0.5 && labels[nidx] == 0 {
519 labels[nidx] = current_label;
520 queue.push_back((nx as usize, ny as usize));
521 }
522 }
523 }
524 }
525
526 stats_list.push(ComponentStats {
527 label: current_label as usize,
528 area,
529 bbox: (min_x, min_y, max_x - min_x + 1, max_y - min_y + 1),
530 centroid: ((sum_x / area as f64) as f32, (sum_y / area as f64) as f32),
531 });
532 }
533 }
534
535 let label_data: Vec<f32> = labels.iter().map(|&l| l as f32).collect();
536 let label_tensor = Tensor::from_vec(vec![h, w, 1], label_data)?;
537 Ok((label_tensor, stats_list))
538}
539
540pub fn region_props(labels: &Tensor) -> Result<Vec<RegionProp>, ImgProcError> {
546 let (h, w, c) = hwc_shape(labels)?;
547 if c != 1 {
548 return Err(ImgProcError::InvalidChannelCount {
549 expected: 1,
550 got: c,
551 });
552 }
553 let data = labels.data();
554
555 let mut max_label = 0u32;
557 for &v in data.iter() {
558 let l = v as u32;
559 if l > max_label {
560 max_label = l;
561 }
562 }
563 if max_label == 0 {
564 return Ok(Vec::new());
565 }
566
567 let n = max_label as usize;
569 let mut area = vec![0usize; n + 1];
570 let mut sum_x = vec![0.0f64; n + 1];
571 let mut sum_y = vec![0.0f64; n + 1];
572 let mut min_x = vec![usize::MAX; n + 1];
573 let mut max_x = vec![0usize; n + 1];
574 let mut min_y = vec![usize::MAX; n + 1];
575 let mut max_y = vec![0usize; n + 1];
576 let mut perim = vec![0usize; n + 1];
577
578 for y in 0..h {
579 for x in 0..w {
580 let l = data[y * w + x] as u32;
581 if l == 0 {
582 continue;
583 }
584 let li = l as usize;
585 area[li] += 1;
586 sum_x[li] += x as f64;
587 sum_y[li] += y as f64;
588 if x < min_x[li] {
589 min_x[li] = x;
590 }
591 if x > max_x[li] {
592 max_x[li] = x;
593 }
594 if y < min_y[li] {
595 min_y[li] = y;
596 }
597 if y > max_y[li] {
598 max_y[li] = y;
599 }
600
601 let is_boundary = x == 0
603 || x == w - 1
604 || y == 0
605 || y == h - 1
606 || data[y * w + (x - 1)] as u32 != l
607 || data[y * w + (x + 1)] as u32 != l
608 || data[(y - 1) * w + x] as u32 != l
609 || data[(y + 1) * w + x] as u32 != l;
610 if is_boundary {
611 perim[li] += 1;
612 }
613 }
614 }
615
616 let mut props = Vec::new();
617 for li in 1..=n {
618 if area[li] == 0 {
619 continue;
620 }
621 props.push(RegionProp {
622 label: li,
623 area: area[li],
624 centroid: (
625 (sum_x[li] / area[li] as f64) as f32,
626 (sum_y[li] / area[li] as f64) as f32,
627 ),
628 bbox: (
629 min_x[li],
630 min_y[li],
631 max_x[li] - min_x[li] + 1,
632 max_y[li] - min_y[li] + 1,
633 ),
634 perimeter: perim[li] as f32,
635 });
636 }
637
638 Ok(props)
639}
640
641pub fn hu_moments(img: &Tensor) -> Result<[f64; 7], ImgProcError> {
646 let (h, w, c) = hwc_shape(img)?;
647 if c != 1 {
648 return Err(ImgProcError::InvalidChannelCount {
649 expected: 1,
650 got: c,
651 });
652 }
653 let data = img.data();
654
655 let mut m00 = 0.0f64;
657 let mut m10 = 0.0f64;
658 let mut m01 = 0.0f64;
659 for y in 0..h {
660 for x in 0..w {
661 let v = data[y * w + x] as f64;
662 m00 += v;
663 m10 += x as f64 * v;
664 m01 += y as f64 * v;
665 }
666 }
667 if m00.abs() < 1e-15 {
668 return Ok([0.0; 7]);
669 }
670 let cx = m10 / m00;
671 let cy = m01 / m00;
672
673 let mut mu20 = 0.0f64;
675 let mut mu02 = 0.0f64;
676 let mut mu11 = 0.0f64;
677 let mut mu30 = 0.0f64;
678 let mut mu03 = 0.0f64;
679 let mut mu21 = 0.0f64;
680 let mut mu12 = 0.0f64;
681 for y in 0..h {
682 for x in 0..w {
683 let v = data[y * w + x] as f64;
684 let dx = x as f64 - cx;
685 let dy = y as f64 - cy;
686 mu20 += dx * dx * v;
687 mu02 += dy * dy * v;
688 mu11 += dx * dy * v;
689 mu30 += dx * dx * dx * v;
690 mu03 += dy * dy * dy * v;
691 mu21 += dx * dx * dy * v;
692 mu12 += dx * dy * dy * v;
693 }
694 }
695
696 let n20 = mu20 / m00.powf(2.0);
698 let n02 = mu02 / m00.powf(2.0);
699 let n11 = mu11 / m00.powf(2.0);
700 let n30 = mu30 / m00.powf(2.5);
701 let n03 = mu03 / m00.powf(2.5);
702 let n21 = mu21 / m00.powf(2.5);
703 let n12 = mu12 / m00.powf(2.5);
704
705 let h1 = n20 + n02;
707 let h2 = (n20 - n02).powi(2) + 4.0 * n11 * n11;
708 let h3 = (n30 - 3.0 * n12).powi(2) + (3.0 * n21 - n03).powi(2);
709 let h4 = (n30 + n12).powi(2) + (n21 + n03).powi(2);
710 let h5 = (n30 - 3.0 * n12) * (n30 + n12) * ((n30 + n12).powi(2) - 3.0 * (n21 + n03).powi(2))
711 + (3.0 * n21 - n03) * (n21 + n03) * (3.0 * (n30 + n12).powi(2) - (n21 + n03).powi(2));
712 let h6 = (n20 - n02) * ((n30 + n12).powi(2) - (n21 + n03).powi(2))
713 + 4.0 * n11 * (n30 + n12) * (n21 + n03);
714 let h7 = (3.0 * n21 - n03) * (n30 + n12) * ((n30 + n12).powi(2) - 3.0 * (n21 + n03).powi(2))
715 - (n30 - 3.0 * n12) * (n21 + n03) * (3.0 * (n30 + n12).powi(2) - (n21 + n03).powi(2));
716
717 Ok([h1, h2, h3, h4, h5, h6, h7])
718}
719
720fn point_line_dist_f32(p: (f32, f32), a: (f32, f32), b: (f32, f32)) -> f32 {
721 let dx = b.0 - a.0;
722 let dy = b.1 - a.1;
723 let len_sq = dx * dx + dy * dy;
724 if len_sq < 1e-12 {
725 return ((p.0 - a.0).powi(2) + (p.1 - a.1).powi(2)).sqrt();
726 }
727 let cross = ((p.0 - a.0) * dy - (p.1 - a.1) * dx).abs();
728 cross / len_sq.sqrt()
729}
730
731pub fn contour_area(contour: &[(usize, usize)]) -> f64 {
736 if contour.len() < 3 {
737 return 0.0;
738 }
739 let n = contour.len();
740 let mut sum = 0.0f64;
741 for i in 0..n {
742 let j = (i + 1) % n;
743 let (x1, y1) = (contour[i].0 as f64, contour[i].1 as f64);
744 let (x2, y2) = (contour[j].0 as f64, contour[j].1 as f64);
745 sum += x1 * y2 - x2 * y1;
746 }
747 sum.abs() / 2.0
748}
749
750pub fn arc_length(contour: &[(usize, usize)], closed: bool) -> f64 {
755 if contour.len() < 2 {
756 return 0.0;
757 }
758 let mut length = 0.0f64;
759 for i in 0..contour.len() - 1 {
760 let dx = contour[i + 1].0 as f64 - contour[i].0 as f64;
761 let dy = contour[i + 1].1 as f64 - contour[i].1 as f64;
762 length += (dx * dx + dy * dy).sqrt();
763 }
764 if closed {
765 let dx = contour[0].0 as f64 - contour[contour.len() - 1].0 as f64;
766 let dy = contour[0].1 as f64 - contour[contour.len() - 1].1 as f64;
767 length += (dx * dx + dy * dy).sqrt();
768 }
769 length
770}
771
772pub fn bounding_rect(contour: &[(usize, usize)]) -> (usize, usize, usize, usize) {
776 if contour.is_empty() {
777 return (0, 0, 0, 0);
778 }
779 let mut min_x = usize::MAX;
780 let mut min_y = usize::MAX;
781 let mut max_x = 0usize;
782 let mut max_y = 0usize;
783 for &(x, y) in contour {
784 min_x = min_x.min(x);
785 min_y = min_y.min(y);
786 max_x = max_x.max(x);
787 max_y = max_y.max(y);
788 }
789 (min_x, min_y, max_x - min_x, max_y - min_y)
790}