///|
fn component_neighbors(x : Int, y : Int) -> Array[Point] {
[
Point::Point(x=Double::from_int(x + 1), y=Double::from_int(y)),
Point::Point(x=Double::from_int(x - 1), y=Double::from_int(y)),
Point::Point(x=Double::from_int(x), y=Double::from_int(y + 1)),
Point::Point(x=Double::from_int(x), y=Double::from_int(y - 1)),
]
}
///|
fn flood_component(
mask : BinaryMask,
labels : Array[Int],
start_x : Int,
start_y : Int,
label : Int,
) -> Component {
let queue : Array[Point] = [
Point::Point(x=Double::from_int(start_x), y=Double::from_int(start_y)),
]
let mut cursor = 0
let mut area = 0
let mut sum_x = 0.0
let mut sum_y = 0.0
let mut min_x = start_x
let mut min_y = start_y
let mut max_x = start_x
let mut max_y = start_y
let mut perimeter = 0.0
labels[start_y * mask.width + start_x] = label
while cursor < queue.length() {
let point = queue[cursor]
cursor += 1
let x = point.x.to_int()
let y = point.y.to_int()
area += 1
sum_x += point.x
sum_y += point.y
min_x = min_x.min(x)
min_y = min_y.min(y)
max_x = max_x.max(x)
max_y = max_y.max(y)
let mut exposed = 0
for neighbor in component_neighbors(x, y) {
let nx = neighbor.x.to_int()
let ny = neighbor.y.to_int()
if !mask.inside(nx, ny) || !mask.at(nx, ny) {
exposed += 1
} else if labels[ny * mask.width + nx] == 0 {
labels[ny * mask.width + nx] = label
queue.push(neighbor)
}
}
perimeter += Double::from_int(exposed)
}
let center = Point::Point(
x=sum_x / Double::from_int(area),
y=sum_y / Double::from_int(area),
)
let circularity = if perimeter <= 0.0 {
0.0
} else {
4.0 * 3.141592653589793 * Double::from_int(area) / (perimeter * perimeter)
}
{
label,
area,
bounds: { min_x, min_y, max_x, max_y },
centroid: center,
perimeter,
circularity,
}
}
///|
pub fn label_components(mask : BinaryMask) -> Array[Component] {
let labels = Array::make(mask.width * mask.height, 0)
let components : Array[Component] = []
let mut label = 0
for y in 0.. Array[Int] {
let labels = Array::make(mask.width * mask.height, 0)
let mut label = 0
for y in 0.. BinaryMask {
let result = BinaryMask(width=mask.width, height=mask.height)
if label <= 0 {
result
} else {
let labels = component_labels(mask)
for y in 0.. Array[Point] {
let labels = component_labels(mask)
let result : Array[Point] = []
for y in 0.. Component {
let components = label_components(mask)
if components.is_empty() {
{
label: 0,
area: 0,
bounds: { min_x: 0, min_y: 0, max_x: -1, max_y: -1 },
centroid: Point::Point(x=0.0, y=0.0),
perimeter: 0.0,
circularity: 0.0,
}
} else {
let mut result = components[0]
for component in components[1:] {
if component.area > result.area {
result = component
}
}
result
}
}
///|
pub fn filter_components_by_area(
mask : BinaryMask,
minimum : Int,
maximum : Int,
) -> BinaryMask {
let result = BinaryMask(width=mask.width, height=mask.height)
let components = label_components(mask)
for component in components {
if component.area >= minimum && component.area <= maximum {
let selected = component_mask(mask, component.label)
for y in 0.. Double {
let width = component.bounds.max_x - component.bounds.min_x + 1
let height = component.bounds.max_y - component.bounds.min_y + 1
if width <= 0 || height <= 0 {
0.0
} else {
Double::from_int(component.area) / Double::from_int(width * height)
}
}
///|
pub fn component_aspect_ratio(component : Component) -> Double {
let width = (component.bounds.max_x - component.bounds.min_x + 1).to_double()
let height = (component.bounds.max_y - component.bounds.min_y + 1).to_double()
if height <= 0.0 {
0.0
} else {
width / height
}
}
///|
pub fn components_sorted_by_area(
mask : BinaryMask,
descending? : Bool = true,
) -> Array[Component] {
let result : Array[Component] = []
for component in label_components(mask) {
let mut index = 0
while index < result.length() {
let before = if descending {
result[index].area >= component.area
} else {
result[index].area <= component.area
}
if !before {
break
}
index += 1
}
result.insert(index, component)
}
result
}