///|
pub(all) enum ThresholdMode {
Absolute(Double)
MeanOffset(Double)
MedianOffset(Double)
Percentile(Double)
} derive(Debug, Eq, ToJson)
///|
pub fn ThermalMatrix::threshold_value(
matrix : ThermalMatrix,
mode : ThresholdMode,
) -> Double raise ThermalError {
match mode {
Absolute(value) => value
MeanOffset(offset) => matrix.stats().mean + offset
MedianOffset(offset) => matrix.stats().p50 + offset
Percentile(p) => {
let stats = matrix.stats()
if p <= 0.5 {
stats.p50
} else if p <= 0.9 {
stats.p90
} else {
stats.p95
}
}
}
}
///|
pub fn ThermalMatrix::adaptive_mask(
matrix : ThermalMatrix,
mode : ThresholdMode,
) -> ThermalMask raise ThermalError {
matrix.threshold_mask(min_temp=matrix.threshold_value(mode))
}
///|
pub(all) struct BoundaryPixel {
point : ThermalPoint
value : Double
normal_x : Int
normal_y : Int
} derive(Debug, Eq, ToJson)
///|
pub fn ThermalMatrix::boundary(
matrix : ThermalMatrix,
mask : ThermalMask,
) -> Array[BoundaryPixel] raise ThermalError {
if matrix.width != mask.width || matrix.height != mask.height {
raise InvalidDimensions(
width=matrix.width,
height=matrix.height,
values=mask.cells.length(),
)
}
let result : Array[BoundaryPixel] = []
for y in 0.. ThermalMask {
let outside = Array::make(mask.cells.length(), false)
let stack : Array[ThermalPoint] = []
for x in 0.. break
Some(point) =>
if point.x >= 0 &&
point.y >= 0 &&
point.x < mask.width &&
point.y < mask.height {
let index = point.y * mask.width + point.x
if !outside[index] && !mask.cells[index] {
outside[index] = true
stack.append(point.neighbors(connectivity=4))
}
}
}
}
let cells : Array[Bool] = []
for index, value in mask.cells {
cells.push(value || !outside[index])
}
{ width: mask.width, height: mask.height, cells }
}
///|
pub fn ThermalMask::remove_small_components(
mask : ThermalMask,
minimum_area~ : Int,
) -> ThermalMask {
let cells = mask.cells.copy()
let visited = Array::make(cells.length(), false)
for y in 0.. break
Some(point) => {
let index = point.y * mask.width + point.x
if !visited[index] && cells[index] {
visited[index] = true
component.push(index)
stack.append(point.neighbors(connectivity=4))
}
}
}
}
if component.length() < minimum_area {
for index in component {
cells[index] = false
}
}
}
}
}
{ width: mask.width, height: mask.height, cells }
}
///|
pub fn ThermalMatrix::region_adjacency(
matrix : ThermalMatrix,
regions : Array[ThermalRegion],
) -> Array[Array[Int]] {
ignore(matrix)
let result : Array[Array[Int]] = []
for region in regions {
let neighbors : Array[Int] = []
for other in regions {
if region.id != other.id &&
(region.max_x + 1 >= other.min_x && other.max_x + 1 >= region.min_x) &&
region.max_y + 1 >= other.min_y &&
other.max_y + 1 >= region.min_y {
neighbors.push(other.id)
}
}
result.push(neighbors)
}
result
}
///|
pub fn ThermalRegion::compactness(region : ThermalRegion) -> Double {
let perimeter = region.perimeter().to_double()
if perimeter == 0.0 {
0.0
} else {
4.0 *
3.141592653589793 *
region.area().to_double() /
(perimeter * perimeter)
}
}
///|
pub fn ThermalRegion::centroid(region : ThermalRegion) -> ThermalPoint {
let mut x = 0
let mut y = 0
for point in region.pixels {
x += point.x
y += point.y
}
let area = Int::max(1, region.area())
ThermalPoint::new(x=x / area, y=y / area)
}