#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Superpixel {
pub id: u32,
pub center_x: f32,
pub center_y: f32,
pub pixels: Vec<(u32, u32)>,
pub mean_color: [f32; 3],
}
impl Superpixel {
#[must_use]
pub fn pixel_count(&self) -> usize {
self.pixels.len()
}
#[must_use]
pub fn area(&self) -> usize {
self.pixels.len()
}
#[must_use]
pub fn compactness(&self) -> f32 {
if self.pixels.is_empty() {
return 0.0;
}
let min_x = self.pixels.iter().map(|p| p.0).min().unwrap_or(0);
let max_x = self.pixels.iter().map(|p| p.0).max().unwrap_or(0);
let min_y = self.pixels.iter().map(|p| p.1).min().unwrap_or(0);
let max_y = self.pixels.iter().map(|p| p.1).max().unwrap_or(0);
let bbox_w = (max_x - min_x + 1) as f32;
let bbox_h = (max_y - min_y + 1) as f32;
let bbox_area = bbox_w * bbox_h;
if bbox_area < 1e-6 {
return 1.0;
}
(self.pixels.len() as f32 / bbox_area).min(1.0)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SlicConfig {
pub num_superpixels: u32,
pub compactness: f32,
pub max_iterations: u32,
}
impl SlicConfig {
#[must_use]
pub fn with_defaults() -> Self {
Self {
num_superpixels: 100,
compactness: 10.0,
max_iterations: 10,
}
}
}
impl Default for SlicConfig {
fn default() -> Self {
Self::with_defaults()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SuperpixelSegmenter {
pub config: SlicConfig,
}
impl SuperpixelSegmenter {
#[must_use]
pub fn new(config: SlicConfig) -> Self {
Self { config }
}
#[must_use]
pub fn estimate_grid_step(&self, img_w: u32, img_h: u32) -> u32 {
let k = self.config.num_superpixels.max(1);
let area = img_w as f64 * img_h as f64;
let step = (area / f64::from(k)).sqrt() as u32;
step.max(1)
}
#[must_use]
pub fn assign_pixels_to_centers(
&self,
pixels: &[u8],
width: u32,
height: u32,
centers: &[(f32, f32)],
) -> Vec<u32> {
let total = (width * height) as usize;
let mut labels = vec![0u32; total];
if centers.is_empty() || total == 0 {
return labels;
}
for py in 0..height {
for px in 0..width {
let idx = (py * width + px) as usize;
let _ = pixels; let best = centers
.iter()
.enumerate()
.map(|(ci, &(cx, cy))| {
let dx = px as f32 - cx;
let dy = py as f32 - cy;
(ci, dx * dx + dy * dy)
})
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.map_or(0, |(ci, _)| ci as u32);
labels[idx] = best;
}
}
labels
}
#[must_use]
pub fn count_superpixels(labels: &[u32]) -> u32 {
if labels.is_empty() {
return 0;
}
let mut seen = std::collections::HashSet::new();
for &l in labels {
seen.insert(l);
}
seen.len() as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_segmenter() -> SuperpixelSegmenter {
SuperpixelSegmenter::new(SlicConfig::default())
}
fn make_superpixel(pixels: Vec<(u32, u32)>) -> Superpixel {
Superpixel {
id: 0,
center_x: 0.0,
center_y: 0.0,
pixels,
mean_color: [128.0, 64.0, 32.0],
}
}
#[test]
fn test_superpixel_pixel_count() {
let sp = make_superpixel(vec![(0, 0), (1, 0), (2, 0)]);
assert_eq!(sp.pixel_count(), 3);
}
#[test]
fn test_superpixel_area_equals_pixel_count() {
let sp = make_superpixel(vec![(0, 0), (0, 1)]);
assert_eq!(sp.area(), sp.pixel_count());
}
#[test]
fn test_superpixel_compactness_empty() {
let sp = make_superpixel(vec![]);
assert_eq!(sp.compactness(), 0.0);
}
#[test]
fn test_superpixel_compactness_full_rectangle() {
let sp = make_superpixel(vec![(0, 0), (1, 0), (0, 1), (1, 1)]);
let c = sp.compactness();
assert!((c - 1.0).abs() < 1e-5, "compactness={}", c);
}
#[test]
fn test_superpixel_compactness_sparse() {
let sp = make_superpixel(vec![(0, 0), (2, 0)]);
let c = sp.compactness();
assert!((c - 2.0 / 3.0).abs() < 1e-5, "compactness={}", c);
}
#[test]
fn test_superpixel_single_pixel_compactness() {
let sp = make_superpixel(vec![(5, 7)]);
assert!((sp.compactness() - 1.0).abs() < 1e-5);
}
#[test]
fn test_slic_config_default_values() {
let cfg = SlicConfig::default();
assert_eq!(cfg.num_superpixels, 100);
assert!((cfg.compactness - 10.0).abs() < 1e-5);
assert_eq!(cfg.max_iterations, 10);
}
#[test]
fn test_estimate_grid_step_basic() {
let seg = default_segmenter(); let step = seg.estimate_grid_step(100, 100);
assert_eq!(step, 10);
}
#[test]
fn test_estimate_grid_step_minimum_one() {
let mut cfg = SlicConfig::default();
cfg.num_superpixels = 10000;
let seg = SuperpixelSegmenter::new(cfg);
let step = seg.estimate_grid_step(5, 5);
assert!(step >= 1);
}
#[test]
fn test_estimate_grid_step_large_image() {
let seg = default_segmenter(); let step = seg.estimate_grid_step(1000, 1000);
assert_eq!(step, 100);
}
#[test]
fn test_assign_pixels_no_centers_returns_zeros() {
let seg = default_segmenter();
let pixels = vec![0u8; 9]; let labels = seg.assign_pixels_to_centers(&pixels, 3, 3, &[]);
assert!(labels.iter().all(|&l| l == 0));
}
#[test]
fn test_assign_pixels_single_center() {
let seg = default_segmenter();
let pixels = vec![128u8; 12]; let centers = vec![(1.0_f32, 1.0_f32)];
let labels = seg.assign_pixels_to_centers(&pixels, 2, 2, ¢ers);
assert_eq!(labels.len(), 4);
assert!(labels.iter().all(|&l| l == 0));
}
#[test]
fn test_assign_pixels_two_centers_partition() {
let seg = default_segmenter();
let pixels = vec![0u8; 12]; let centers = vec![(0.5_f32, 0.0_f32), (2.5_f32, 0.0_f32)];
let labels = seg.assign_pixels_to_centers(&pixels, 4, 1, ¢ers);
assert_eq!(labels[0], 0);
assert_eq!(labels[1], 0);
assert_eq!(labels[2], 1);
assert_eq!(labels[3], 1);
}
#[test]
fn test_count_superpixels_empty() {
assert_eq!(SuperpixelSegmenter::count_superpixels(&[]), 0);
}
#[test]
fn test_count_superpixels_all_same() {
let labels = vec![3u32; 10];
assert_eq!(SuperpixelSegmenter::count_superpixels(&labels), 1);
}
#[test]
fn test_count_superpixels_unique_labels() {
let labels = vec![0u32, 1, 2, 1, 0, 3];
assert_eq!(SuperpixelSegmenter::count_superpixels(&labels), 4);
}
}