1use crate::cv::ellipse::fit_ellipse_lstsq;
5use crate::cv::points::{convex_hull, point_to_segment_distance};
6
7#[inline]
8pub fn area(points: &[[f32; 2]]) -> f32 {
9 let mut area = 0.0;
10
11 let n = points.len();
12 for i in 0..n - 1 {
13 let p1 = points[i];
14 let p2 = points[i + 1];
15 area += p1[0] * p2[1] - p2[0] * p1[1];
16 }
17
18 if points[0] != points[n - 1] {
19 let p1 = points[n - 1];
20 let p2 = points[0];
21 area += p1[0] * p2[1] - p2[0] * p1[1];
22 }
23
24 area.abs() / 2.0
25}
26
27#[inline]
28pub fn area_bbox(points: &[[f32; 2]]) -> f32 {
29 let (mut xmin, mut ymin) = (points[0][0], points[0][1]);
30 let (mut xmax, mut ymax) = (points[0][0], points[0][1]);
31
32 for point in points.iter().skip(1) {
33 xmin = if point[0] < xmin { point[0] } else { xmin };
34 ymin = if point[1] < ymin { point[1] } else { ymin };
35 xmax = if point[0] > xmax { point[0] } else { xmax };
36 ymax = if point[1] > ymax { point[1] } else { ymax };
37 }
38
39 (xmax - xmin) * (ymax - ymin)
40}
41
42#[inline]
43pub fn area_convex(points: &[[f32; 2]]) -> f32 {
44 area(&convex_hull(points))
45}
46
47#[inline]
48pub fn perimeter(points: &[[f32; 2]]) -> f32 {
49 let n_points = points.len();
50
51 let mut perimeter = 0.0;
52
53 for i in 0..n_points - 1 {
54 let dx = points[i][0] - points[i + 1][0];
55 let dy = points[i][1] - points[i + 1][1];
56 perimeter += (dx * dx + dy * dy).sqrt();
57 }
58
59 if points[0] != points[n_points - 1] {
60 let dx = points[points.len() - 1][0] - points[0][0];
61 let dy = points[points.len() - 1][1] - points[0][1];
62 perimeter += (dx * dx + dy * dy).sqrt();
63 }
64
65 perimeter
66}
67
68#[inline]
69pub fn centroid(points: &[[f32; 2]]) -> [f32; 2] {
70 let mut sum_x = 0.0;
71 let mut sum_y = 0.0;
72 let mut area = 0.0;
73
74 let n = points.len();
75 let is_closed = points[0] == points[n - 1];
76 let n_end = if is_closed { n - 1 } else { n };
77
78 for i in 0..n_end {
79 let j = (i + 1) % n_end;
80 let p1 = points[i];
81 let p2 = points[j];
82 let cross = p1[0] * p2[1] - p2[0] * p1[1];
83 sum_x += (p1[0] + p2[0]) * cross;
84 sum_y += (p1[1] + p2[1]) * cross;
85 area += cross;
86 }
87
88 area /= 2.0;
89 [sum_x / (6.0 * area.abs()), sum_y / (6.0 * area.abs())]
90}
91
92#[inline]
93pub fn center(points: &[[f32; 2]]) -> [f32; 2] {
94 let mut sum_x = 0.0;
95 let mut sum_y = 0.0;
96
97 let n = points.len();
98 let is_closed = points[0] == points[n - 1];
99 let n_end = if is_closed { n - 1 } else { n };
100
101 for point in points.iter().take(n_end) {
102 sum_x += point[0];
103 sum_y += point[1];
104 }
105
106 [sum_x / n_end as f32, sum_y / n_end as f32]
107}
108
109#[inline]
110pub fn elongation(points: &[[f32; 2]]) -> f32 {
111 let (mut xmin, mut ymin) = (points[0][0], points[0][1]);
112 let (mut xmax, mut ymax) = (points[0][0], points[0][1]);
113
114 for point in points.iter().skip(1) {
115 xmin = if point[0] < xmin { point[0] } else { xmin };
116 ymin = if point[1] < ymin { point[1] } else { ymin };
117 xmax = if point[0] > xmax { point[0] } else { xmax };
118 ymax = if point[1] > ymax { point[1] } else { ymax };
119 }
120
121 let elongation = (xmax - xmin) / (ymax - ymin);
122
123 if elongation > 1.0 {
124 1.0 / elongation
125 } else {
126 elongation
127 }
128}
129
130#[inline]
131pub fn thread_length(points: &[[f32; 2]]) -> f32 {
132 let perimeter = perimeter(points);
133 let area = area(points);
134
135 let left = perimeter.powi(2);
136 let right = 16.0 * area;
137
138 let coefficient = if left <= right {
139 0.0
140 } else {
141 (left - right).sqrt()
142 };
143
144 (perimeter + coefficient) / 4.0
145}
146
147#[inline]
148pub fn thread_width(points: &[[f32; 2]]) -> f32 {
149 let perimeter = perimeter(points);
150 let area = area(points);
151
152 let left = perimeter.powi(2);
153 let right = 16.0 * area;
154
155 let coefficient = if left <= right {
156 0.0
157 } else {
158 (left - right).sqrt()
159 };
160
161 let thread_length = (perimeter + coefficient) / 4.0;
162 area / thread_length
163}
164
165#[inline]
166pub fn solidity(points: &[[f32; 2]]) -> f32 {
167 let area = area(points);
168 let area_convex = area_convex(points);
169
170 area / area_convex
171}
172
173#[inline]
174pub fn extent(points: &[[f32; 2]]) -> f32 {
175 let area = area(points);
176 let area_bbox = area_bbox(points);
177
178 area / area_bbox
179}
180
181#[inline]
182pub fn form_factor(points: &[[f32; 2]]) -> f32 {
183 let perimeter = perimeter(points);
184 let area = area(points);
185
186 (4.0 * std::f32::consts::PI * area) / (perimeter * perimeter)
187}
188
189#[inline]
190pub fn equivalent_diameter(points: &[[f32; 2]]) -> f32 {
191 (area(points) / std::f32::consts::PI).sqrt() * 2.0
192}
193
194#[inline]
195pub fn eccentricity(points: &[[f32; 2]]) -> f32 {
196 let ellipse = fit_ellipse_lstsq(points);
197 ellipse[2]
198}
199
200#[inline]
201pub fn major_axis_length(points: &[[f32; 2]]) -> f32 {
202 let ellipse = fit_ellipse_lstsq(points);
203 ellipse[0]
204}
205
206#[inline]
207pub fn minor_axis_length(points: &[[f32; 2]]) -> f32 {
208 let ellipse = fit_ellipse_lstsq(points);
209 ellipse[1]
210}
211
212#[inline]
213pub fn min_radius(points: &[[f32; 2]]) -> f32 {
214 let [cx, cy] = centroid(points);
215 let mut min_radius = f32::MAX;
216
217 for i in 0..points.len() {
218 let p1 = points[i];
219 let p2 = points[(i + 1) % points.len()];
220
221 let distance = point_to_segment_distance(cx, cy, p1, p2);
222 if distance < min_radius {
223 min_radius = distance;
224 }
225 }
226
227 min_radius
228}
229
230#[inline]
231pub fn max_radius(points: &[[f32; 2]]) -> f32 {
232 let [x_centroid, y_centroid] = centroid(points);
233 let mut maximum_radius = 0.0;
234
235 for point in points.iter() {
236 let (x, y) = (point[0], point[1]);
237 let distance = (x_centroid - x) * (x_centroid - x) + (y_centroid - y) * (y_centroid - y);
238 if distance > maximum_radius {
239 maximum_radius = distance;
240 }
241 }
242
243 maximum_radius.sqrt()
244}
245
246#[inline]
247pub fn mean_radius(points: &[[f32; 2]]) -> f32 {
248 let [x_centroid, y_centroid] = centroid(points);
249 let include_last = (points.last().unwrap() == &points[0]) as usize;
250
251 let mut mean_radius = 0.0;
252 for point in points.iter().take(points.len() - include_last) {
253 let (x, y) = (point[0], point[1]);
254 let distance = (x_centroid - x) * (x_centroid - x) + (y_centroid - y) * (y_centroid - y);
255 mean_radius += distance.sqrt();
256 }
257
258 mean_radius / (points.len() - include_last) as f32
259}
260
261#[inline]
262pub fn min_feret(points: &[[f32; 2]]) -> f32 {
263 let norm = |x: [f32; 2]| -> f32 { (x[0] * x[0] + x[1] * x[1]).sqrt() };
264 let cross_product = |x: [f32; 2], y: [f32; 2]| -> f32 { x[0] * y[1] - x[1] * y[0] };
265 let n_points = points.len();
266
267 let mut feret_diameter_minimum = f32::MAX;
268 for i in 0..n_points {
269 let p1 = points[i];
270 let p2 = points[(i + 1) % n_points];
271 let diff_a = [p2[0] - p1[0], p2[1] - p1[1]];
272
273 let mut distance = 0.0;
274 for point in points.iter().take(n_points) {
275 let diff_b = [point[0] - p1[0], point[1] - p1[1]];
276 let d = (cross_product(diff_a, diff_b) / norm(diff_a)).abs();
277
278 if d > distance {
279 distance = d;
280 }
281 }
282
283 if distance < feret_diameter_minimum && distance > f32::EPSILON {
284 feret_diameter_minimum = distance;
285 }
286 }
287
288 feret_diameter_minimum
289}
290
291#[inline]
292pub fn max_feret(points: &[[f32; 2]]) -> f32 {
293 let mut max_diameter = 0f32;
294 let n_points = points.len();
295
296 for i in 0..n_points {
297 for j in (i + 1)..n_points {
298 let dx = points[j][0] - points[i][0];
299 let dy = points[j][1] - points[i][1];
300
301 if dx == 0.0 && dy == 0.0 {
302 continue;
303 }
304
305 let mut min_proj = f32::INFINITY;
306 let mut max_proj = f32::NEG_INFINITY;
307
308 for k in 0..n_points {
309 let proj = (points[k][0] - points[i][0]) * dx + (points[k][1] - points[i][1]) * dy;
310
311 min_proj = min_proj.min(proj);
312 max_proj = max_proj.max(proj);
313 }
314
315 let diameter = ((max_proj - min_proj) / ((dx * dx + dy * dy).sqrt())).abs();
316 max_diameter = max_diameter.max(diameter);
317 }
318 }
319
320 max_diameter
321}
322
323#[inline]
324pub fn descriptors(points: &[[f32; 2]]) -> [f32; 23] {
325 let n = points.len();
326 let is_closed = points[0] == points[n - 1];
327 let n_end = if is_closed { n - 1 } else { n };
328
329 let mut area = 0f32;
330 let mut perimeter = 0f32;
331
332 let mut sum_x = 0f32;
333 let mut sum_y = 0f32;
334 let mut mean_x = 0f32;
335 let mut mean_y = 0f32;
336
337 let (mut xmin, mut ymin) = (points[0][0], points[0][1]);
338 let (mut xmax, mut ymax) = (points[0][0], points[0][1]);
339
340 for i in 0..n {
341 let p1 = points[i];
342 let p2 = points[(i + 1) % n];
343
344 if i < n_end {
346 let cross = p1[0] * p2[1] - p2[0] * p1[1];
347 area += cross;
348 sum_x += (p1[0] + p2[0]) * cross;
349 sum_y += (p1[1] + p2[1]) * cross;
350 }
351
352 if !is_closed || (i + 1) % n != 0 {
354 let dx = p1[0] - p2[0];
355 let dy = p1[1] - p2[1];
356 perimeter += (dx * dx + dy * dy).sqrt();
357 }
358
359 xmin = xmin.min(p1[0]);
361 ymin = ymin.min(p1[1]);
362 xmax = xmax.max(p1[0]);
363 ymax = ymax.max(p1[1]);
364
365 if i < n_end {
367 mean_x += p1[0];
368 mean_y += p1[1];
369 }
370 }
371
372 let area = area.abs() / 2.0;
373 let area_bbox = (xmax - xmin) * (ymax - ymin);
374
375 let centroid_x = sum_x / (6.0 * area.abs());
376 let centroid_y = sum_y / (6.0 * area.abs());
377 let center_x = mean_x / n_end as f32;
378 let center_y = mean_y / n_end as f32;
379
380 let elongation = {
381 let e = (xmax - xmin) / (ymax - ymin);
382 if e > 1.0 { 1.0 / e } else { e }
383 };
384
385 let mut minimum_radius = f32::MAX;
386 let mut maximum_radius = 0f32;
387 let mut mean_radius = 0f32;
388
389 let mut min_feret = f32::MAX;
390 let mut max_feret = 0f32;
391
392 for i in 0..n {
393 let p1 = points[i];
394 let p2 = points[(i + 1) % n];
395
396 minimum_radius =
398 minimum_radius.min(point_to_segment_distance(centroid_x, centroid_y, p1, p2));
399
400 if !is_closed || i < n - 1 {
402 let dx = centroid_x - p1[0];
403 let dy = centroid_y - p1[1];
404 let distance_sq = dx * dx + dy * dy;
405 maximum_radius = maximum_radius.max(distance_sq);
406 mean_radius += distance_sq.sqrt();
407 }
408
409 let diff_a = [p2[0] - p1[0], p2[1] - p1[1]];
411 let norm_a = (diff_a[0] * diff_a[0] + diff_a[1] * diff_a[1]).sqrt();
412
413 let mut max_distance = 0f32;
414 for point in points {
415 let diff_b = [point[0] - p1[0], point[1] - p1[1]];
416 let d = (diff_a[0] * diff_b[1] - diff_a[1] * diff_b[0]).abs() / norm_a;
417 max_distance = max_distance.max(d);
418 }
419
420 if max_distance < min_feret && max_distance > f32::EPSILON {
421 min_feret = max_distance;
422 }
423 }
424
425 maximum_radius = maximum_radius.sqrt();
426 mean_radius /= n_end as f32;
427
428 for i in 0..n {
430 for j in (i + 1)..n {
431 let dx = points[j][0] - points[i][0];
432 let dy = points[j][1] - points[i][1];
433
434 if dx == 0.0 && dy == 0.0 {
435 continue;
436 }
437
438 let norm = (dx * dx + dy * dy).sqrt();
439
440 let mut min_proj = f32::INFINITY;
441 let mut max_proj = f32::NEG_INFINITY;
442
443 for point in points {
444 let proj =
445 ((point[0] - points[i][0]) * dx + ((point[1] - points[i][1]) * dy)) / norm;
446 min_proj = min_proj.min(proj);
447 max_proj = max_proj.max(proj);
448 }
449
450 max_feret = max_feret.max((max_proj - min_proj).abs());
451 }
452 }
453
454 let area_convex = {
456 let convex_hull_points = convex_hull(points);
457
458 let mut area = 0.0;
459 let n_hull = convex_hull_points.len();
460 for i in 0..n_hull - 1 {
461 let p1 = convex_hull_points[i];
462 let p2 = convex_hull_points[i + 1];
463 area += p1[0] * p2[1] - p2[0] * p1[1];
464 }
465
466 if convex_hull_points[0] != convex_hull_points[n_hull - 1] {
467 let p1 = convex_hull_points[n_hull - 1];
468 let p2 = convex_hull_points[0];
469 area += p1[0] * p2[1] - p2[0] * p1[1];
470 }
471
472 area.abs() / 2.0
473 };
474
475 let ellipse = fit_ellipse_lstsq(points);
477 let major_axis = ellipse[0];
478 let minor_axis = ellipse[1];
479 let eccentricity = ellipse[2];
480
481 let thread_left = perimeter.powi(2);
483 let thread_right = 16.0 * area;
484 let thread_coefficient = if thread_left <= thread_right {
485 0.0
486 } else {
487 (thread_left - thread_right).sqrt()
488 };
489 let thread_length = (perimeter + thread_coefficient) / 4.0;
490 let thread_width = area / thread_length;
491
492 let solidity = area / area_convex;
494 let extent = area / area_bbox;
495 let form_factor = (4.0 * std::f32::consts::PI * area) / (perimeter * perimeter);
496 let equivalent_diameter = (area / std::f32::consts::PI).sqrt() * 2.0;
497
498 [
499 centroid_x,
500 centroid_y,
501 center_x,
502 center_y,
503 area,
504 area_bbox,
505 area_convex,
506 perimeter,
507 elongation,
508 thread_length,
509 thread_width,
510 solidity,
511 extent,
512 form_factor,
513 equivalent_diameter,
514 eccentricity,
515 major_axis,
516 minor_axis,
517 minimum_radius,
518 maximum_radius,
519 mean_radius,
520 min_feret,
521 max_feret,
522 ]
523}
524
525#[cfg(test)]
526mod test {
527 use super::*;
528
529 const EPSILON: f32 = 1e-5;
530
531 fn unit_circle(close: bool) -> Vec<[f32; 2]> {
532 let mut points = Vec::with_capacity(360);
533 for i in 0..360 {
534 let t = 2.0 * std::f32::consts::PI * i as f32 / 360f32;
535 points.push([t.cos(), t.sin()]);
536 }
537
538 if close {
539 points.push(points[0]);
540 }
541
542 points
543 }
544
545 fn unit_circle_shifted(close: bool) -> Vec<[f32; 2]> {
546 let mut points = Vec::with_capacity(360);
547 for i in 0..360 {
548 let t = 2.0 * std::f32::consts::PI * i as f32 / 360f32;
549 points.push([t.cos() + 1.0, t.sin() + 2.0]);
550 }
551
552 if close {
553 points.push(points[0]);
554 }
555
556 points
557 }
558
559 fn unit_square(close: bool) -> Vec<[f32; 2]> {
560 let mut points = vec![[-1.0, -1.0], [1.0, -1.0], [1.0, 1.0], [-1.0, 1.0]];
561 if close {
562 points.push(points[0]);
563 }
564 points
565 }
566
567 fn test_equivalence(f: fn(&[[f32; 2]]) -> f32) {
568 let open_circle = unit_circle(false);
569 let open_square = unit_square(false);
570 let closed_circle = unit_circle(true);
571 let closed_square = unit_square(true);
572
573 assert_eq!(f(&open_circle), f(&closed_circle));
574 assert_eq!(f(&open_square), f(&closed_square));
575 }
576
577 #[test]
578 fn test_centroid() {
579 for close in [true, false] {
580 let circle = unit_circle(close);
581 let xy = centroid(&circle);
582 assert!((xy[0] - 0.0).abs() < EPSILON);
583 assert!((xy[1] - 0.0).abs() < EPSILON);
584
585 let square = unit_square(close);
586 let xy = centroid(&square);
587 assert!((xy[0] - 0.0).abs() < EPSILON);
588 assert!((xy[1] - 0.0).abs() < EPSILON);
589 }
590 }
591
592 #[test]
593 fn test_centroid_shifted() {
594 for close in [true, false] {
595 let circle = unit_circle_shifted(close);
596
597 let xy = centroid(&circle);
598 assert!((xy[0] - 1.0).abs() < EPSILON);
599 assert!((xy[1] - 2.0).abs() < EPSILON);
600 }
601 }
602
603 #[test]
604 fn test_center() {
605 for close in [true, false] {
606 let circle = unit_circle(close);
607 let xy = center(&circle);
608 assert!((xy[0] - 0.0).abs() < EPSILON);
609 assert!((xy[1] - 0.0).abs() < EPSILON);
610
611 let square = unit_square(close);
612 let xy = center(&square);
613 assert!((xy[0] - 0.0).abs() < EPSILON);
614 assert!((xy[1] - 0.0).abs() < EPSILON);
615 }
616 }
617
618 #[test]
619 fn test_area() {
620 for close in [true, false] {
621 let circle = unit_circle(close);
622 let area_polygon = area(&circle);
623 let circle_area = 0.5 * 360f32 * (2.0 * std::f32::consts::PI / 360f32).sin();
624 assert!((area_polygon - circle_area).abs() < EPSILON);
625
626 let square = unit_square(close);
627 let area_polygon = area(&square);
628 assert!((area_polygon - 4.0).abs() < EPSILON);
629 }
630
631 test_equivalence(area);
632 }
633
634 #[test]
635 fn test_area_bbox() {
636 for close in [true, false] {
637 let circle = unit_circle(close);
638 let area = area_bbox(&circle);
639 assert!((area - 4.0).abs() < EPSILON);
640
641 let square = unit_square(close);
642 let area = area_bbox(&square);
643 assert!((area - 4.0).abs() < EPSILON);
644 }
645
646 test_equivalence(area_bbox);
647 }
648
649 #[test]
650 fn test_area_convex() {
651 for close in [true, false] {
652 let circle = unit_circle(close);
653 let area = area_convex(&circle);
654 let circle_area = 0.5 * 360f32 * (2.0 * std::f32::consts::PI / 360f32).sin();
655 assert!((area - circle_area).abs() < EPSILON);
656
657 let square = unit_square(close);
658 let area = area_convex(&square);
659 assert!((area - 4.0).abs() < EPSILON);
660 }
661
662 test_equivalence(area_convex);
663 }
664
665 #[test]
666 fn test_perimeter() {
667 for close in [true, false] {
668 let circle = unit_circle(close);
669 let perimeter_polygon = perimeter(&circle);
670 let circle_perimeter = 360f32 * 2.0 * (std::f32::consts::PI / 360f32).sin();
671 assert!((perimeter_polygon - circle_perimeter).abs() < 1e-4);
672
673 let square = unit_square(close);
674 let perimeter_polygon = perimeter(&square);
675 assert!((perimeter_polygon - 8.0).abs() < EPSILON);
676 }
677
678 test_equivalence(perimeter);
679 }
680
681 #[test]
682 fn test_elongation() {
683 for close in [true, false] {
684 let circle = unit_circle(close);
685 let elongation_polygon = elongation(&circle);
686 assert!((elongation_polygon - 1.0).abs() < EPSILON);
687
688 let square = unit_square(close);
689 let elongation_polygon = elongation(&square);
690 assert!((elongation_polygon - 1.0).abs() < EPSILON);
691 }
692
693 test_equivalence(elongation);
694 }
695
696 #[test]
697 fn test_solidity() {
698 for close in [true, false] {
699 let circle = unit_circle(close);
700 let solidity_polygon = solidity(&circle);
701 assert!((solidity_polygon - 1.0).abs() < EPSILON);
702
703 let square = unit_square(close);
704 let solidity_polygon = solidity(&square);
705 assert!((solidity_polygon - 1.0).abs() < EPSILON);
706 }
707
708 test_equivalence(solidity);
709 }
710
711 #[test]
712 fn test_extent() {
713 for close in [true, false] {
714 let circle = unit_circle(close);
715 let extent_polygon = extent(&circle);
716 let circle_area = 0.5 * 360f32 * (2.0 * std::f32::consts::PI / 360f32).sin();
717 assert!((extent_polygon - circle_area / 4.0).abs() < EPSILON);
718
719 let square = unit_square(close);
720 let extent_polygon = extent(&square);
721 assert!((extent_polygon - 1.0).abs() < EPSILON);
722 }
723
724 test_equivalence(extent);
725 }
726
727 #[test]
728 fn test_form_factor() {
729 for close in [true, false] {
730 let circle = unit_circle(close);
731 let form_factor_polygon = form_factor(&circle);
732 let circle_area = 0.5 * 360f32 * (2.0 * std::f32::consts::PI / 360f32).sin();
733 let circle_perimeter = 360f32 * 2.0 * (std::f32::consts::PI / 360f32).sin();
734 assert!(
735 (form_factor_polygon
736 - (4.0 * std::f32::consts::PI * circle_area)
737 / (circle_perimeter * circle_perimeter))
738 .abs()
739 < EPSILON
740 );
741
742 let square = unit_square(close);
743 let form_factor_polygon = form_factor(&square);
744 assert!((form_factor_polygon - std::f32::consts::PI / 4.0).abs() < EPSILON);
745 }
746
747 test_equivalence(form_factor);
748 }
749
750 #[test]
751 fn test_thread_length() {
752 for close in [true, false] {
753 let circle = unit_circle(close);
754 let thread_length_polygon = thread_length(&circle);
755 let circle_perimeter = 360f32 * 2.0 * (std::f32::consts::PI / 360f32).sin();
756 assert!((thread_length_polygon - circle_perimeter / 4.0).abs() < EPSILON);
757
758 let square = unit_square(close);
759 let thread_length_polygon = thread_length(&square);
760 assert!((thread_length_polygon - 2.0).abs() < EPSILON);
761 }
762
763 test_equivalence(thread_length);
764 }
765
766 #[test]
767 fn test_thread_width() {
768 for close in [true, false] {
769 let circle = unit_circle(close);
770 let thread_width_polygon = thread_width(&circle);
771 let circle_area = 0.5 * 360f32 * (2.0 * std::f32::consts::PI / 360f32).sin();
772 let circle_perimeter = 360f32 * 2.0 * (std::f32::consts::PI / 360f32).sin();
773 assert!((thread_width_polygon - (4.0 * circle_area) / circle_perimeter).abs() < 1e-4);
774
775 let square = unit_square(close);
776 let thread_width_polygon = thread_width(&square);
777 assert!((thread_width_polygon - 2.0).abs() < EPSILON);
778 }
779
780 test_equivalence(thread_width);
781 }
782
783 #[test]
784 fn feret_diameter_maximum() {
785 for close in [true, false] {
786 let circle = unit_circle(close);
787 let feret_diameter_maximum = max_feret(&circle);
788 assert!((feret_diameter_maximum - 2.0).abs() < 1e-4);
791
792 let square = unit_square(close);
793 let feret_diameter_maximum = max_feret(&square);
794 assert!((feret_diameter_maximum - 8_f32.sqrt()).abs() < EPSILON);
795 }
796
797 test_equivalence(max_feret);
798 }
799
800 #[test]
801 fn test_feret_diameter_minimum() {
802 for close in [true, false] {
803 let circle = unit_circle(close);
804 let feret_diameter_minimum = min_feret(&circle);
805 assert!((feret_diameter_minimum - 2.0).abs() < 1e-4);
808
809 let square = unit_square(close);
810 let feret_diameter_minimum = min_feret(&square);
811 assert!((feret_diameter_minimum - 2.0).abs() < EPSILON);
812 }
813
814 test_equivalence(min_feret);
815 }
816
817 #[test]
818 fn test_min_radius() {
819 for close in [true, false] {
820 let circle = unit_circle(close);
821 let min_radius_polygon = min_radius(&circle);
822 assert!((min_radius_polygon - 1.0).abs() < 1e-4);
823
824 let square = unit_square(close);
825 let min_radius_polygon = min_radius(&square);
826 assert!((min_radius_polygon - 1.0).abs() < EPSILON);
827 }
828
829 test_equivalence(min_radius);
830 }
831
832 #[test]
833 fn test_min_radius_shifted() {
834 for close in [true, false] {
835 let circle = unit_circle_shifted(close);
836 let min_radius_polygon = min_radius(&circle);
837 assert!((min_radius_polygon - 1.0).abs() < 1e-4);
838 }
839 }
840
841 #[test]
842 fn test_max_radius() {
843 for close in [true, false] {
844 let circle = unit_circle(close);
845 let max_radius_polygon = max_radius(&circle);
846 assert!((max_radius_polygon - 1.0).abs() < EPSILON);
847
848 let square = unit_square(close);
849 let max_radius_polygon = max_radius(&square);
850 let h = (2.0_f32).sqrt();
851 assert!((max_radius_polygon - h).abs() < EPSILON);
852 }
853
854 test_equivalence(max_radius);
855 }
856
857 #[test]
858 fn test_max_radius_shifted() {
859 for close in [true, false] {
860 let circle = unit_circle_shifted(close);
861 let max_radius_polygon = min_radius(&circle);
862 assert!((max_radius_polygon - 1.0).abs() < 1e-4);
863 }
864 }
865
866 #[test]
867 fn test_mean_radius() {
868 for close in [true, false] {
869 let circle = unit_circle(close);
870 let mean_radius_polygon = mean_radius(&circle);
871 assert!((mean_radius_polygon - 1.0).abs() < EPSILON);
872
873 let square = unit_square(close);
874 let mean_radius_polygon = mean_radius(&square);
875 let h = (2.0_f32).sqrt();
876 assert!((mean_radius_polygon - h).abs() < EPSILON);
877 }
878
879 test_equivalence(mean_radius);
880 }
881
882 #[test]
883 fn test_eccentricity() {
884 for close in [true, false] {
885 let circle = unit_circle(close);
886 let eccentricity_polygon = eccentricity(&circle);
887 assert!((eccentricity_polygon - 0.0).abs() < 1e-2);
891 }
892
893 test_equivalence(eccentricity);
894 }
895
896 #[test]
897 fn test_major_axis_length() {
898 for close in [true, false] {
899 let circle = unit_circle(close);
900 let major_axis_length_polygon = major_axis_length(&circle);
901 assert!((major_axis_length_polygon - 2.0).abs() < EPSILON);
902 }
903
904 test_equivalence(major_axis_length);
905 }
906
907 #[test]
908 fn test_minor_axis_length() {
909 for close in [true, false] {
910 let circle = unit_circle(close);
911 let minor_axis_length_polygon = minor_axis_length(&circle);
912 assert!((minor_axis_length_polygon - 2.0).abs() < EPSILON);
913 }
914
915 test_equivalence(minor_axis_length);
916 }
917
918 #[test]
919 fn test_descriptors() {
920 for close in [true, false] {
921 for shifted in [true, false] {
922 let points = if !shifted {
923 unit_circle(close)
924 } else {
925 let mut points = vec![];
926 for point in unit_circle(close).iter().copied() {
927 points.push([point[0] + 10f32, point[1] + 12f32])
928 }
929 points
930 };
931
932 let descriptors = descriptors(&points);
933 let centroid = centroid(&points);
934 let center = center(&points);
935 let area_polygon = area(&points);
936 let area_bbox = area_bbox(&points);
937 let area_convex = area_convex(&unit_circle(close));
938 let perimeter = perimeter(&points);
939 let elongation = elongation(&points);
940 let solidity = solidity(&points);
941 let extent = extent(&points);
942 let form_factor = form_factor(&points);
943 let equivalent_diameter = equivalent_diameter(&points);
944 let major_axis = major_axis_length(&points);
945 let minor_axis = minor_axis_length(&points);
946 let thread_length = thread_length(&points);
947 let thread_width = thread_width(&points);
948 let minimum_radius = min_radius(&points);
949 let maximum_radius = max_radius(&points);
950 let mean_radius = mean_radius(&points);
951 let eccentricity = eccentricity(&points);
952 let min_feret = min_feret(&points);
953 let max_feret = max_feret(&points);
954
955 assert_eq!(descriptors[0], centroid[0]);
956 assert_eq!(descriptors[1], centroid[1]);
957 assert_eq!(descriptors[2], center[0]);
958 assert_eq!(descriptors[3], center[1]);
959 assert_eq!(descriptors[4], area_polygon);
960 assert_eq!(descriptors[5], area_bbox);
961 if shifted {
962 assert!((descriptors[6] - area_convex).abs() < 1e-4);
967 } else {
968 assert_eq!(descriptors[6], area_convex);
969 }
970 assert_eq!(descriptors[7], perimeter);
971 assert_eq!(descriptors[8], elongation);
972 assert_eq!(descriptors[9], thread_length);
973 assert_eq!(descriptors[10], thread_width);
974 assert_eq!(descriptors[11], solidity);
975 assert_eq!(descriptors[12], extent);
976 assert_eq!(descriptors[13], form_factor);
977 assert_eq!(descriptors[14], equivalent_diameter);
978 assert_eq!(descriptors[15], eccentricity);
979 assert_eq!(descriptors[16], major_axis);
980 assert_eq!(descriptors[17], minor_axis);
981 assert_eq!(descriptors[18], minimum_radius);
982 assert_eq!(descriptors[19], maximum_radius);
983 assert_eq!(descriptors[20], mean_radius);
984 assert_eq!(descriptors[21], min_feret);
985 assert_eq!(descriptors[22], max_feret);
986 }
987 }
988 }
989}