use crate::image::ImageView;
use crate::segmentation::{ComponentStats, LabelResult, UnionFind};
use bumpalo::Bump;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RleSegment {
pub y: u16,
pub start_x: u16,
pub end_x: u16,
pub label: u32,
}
impl RleSegment {
#[must_use]
pub const fn new(y: u16, start_x: u16, end_x: u16) -> Self {
Self {
y,
start_x,
end_x,
label: 0,
}
}
}
pub mod simd_scanline;
#[allow(dead_code)]
#[must_use]
pub fn extract_rle_segments_scalar(img: &ImageView, threshold_map: &[u8]) -> Vec<RleSegment> {
let mut segments = Vec::new();
let height = img.height as u16;
for y in 0..height {
let y_usize = y as usize;
let src_row = img.get_row(y_usize);
let thresh_row = &threshold_map[y_usize * img.width..(y_usize + 1) * img.width];
process_row_scalar(src_row, thresh_row, y, &mut segments);
}
segments
}
pub fn process_row_scalar(
src_row: &[u8],
thresh_row: &[u8],
y: u16,
segments: &mut Vec<RleSegment>,
) {
let mut in_segment = false;
let mut start_x = 0;
let width = src_row.len();
for (x, (&s, &t)) in src_row.iter().zip(thresh_row.iter()).enumerate() {
let is_foreground = s < t;
if is_foreground && !in_segment {
in_segment = true;
start_x = x as u16;
} else if !is_foreground && in_segment {
in_segment = false;
segments.push(RleSegment::new(y, start_x, x as u16));
}
}
if in_segment {
segments.push(RleSegment::new(y, start_x, width as u16));
}
}
#[allow(clippy::too_many_lines)]
pub fn label_components_lsl<'a>(
arena: &'a Bump,
img: &ImageView,
threshold_map: &[u8],
use_8_connectivity: bool,
min_area: u32,
) -> LabelResult<'a> {
let mut runs = simd_scanline::extract_rle_segments(img, threshold_map);
for (id, run) in runs.iter_mut().enumerate() {
run.label = id as u32;
}
if runs.is_empty() {
return LabelResult {
labels: arena.alloc_slice_fill_copy(img.width * img.height, 0u32),
component_stats: Vec::new(),
};
}
let mut uf = UnionFind::new_in(arena, runs.len());
let mut curr_row_range = 0..0;
let mut i = 0;
while i < runs.len() {
let y = runs[i].y;
let start = i;
while i < runs.len() && runs[i].y == y {
i += 1;
}
let prev_row_range = curr_row_range;
curr_row_range = start..i;
if y > 0 && !prev_row_range.is_empty() && runs[prev_row_range.start].y == y - 1 {
let mut p_idx = prev_row_range.start;
for c_idx in curr_row_range.clone() {
let curr = &runs[c_idx];
if use_8_connectivity {
while p_idx < prev_row_range.end && runs[p_idx].end_x < curr.start_x {
p_idx += 1;
}
let mut temp_p = p_idx;
while temp_p < prev_row_range.end && runs[temp_p].start_x <= curr.end_x {
uf.union(curr.label, runs[temp_p].label);
temp_p += 1;
}
} else {
while p_idx < prev_row_range.end && runs[p_idx].end_x <= curr.start_x {
p_idx += 1;
}
let mut temp_p = p_idx;
while temp_p < prev_row_range.end && runs[temp_p].start_x < curr.end_x {
uf.union(curr.label, runs[temp_p].label);
temp_p += 1;
}
}
}
}
}
let mut root_to_temp_idx = vec![usize::MAX; runs.len()];
let mut temp_stats = Vec::new();
for run in &runs {
let root = uf.find(run.label) as usize;
if root_to_temp_idx[root] == usize::MAX {
root_to_temp_idx[root] = temp_stats.len();
temp_stats.push(ComponentStats {
first_pixel_x: run.start_x,
first_pixel_y: run.y,
..ComponentStats::default()
});
}
let s_idx = root_to_temp_idx[root];
let stats = &mut temp_stats[s_idx];
stats.min_x = stats.min_x.min(run.start_x);
stats.max_x = stats.max_x.max(run.end_x - 1);
stats.min_y = stats.min_y.min(run.y);
stats.max_y = stats.max_y.max(run.y);
stats.pixel_count += u32::from(run.end_x - run.start_x);
let a = u64::from(run.start_x);
let b = u64::from(run.end_x);
let yu = u64::from(run.y);
stats.m10 += b * (b - 1) / 2 - a * a.saturating_sub(1) / 2;
stats.m01 += yu * (b - a);
stats.m20 +=
(b - 1) * b * (2 * b - 1) / 6 - a.saturating_sub(1) * a * (2 * a).saturating_sub(1) / 6;
stats.m02 += yu * yu * (b - a);
stats.m11 += yu * (b * (b - 1) / 2 - a * a.saturating_sub(1) / 2);
}
let mut component_stats = Vec::with_capacity(temp_stats.len());
let mut root_to_final_label = vec![0u32; runs.len()];
let mut next_label = 1u32;
for (root, root_to_temp) in root_to_temp_idx.iter().enumerate() {
if *root_to_temp != usize::MAX {
let s = temp_stats[*root_to_temp];
if s.pixel_count >= min_area {
component_stats.push(s);
root_to_final_label[root] = next_label;
next_label += 1;
}
}
}
let labels = arena.alloc_slice_fill_copy(img.width * img.height, 0u32);
let width = img.width;
for run in &runs {
let root = uf.find(run.label) as usize;
let label = root_to_final_label[root];
if label > 0 {
let row_off = run.y as usize * width;
labels[row_off + run.start_x as usize..row_off + run.end_x as usize].fill(label);
}
}
LabelResult {
labels,
component_stats,
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_label_components_lsl_red_phase() {
let arena = Bump::new();
let width = 8;
let height = 4;
let mut pixels = vec![200u8; width * height];
pixels[1] = 50;
pixels[2] = 50;
pixels[5] = 50;
pixels[6] = 50;
pixels[8 + 1] = 50;
pixels[8 + 2] = 50;
pixels[8 + 5] = 50;
pixels[8 + 6] = 50;
let threshold_map = vec![128u8; width * height];
let img = ImageView::new(&pixels, width, height, width).expect("Valid test image");
let result = label_components_lsl(&arena, &img, &threshold_map, true, 1);
assert_eq!(result.component_stats.len(), 2);
let mut found_labels = std::collections::HashSet::new();
found_labels.insert(result.labels[1]);
found_labels.insert(result.labels[5]);
assert_eq!(found_labels.len(), 2);
assert!(!found_labels.contains(&0));
assert_eq!(result.component_stats[0].pixel_count, 4);
assert_eq!(result.component_stats[1].pixel_count, 4);
}
#[test]
fn test_extract_rle_segments_scalar() {
let width = 8;
let height = 2;
let pixels = vec![
200, 50, 50, 200, 50, 200, 200, 200, 50, 50, 50, 50, 200, 200, 200, 200, ];
let threshold_map = vec![128u8; width * height];
let img = ImageView::new(&pixels, width, height, width).expect("Valid test image");
let segments = extract_rle_segments_scalar(&img, &threshold_map);
assert_eq!(segments.len(), 3);
assert_eq!(segments[0], RleSegment::new(0, 1, 3));
assert_eq!(segments[1], RleSegment::new(0, 4, 5));
assert_eq!(segments[2], RleSegment::new(1, 0, 4));
}
}