Skip to content

Commit cc87525

Browse files
committed
Fix segments ending on limbs
1 parent 52ed19c commit cc87525

File tree

3 files changed

+104
-35
lines changed

3 files changed

+104
-35
lines changed

crates/geometry/src/line.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,13 @@ impl<Frame> Line2<Frame> {
3737
self.0.y() - (self.0.x() * self.slope())
3838
}
3939

40+
pub fn project_onto_segment_in_x_axis(&self, point: Point2<Frame>) -> f32 {
41+
let rise = (point.x() - self.0.x()) * self.slope();
42+
rise + self.0.y()
43+
}
44+
4045
pub fn is_above(&self, point: Point2<Frame>) -> bool {
41-
self.signed_distance_to_point(point) >= 0.0
46+
point.y() >= self.project_onto_segment_in_x_axis(point)
4247
}
4348

4449
pub fn signed_distance_to_point(&self, point: Point2<Frame>) -> f32 {

crates/types/src/limb.rs

+73-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use geometry::line::Line;
44
use serde::{Deserialize, Serialize};
55

66
use coordinate_systems::Pixel;
7-
use linear_algebra::{point, Point2};
7+
use linear_algebra::Point2;
88
use path_serde::{PathDeserialize, PathIntrospect, PathSerialize};
99

1010
#[derive(
@@ -14,21 +14,35 @@ pub struct Limb {
1414
pub pixel_polygon: Vec<Point2<Pixel>>,
1515
}
1616

17-
pub fn is_above_limbs(pixel_position: Point2<Pixel>, projected_limbs: &[Limb]) -> bool {
18-
projected_limbs.iter().all(|limb| {
19-
match limb.pixel_polygon.as_slice().windows(2).find(|points| {
20-
points[0].x() <= pixel_position.x() && points[1].x() >= pixel_position.x()
21-
}) {
22-
Some(points) => {
23-
if points[0].x() == points[1].x() {
24-
return (pixel_position.y()) < f32::min(points[0].y(), points[1].y());
25-
}
26-
27-
// since Y is pointing downwards, "is above" is actually !Line::is_above()
28-
!Line(points[0], points[1]).is_above(point![pixel_position.x(), pixel_position.y()])
29-
}
30-
None => true,
31-
}
17+
pub fn project_onto_limbs(position: Point2<Pixel>, projected_limbs: &[Limb]) -> Option<f32> {
18+
projected_limbs
19+
.iter()
20+
.flat_map(|limb| {
21+
limb.pixel_polygon
22+
.as_slice()
23+
.windows(2)
24+
.filter_map(|points| {
25+
let is_outside_of_segment =
26+
position.x() < points[0].x() || position.x() > points[1].x();
27+
if is_outside_of_segment {
28+
return None;
29+
}
30+
31+
let is_vertical_segment = points[0].x() == points[1].x();
32+
if is_vertical_segment {
33+
return Some(f32::min(points[0].y(), points[1].y()));
34+
}
35+
36+
Some(Line(points[0], points[1]).project_onto_segment_in_x_axis(position))
37+
})
38+
.min_by(f32::total_cmp)
39+
})
40+
.min_by(f32::total_cmp)
41+
}
42+
43+
pub fn is_above_limbs(position: Point2<Pixel>, projected_limbs: &[Limb]) -> bool {
44+
project_onto_limbs(position, projected_limbs).map_or(true, |projected_position_y| {
45+
position.y() < projected_position_y
3246
})
3347
}
3448

@@ -38,3 +52,46 @@ pub fn is_above_limbs(pixel_position: Point2<Pixel>, projected_limbs: &[Limb]) -
3852
pub struct ProjectedLimbs {
3953
pub limbs: Vec<Limb>,
4054
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use linear_algebra::point;
59+
60+
use super::*;
61+
62+
#[test]
63+
fn left_limb_is_ignored() {
64+
let position = point![2.0, 0.0];
65+
let projected_limbs = vec![Limb {
66+
pixel_polygon: vec![point![0.0, 0.0], point![1.0, 1.0]],
67+
}];
68+
assert!(is_above_limbs(position, &projected_limbs));
69+
}
70+
71+
#[test]
72+
fn right_limb_is_ignored() {
73+
let position = point![2.0, 0.0];
74+
let projected_limbs = vec![Limb {
75+
pixel_polygon: vec![point![3.0, 0.0], point![4.0, 1.0]],
76+
}];
77+
assert!(is_above_limbs(position, &projected_limbs));
78+
}
79+
80+
#[test]
81+
fn too_high_limb_leads_to_point_being_below() {
82+
let position = point![2.0, 0.0];
83+
let projected_limbs = vec![Limb {
84+
pixel_polygon: vec![point![1.0, 10.0], point![3.0, 11.0]],
85+
}];
86+
assert!(is_above_limbs(position, &projected_limbs));
87+
}
88+
89+
#[test]
90+
fn low_limb_leads_to_point_being_above() {
91+
let position = point![2.0, 10.0];
92+
let projected_limbs = vec![Limb {
93+
pixel_polygon: vec![point![1.0, 0.0], point![3.0, 1.0]],
94+
}];
95+
assert!(!is_above_limbs(position, &projected_limbs));
96+
}
97+
}

crates/vision/src/image_segmenter.rs

+25-18
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use types::{
1616
color::{Hsv, Intensity, RgChromaticity, Rgb, YCbCr444},
1717
field_color::FieldColorParameters,
1818
image_segments::{Direction, EdgeType, ImageSegments, ScanGrid, ScanLine, Segment},
19-
limb::{is_above_limbs, Limb, ProjectedLimbs},
19+
limb::project_onto_limbs,
20+
limb::{Limb, ProjectedLimbs},
2021
parameters::{EdgeDetectionSourceParameters, MedianModeParameters},
2122
ycbcr422_image::YCbCr422Image,
2223
};
@@ -386,14 +387,18 @@ fn new_vertical_scan_line(
386387
if let Some(segment) =
387388
detect_edge(&mut state, y as u16, edge_detection_value, edge_threshold)
388389
{
389-
if segment_is_below_limbs(position as u16, &segment, projected_limbs) {
390-
fix_previous_edge_type(&mut segments);
391-
break;
392-
}
393390
segments.push(set_field_color_in_segment(
394391
set_color_in_segment(segment, position, Direction::Vertical, image),
395392
field_color,
396393
));
394+
let projected_y_on_limb =
395+
project_onto_limbs(point![position as f32, segment.end as f32], projected_limbs);
396+
let is_below_limbs = projected_y_on_limb
397+
.is_some_and(|projected_y_on_limb| segment.end as f32 > projected_y_on_limb);
398+
if is_below_limbs {
399+
fix_previous_segment(&mut segments, projected_y_on_limb.unwrap() as u16);
400+
break;
401+
}
397402
}
398403
}
399404

@@ -405,11 +410,23 @@ fn new_vertical_scan_line(
405410
color: Default::default(),
406411
field_color: Intensity::Low,
407412
};
408-
if !segment_is_below_limbs(position as u16, &last_segment, projected_limbs) {
413+
414+
if segments.last().map_or(true, |segment| {
415+
segment.end_edge_type != EdgeType::LimbBorder
416+
}) {
409417
segments.push(set_field_color_in_segment(
410418
set_color_in_segment(last_segment, position, Direction::Vertical, image),
411419
field_color,
412420
));
421+
let projected_y_on_limb = project_onto_limbs(
422+
point![position as f32, last_segment.end as f32],
423+
projected_limbs,
424+
);
425+
let is_below_limbs = projected_y_on_limb
426+
.is_some_and(|projected_y_on_limb| last_segment.end as f32 > projected_y_on_limb);
427+
if is_below_limbs {
428+
fix_previous_segment(&mut segments, projected_y_on_limb.unwrap() as u16);
429+
}
413430
}
414431

415432
ScanLine {
@@ -519,20 +536,10 @@ fn average_image_pixels(
519536
sum.average()
520537
}
521538

522-
fn segment_is_below_limbs(
523-
scan_line_position: u16,
524-
segment: &Segment,
525-
projected_limbs: &[Limb],
526-
) -> bool {
527-
!is_above_limbs(
528-
point![scan_line_position as f32, segment.end as f32],
529-
projected_limbs,
530-
)
531-
}
532-
533-
fn fix_previous_edge_type(segments: &mut [Segment]) {
539+
fn fix_previous_segment(segments: &mut [Segment], y_on_limb: u16) {
534540
if let Some(previous_segment) = segments.last_mut() {
535541
previous_segment.end_edge_type = EdgeType::LimbBorder;
542+
previous_segment.end = y_on_limb;
536543
}
537544
}
538545

0 commit comments

Comments
 (0)