use leptonica::morph::MorphOpType;
use leptonica::morph::morphapp::{
RunDirection, RunType, ScaleDirection, TophatType, display_matched_pattern, fast_tophat,
h_dome, morph_sequence_by_component, morph_sequence_by_region, pixa_extend_by_morph,
pixa_extend_by_scaling, remove_matched_pattern, run_histogram_morph, selective_conn_comp_fill,
};
use leptonica::morph::sel::SelElement;
use leptonica::morph::selgen::{
generate_sel_boundary, generate_sel_random, generate_sel_with_runs, get_run_centers_on_line,
get_runs_on_line, subsample_boundary_pixels,
};
use leptonica::morph::thin::pixa_thin_connected;
use leptonica::morph::{Connectivity, ThinType};
use leptonica::{Pix, Pixa, PixelDepth, Sarray};
fn make_two_rects(w: u32, h: u32) -> Pix {
let pix = Pix::new(w, h, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for y in 2..10 {
for x in 2..10 {
pm.set_pixel_unchecked(x, y, 1);
}
}
for y in 20..30 {
for x in 20..30 {
pm.set_pixel_unchecked(x, y, 1);
}
}
pm.into()
}
fn make_rect(w: u32, h: u32, x0: u32, y0: u32, x1: u32, y1: u32) -> Pix {
let pix = Pix::new(w, h, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for y in y0..y1 {
for x in x0..x1 {
pm.set_pixel_unchecked(x, y, 1);
}
}
pm.into()
}
fn make_run_pattern() -> Pix {
let pix = Pix::new(40, 10, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for x in 5..15 {
pm.set_pixel_unchecked(x, 5, 1);
}
for x in 20..30 {
pm.set_pixel_unchecked(x, 5, 1);
}
pm.into()
}
#[test]
fn test_morph_sequence_by_component_basic() {
let pix = make_two_rects(40, 40);
let result = morph_sequence_by_component(&pix, "d3.3", 0, 0, 4).unwrap();
assert_eq!(result.depth(), PixelDepth::Bit1);
assert_eq!(result.width(), 40);
assert_eq!(result.height(), 40);
assert!(result.count_pixels() >= pix.count_pixels());
}
#[test]
fn test_morph_sequence_by_component_min_size_filter() {
let pix = make_two_rects(40, 40);
let result = morph_sequence_by_component(&pix, "d3.3", 9, 9, 4).unwrap();
assert!(result.count_pixels() > 0);
let top_left_count: u64 = (2..10u32)
.flat_map(|y| (2..10u32).map(move |x| (x, y)))
.filter(|&(x, y)| result.get_pixel_unchecked(x, y) != 0)
.count() as u64;
assert_eq!(top_left_count, 0);
}
#[test]
fn test_morph_sequence_by_component_requires_1bpp() {
let pix = Pix::new(40, 40, PixelDepth::Bit8).unwrap();
assert!(morph_sequence_by_component(&pix, "d3.3", 0, 0, 4).is_err());
}
#[test]
fn test_morph_sequence_by_region_basic() {
let pix = make_two_rects(40, 40);
let mask = make_two_rects(40, 40);
let result = morph_sequence_by_region(&pix, &mask, "d3.3", 4, 0, 0).unwrap();
assert_eq!(result.depth(), PixelDepth::Bit1);
assert_eq!(result.width(), 40);
assert_eq!(result.height(), 40);
assert!(result.count_pixels() > 0);
}
#[test]
fn test_morph_sequence_by_region_requires_1bpp() {
let pix = Pix::new(40, 40, PixelDepth::Bit8).unwrap();
let mask = make_two_rects(40, 40);
assert!(morph_sequence_by_region(&pix, &mask, "d3.3", 4, 0, 0).is_err());
}
#[test]
fn test_selective_conn_comp_fill_fills_hole() {
let pix = Pix::new(24, 24, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for y in 4..20u32 {
for x in 4..20u32 {
pm.set_pixel_unchecked(x, y, 1);
}
}
for y in 10..14u32 {
for x in 10..14u32 {
pm.set_pixel_unchecked(x, y, 0);
}
}
let pix: Pix = pm.into();
let out = selective_conn_comp_fill(&pix, 8, 1, 1).unwrap();
assert_eq!(out.get_pixel_unchecked(12, 12), 1);
}
#[test]
fn test_remove_and_display_matched_pattern() {
let pix = Pix::new(12, 12, PixelDepth::Bit1).unwrap();
let mut sm = pix.try_into_mut().unwrap();
sm.set_pixel_unchecked(6, 7, 1);
let src: Pix = sm.into();
let pattern = Pix::new(1, 1, PixelDepth::Bit1).unwrap();
let mut pm = pattern.try_into_mut().unwrap();
pm.set_pixel_unchecked(0, 0, 1);
let pattern: Pix = pm.into();
let matches = Pix::new(12, 12, PixelDepth::Bit1).unwrap();
let mut mm = matches.try_into_mut().unwrap();
mm.set_pixel_unchecked(6, 7, 1);
let matches: Pix = mm.into();
let removed = remove_matched_pattern(&src, &pattern, &matches, 0, 0, 0).unwrap();
assert_eq!(removed.get_pixel_unchecked(6, 7), 0);
let color = 0x00ff00ff;
let shown = display_matched_pattern(&src, &pattern, &matches, 0, 0, color, 1.0).unwrap();
assert_eq!(shown.depth(), PixelDepth::Bit32);
assert_eq!(shown.get_pixel_unchecked(6, 7), color);
}
#[test]
fn test_pixa_extend_by_morph_and_scaling() {
let pix = Pix::new(10, 6, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
pm.set_pixel_unchecked(5, 3, 1);
let mut pixa = Pixa::new();
pixa.push(pm.into());
let extm = pixa_extend_by_morph(&pixa, MorphOpType::Dilate, 2, None, true).unwrap();
assert_eq!(extm.len(), 3);
assert!(extm.get(2).unwrap().count_pixels() >= extm.get(1).unwrap().count_pixels());
let exts =
pixa_extend_by_scaling(&pixa, &[0.5, 2.0], ScaleDirection::BothDirections, false).unwrap();
assert_eq!(exts.len(), 2);
assert_eq!(exts.get(0).unwrap().width(), 5);
assert_eq!(exts.get(1).unwrap().height(), 12);
}
#[test]
fn test_run_histogram_morph_and_gray_ops() {
let pix = Pix::new(8, 1, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for x in 2..6u32 {
pm.set_pixel_unchecked(x, 0, 1);
}
let pix: Pix = pm.into();
let na = run_histogram_morph(&pix, RunType::On, RunDirection::Horizontal, 4).unwrap();
assert!(!na.is_empty());
assert_eq!(na.get(0), Some(0.0));
let gray = Pix::new(16, 16, PixelDepth::Bit8).unwrap();
let mut gm = gray.try_into_mut().unwrap();
for y in 0..16u32 {
for x in 0..16u32 {
gm.set_pixel_unchecked(x, y, 80);
}
}
gm.set_pixel_unchecked(8, 8, 130);
let gray: Pix = gm.into();
let dome = h_dome(&gray, 20, 4).unwrap();
assert!(dome.get_pixel_unchecked(8, 8) > 0);
let tophat = fast_tophat(&gray, 3, 3, TophatType::White).unwrap();
assert_eq!(tophat.depth(), PixelDepth::Bit8);
}
#[test]
fn test_generate_sel_boundary_basic() {
let pix = make_rect(20, 20, 5, 5, 15, 15);
let sel = generate_sel_boundary(&pix, 1, 1, 0, 0, true, true, true, true).unwrap();
assert!(sel.hit_count() > 0);
assert!(sel.miss_count() > 0);
}
#[test]
fn test_generate_sel_boundary_skip_hits() {
let pix = make_rect(20, 20, 5, 5, 15, 15);
let sel = generate_sel_boundary(&pix, 1, 1, 2, 2, true, true, true, true).unwrap();
assert!(sel.hit_count() > 0);
}
#[test]
fn test_generate_sel_boundary_requires_1bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
assert!(generate_sel_boundary(&pix, 1, 1, 0, 0, true, true, true, true).is_err());
}
#[test]
fn test_generate_sel_with_runs_basic() {
let pix = make_rect(30, 30, 5, 5, 25, 25);
let sel = generate_sel_with_runs(&pix, 2, 2, 1, 1, 0, 0, 0, 0).unwrap();
assert!(sel.hit_count() > 0);
}
#[test]
fn test_generate_sel_with_runs_requires_1bpp() {
let pix = Pix::new(30, 30, PixelDepth::Bit8).unwrap();
assert!(generate_sel_with_runs(&pix, 2, 2, 1, 1, 0, 0, 0, 0).is_err());
}
#[test]
fn test_generate_sel_random_basic() {
let pix = make_rect(30, 30, 5, 5, 25, 25);
let sel = generate_sel_random(&pix, 0.5, 0.5, 1, 0, 0, 0, 0).unwrap();
assert!(sel.hit_count() > 0);
}
#[test]
fn test_generate_sel_random_requires_1bpp() {
let pix = Pix::new(30, 30, PixelDepth::Bit8).unwrap();
assert!(generate_sel_random(&pix, 0.5, 0.5, 1, 0, 0, 0, 0).is_err());
}
#[test]
fn test_get_run_centers_on_line_horizontal() {
let pix = make_run_pattern();
let centers = get_run_centers_on_line(&pix, -1, 5, 1).unwrap();
assert_eq!(centers.len(), 2);
let c0 = centers.get(0).unwrap() as u32;
assert!((9..=10).contains(&c0));
let c1 = centers.get(1).unwrap() as u32;
assert!((24..=25).contains(&c1));
}
#[test]
fn test_get_run_centers_on_line_vertical() {
let pix = make_run_pattern();
let centers = get_run_centers_on_line(&pix, 10, -1, 1).unwrap();
assert_eq!(centers.len(), 1);
}
#[test]
fn test_get_run_centers_on_line_min_length_filter() {
let pix = make_run_pattern();
let centers = get_run_centers_on_line(&pix, -1, 5, 15).unwrap();
assert_eq!(centers.len(), 0);
}
#[test]
fn test_get_runs_on_line_horizontal() {
let pix = make_run_pattern();
let runs = get_runs_on_line(&pix, 0, 5, 39, 5).unwrap();
assert!(runs.len() >= 5);
assert!((runs.get(0).unwrap() - 5.0).abs() < 0.01);
assert!((runs.get(1).unwrap() - 10.0).abs() < 0.01);
}
#[test]
fn test_get_runs_on_line_single_pixel_line() {
let pix = Pix::new(1, 1, PixelDepth::Bit1).unwrap();
let mut pm = pix.try_into_mut().unwrap();
pm.set_pixel_unchecked(0, 0, 1);
let pix: Pix = pm.into();
let runs = get_runs_on_line(&pix, 0, 0, 0, 0).unwrap();
assert!(runs.len() >= 2);
}
#[test]
fn test_subsample_boundary_pixels_skip_0() {
let pix = make_rect(20, 20, 5, 5, 15, 15);
let boundary =
leptonica::morph::extract_boundary(&pix, leptonica::morph::BoundaryType::Inner).unwrap();
let pta = subsample_boundary_pixels(&boundary, 0).unwrap();
assert!(!pta.is_empty());
}
#[test]
fn test_subsample_boundary_pixels_skip_2() {
let pix = make_rect(20, 20, 5, 5, 15, 15);
let boundary =
leptonica::morph::extract_boundary(&pix, leptonica::morph::BoundaryType::Inner).unwrap();
let all = subsample_boundary_pixels(&boundary, 0).unwrap();
let sampled = subsample_boundary_pixels(&boundary, 2).unwrap();
assert!(sampled.len() <= all.len());
assert!(!sampled.is_empty());
}
#[test]
fn test_sela_create_from_color_pixa_basic() {
use leptonica::morph::sel::sela_create_from_color_pixa;
let pix = Pix::new(3, 3, PixelDepth::Bit32).unwrap();
let mut pm = pix.try_into_mut().unwrap();
for y in 0..3u32 {
for x in 0..3u32 {
pm.set_pixel_unchecked(x, y, 0xFFFFFF00);
}
}
pm.set_pixel_unchecked(1, 1, 0x00FF0000);
pm.set_pixel_unchecked(0, 0, 0xFF000000);
let pix: Pix = pm.into();
let mut pixa = Pixa::new();
pixa.push(pix);
let mut sa = Sarray::new();
sa.push("test_sel");
let sela = sela_create_from_color_pixa(&pixa, &sa).unwrap();
assert_eq!(sela.count(), 1);
let sel = sela.get(0).unwrap();
assert_eq!(sel.get_element(1, 1), Some(SelElement::Hit));
assert_eq!(sel.get_element(0, 0), Some(SelElement::Miss));
}
#[test]
fn test_sela_create_from_color_pixa_mismatched_lengths() {
use leptonica::morph::sel::sela_create_from_color_pixa;
let pixa = Pixa::new();
let mut sa = Sarray::new();
sa.push("extra_name");
assert!(sela_create_from_color_pixa(&pixa, &sa).is_err());
}
#[test]
fn test_pixa_thin_connected_basic() {
let mut pixa = Pixa::new();
pixa.push(make_rect(20, 20, 2, 5, 18, 15));
pixa.push(make_rect(30, 30, 3, 3, 27, 27));
let result = pixa_thin_connected(&pixa, ThinType::Foreground, Connectivity::Four, 0).unwrap();
assert_eq!(result.len(), 2);
for i in 0..result.len() {
let orig = pixa.get(i).unwrap();
let thinned = result.get(i).unwrap();
assert_eq!(thinned.depth(), PixelDepth::Bit1);
assert!(thinned.count_pixels() <= orig.count_pixels());
assert!(thinned.count_pixels() > 0);
}
}
#[test]
fn test_pixa_thin_connected_requires_1bpp() {
let mut pixa = Pixa::new();
pixa.push(Pix::new(10, 10, PixelDepth::Bit8).unwrap());
assert!(pixa_thin_connected(&pixa, ThinType::Foreground, Connectivity::Four, 0).is_err());
}
#[test]
fn test_pixa_thin_connected_empty_pixa() {
let pixa = Pixa::new();
let result = pixa_thin_connected(&pixa, ThinType::Foreground, Connectivity::Four, 0).unwrap();
assert_eq!(result.len(), 0);
}