///|
pub enum Connectivity {
Four
Eight
} derive(Debug, Eq)
///|
pub struct Component {
id : Int
area : Int
bounds : Rect
pixels : Array[Point]
} derive(Debug, Eq)
///|
pub fn Component::new(id~ : Int, pixels~ : Array[Point]) -> Component {
let mut left = 0
let mut top = 0
let mut right = 0
let mut bottom = 0
if pixels.length() > 0 {
left = pixels[0].x
top = pixels[0].y
right = pixels[0].x + 1
bottom = pixels[0].y + 1
for point in pixels {
left = min_i(left, point.x)
top = min_i(top, point.y)
right = max_i(right, point.x + 1)
bottom = max_i(bottom, point.y + 1)
}
}
{
id,
area: pixels.length(),
bounds: Rect::new(x=left, y=top, width=right - left, height=bottom - top),
pixels,
}
}
///|
pub fn Component::centroid_x1000(self : Component) -> Point {
if self.area == 0 {
Point::new(x=0, y=0)
} else {
let mut x = 0
let mut y = 0
for point in self.pixels {
x += point.x
y += point.y
}
Point::new(x=x * 1000 / self.area, y=y * 1000 / self.area)
}
}
///|
pub fn Component::perimeter(
self : Component,
connectivity? : Connectivity = Four,
) -> Int {
let mut total = 0
let directions = [
Point::new(x=1, y=0),
Point::new(x=-1, y=0),
Point::new(x=0, y=1),
Point::new(x=0, y=-1),
]
for point in self.pixels {
let mut exposed = 0
for direction in directions {
let found = self.contains(
Point::new(x=point.x + direction.x, y=point.y + direction.y),
)
if !found {
exposed += 1
}
}
if connectivity == Eight {
let diagonals = [
Point::new(x=1, y=1),
Point::new(x=1, y=-1),
Point::new(x=-1, y=1),
Point::new(x=-1, y=-1),
]
for direction in diagonals {
if !self.contains(
Point::new(x=point.x + direction.x, y=point.y + direction.y),
) {
exposed += 1
}
}
}
total += exposed
}
total
}
///|
pub fn Component::contains(self : Component, point : Point) -> Bool {
for candidate in self.pixels {
if candidate == point {
return true
}
}
false
}
///|
pub fn Component::to_mask(self : Component, size : Size) -> Mask {
let mask = Mask::new(width=size.width, height=size.height)
for point in self.pixels {
mask.set(point.x, point.y, self.id)
}
mask
}
///|
pub fn connected_components(
mask : Mask,
foreground : Int,
connectivity? : Connectivity = Four,
) -> Array[Component] {
let labels = Array::make(mask.values.length(), 0)
let components : Array[Component] = []
let width = max_i(0, mask.width)
let height = max_i(0, mask.height)
let cardinal = [
Point::new(x=1, y=0),
Point::new(x=-1, y=0),
Point::new(x=0, y=1),
Point::new(x=0, y=-1),
]
let diagonal = [
Point::new(x=1, y=1),
Point::new(x=1, y=-1),
Point::new(x=-1, y=1),
Point::new(x=-1, y=-1),
]
let mut next_id = 1
for y in 0..= 0 && ny >= 0 && nx < width && ny < height {
let ni = ny * width + nx
if mask.values[ni] == foreground && labels[ni] == 0 {
labels[ni] = next_id
queue.push(Point::new(x=nx, y=ny))
}
}
}
if connectivity == Eight {
for direction in diagonal {
let nx = point.x + direction.x
let ny = point.y + direction.y
if nx >= 0 && ny >= 0 && nx < width && ny < height {
let ni = ny * width + nx
if mask.values[ni] == foreground && labels[ni] == 0 {
labels[ni] = next_id
queue.push(Point::new(x=nx, y=ny))
}
}
}
}
}
components.push(Component::new(id=next_id, pixels~))
next_id += 1
}
}
}
components
}
///|
pub fn component_labels(mask : Mask, foreground : Int) -> Mask {
let out = Mask::new(width=mask.width, height=mask.height)
let components = connected_components(mask, foreground)
for component in components {
for point in component.pixels {
out.set(point.x, point.y, component.id)
}
}
out
}
///|
pub fn remove_small_components(
mask : Mask,
foreground : Int,
minimum_area : Int,
connectivity? : Connectivity = Four,
) -> Mask {
let out = Mask::new(width=mask.width, height=mask.height)
let threshold = max_i(0, minimum_area)
for component in connected_components(mask, foreground, connectivity~) {
if component.area >= threshold {
for point in component.pixels {
out.set(point.x, point.y, foreground)
}
}
}
out
}
///|
pub fn keep_largest_component(
mask : Mask,
foreground : Int,
connectivity? : Connectivity = Four,
) -> Mask {
let out = Mask::new(width=mask.width, height=mask.height)
let mut largest : Component? = None
for component in connected_components(mask, foreground, connectivity~) {
match largest {
None => largest = Some(component)
Some(previous) =>
if component.area > previous.area {
largest = Some(component)
}
}
}
match largest {
None => out
Some(component) => {
for point in component.pixels {
out.set(point.x, point.y, foreground)
}
out
}
}
}
///|
pub fn binary_fill_holes(mask : Mask, foreground : Int) -> Mask {
let out = mask
let background = Mask::new(width=mask.width, height=mask.height)
let queue : Array[Point] = []
if mask.width > 0 && mask.height > 0 {
for x in 0.. Mask {
let out = Mask::new(width=mask.width, height=mask.height)
for y in 0..