use alloc::vec;
use alloc::vec::Vec;
use crate::dynmatrix::DynMatrix;
use crate::traits::{MatrixRef, Scalar};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Connectivity {
Four,
Eight,
}
#[derive(Debug, Clone)]
pub struct Component {
pub area: u32,
pub bbox_min: (u32, u32),
pub bbox_max: (u32, u32),
pub centroid: (f64, f64),
pub mu20: f64,
pub mu02: f64,
pub mu11: f64,
}
struct UnionFind {
parent: Vec<u32>,
rank: Vec<u8>,
}
impl UnionFind {
fn new() -> Self {
Self {
parent: vec![0],
rank: vec![0],
}
}
fn make_set(&mut self) -> u32 {
let id = self.parent.len() as u32;
self.parent.push(id);
self.rank.push(0);
id
}
fn find(&mut self, mut x: u32) -> u32 {
while self.parent[x as usize] != x {
let p = self.parent[x as usize];
let gp = self.parent[p as usize];
self.parent[x as usize] = gp;
x = gp;
}
x
}
fn union(&mut self, a: u32, b: u32) -> u32 {
let ra = self.find(a);
let rb = self.find(b);
if ra == rb {
return ra;
}
let (rhi, rlo) = match self.rank[ra as usize].cmp(&self.rank[rb as usize]) {
core::cmp::Ordering::Less => (rb, ra),
core::cmp::Ordering::Greater => (ra, rb),
core::cmp::Ordering::Equal => {
self.rank[ra as usize] += 1;
(ra, rb)
}
};
self.parent[rlo as usize] = rhi;
rhi
}
}
fn first_pass<T, I>(img: &I, connectivity: Connectivity, background: T) -> (Vec<u32>, UnionFind)
where
T: Scalar + PartialEq,
I: MatrixRef<T> + ?Sized,
{
let h = img.nrows();
let w = img.ncols();
let mut labels: Vec<u32> = vec![0; h * w];
let mut uf = UnionFind::new();
let eight = matches!(connectivity, Connectivity::Eight);
for r in 0..h {
for c in 0..w {
if *img.get(r, c) == background {
continue;
}
let mut n: [u32; 4] = [0; 4];
let mut k: usize = 0;
if c > 0 {
let l = labels[r * w + (c - 1)];
if l != 0 {
n[k] = l;
k += 1;
}
}
if r > 0 {
let l = labels[(r - 1) * w + c];
if l != 0 {
n[k] = l;
k += 1;
}
if eight {
if c > 0 {
let l = labels[(r - 1) * w + (c - 1)];
if l != 0 {
n[k] = l;
k += 1;
}
}
if c + 1 < w {
let l = labels[(r - 1) * w + (c + 1)];
if l != 0 {
n[k] = l;
k += 1;
}
}
}
}
let assigned = if k == 0 {
uf.make_set()
} else {
let mut min_label = n[0];
for i in 1..k {
min_label = uf.union(min_label, n[i]);
}
for i in 0..k {
uf.union(min_label, n[i]);
}
min_label
};
labels[r * w + c] = assigned;
}
}
(labels, uf)
}
fn resolve_roots(uf: &mut UnionFind) -> (Vec<u32>, usize) {
let n = uf.parent.len();
let mut canonical: Vec<u32> = vec![0; n];
let mut num = 0u32;
for pid in 1..n as u32 {
let root = uf.find(pid);
if canonical[root as usize] == 0 {
num += 1;
canonical[root as usize] = num;
}
canonical[pid as usize] = canonical[root as usize];
}
(canonical, num as usize)
}
#[allow(clippy::type_complexity)]
fn second_pass(
labels: &mut [u32],
canonical: &[u32],
h: usize,
w: usize,
num_components: usize,
) -> Vec<Component> {
let n = num_components + 1;
let mut areas: Vec<u32> = vec![0; n];
let mut sum_r: Vec<f64> = vec![0.0; n];
let mut sum_c: Vec<f64> = vec![0.0; n];
let mut sum_rr: Vec<f64> = vec![0.0; n];
let mut sum_cc: Vec<f64> = vec![0.0; n];
let mut sum_rc: Vec<f64> = vec![0.0; n];
let mut r_min: Vec<u32> = vec![u32::MAX; n];
let mut r_max: Vec<u32> = vec![0; n];
let mut c_min: Vec<u32> = vec![u32::MAX; n];
let mut c_max: Vec<u32> = vec![0; n];
for r in 0..h {
for c in 0..w {
let prov = labels[r * w + c];
if prov == 0 {
continue;
}
let id = canonical[prov as usize];
labels[r * w + c] = id;
let i = id as usize;
let rf = r as f64;
let cf = c as f64;
areas[i] += 1;
sum_r[i] += rf;
sum_c[i] += cf;
sum_rr[i] += rf * rf;
sum_cc[i] += cf * cf;
sum_rc[i] += rf * cf;
let ru = r as u32;
let cu = c as u32;
if ru < r_min[i] {
r_min[i] = ru;
}
if ru > r_max[i] {
r_max[i] = ru;
}
if cu < c_min[i] {
c_min[i] = cu;
}
if cu > c_max[i] {
c_max[i] = cu;
}
}
}
let mut out = Vec::with_capacity(num_components);
for i in 1..n {
let a = areas[i] as f64;
let rbar = sum_r[i] / a;
let cbar = sum_c[i] / a;
let mu20 = sum_rr[i] - a * rbar * rbar;
let mu02 = sum_cc[i] - a * cbar * cbar;
let mu11 = sum_rc[i] - a * rbar * cbar;
out.push(Component {
area: areas[i],
bbox_min: (r_min[i], c_min[i]),
bbox_max: (r_max[i], c_max[i]),
centroid: (rbar, cbar),
mu20,
mu02,
mu11,
});
}
out
}
pub fn connected_components<T, I>(
img: &I,
connectivity: Connectivity,
background: T,
) -> Vec<Component>
where
T: Scalar + PartialEq,
I: MatrixRef<T> + ?Sized,
{
let h = img.nrows();
let w = img.ncols();
if h == 0 || w == 0 {
return Vec::new();
}
let (mut labels, mut uf) = first_pass(img, connectivity, background);
let (canonical, num) = resolve_roots(&mut uf);
second_pass(&mut labels, &canonical, h, w, num)
}
pub fn connected_components_with_label_buffer<T, I>(
img: &I,
connectivity: Connectivity,
background: T,
) -> (Vec<u32>, Vec<Component>)
where
T: Scalar + PartialEq,
I: MatrixRef<T> + ?Sized,
{
let h = img.nrows();
let w = img.ncols();
if h == 0 || w == 0 {
return (Vec::new(), Vec::new());
}
let (mut labels, mut uf) = first_pass(img, connectivity, background);
let (canonical, num) = resolve_roots(&mut uf);
let comps = second_pass(&mut labels, &canonical, h, w, num);
(labels, comps)
}
pub fn connected_components_labeled<T, I>(
img: &I,
connectivity: Connectivity,
background: T,
) -> (DynMatrix<u32>, Vec<Component>)
where
T: Scalar + PartialEq,
I: MatrixRef<T> + ?Sized,
{
let h = img.nrows();
let w = img.ncols();
if h == 0 || w == 0 {
return (DynMatrix::<u32>::zeros(h, w), Vec::new());
}
let (mut labels, mut uf) = first_pass(img, connectivity, background);
let (canonical, num) = resolve_roots(&mut uf);
let comps = second_pass(&mut labels, &canonical, h, w, num);
let mut out = DynMatrix::<u32>::zeros(h, w);
for r in 0..h {
for c in 0..w {
out[(r, c)] = labels[r * w + c];
}
}
(out, comps)
}