///|
/// Reverse the grayscale ordering while preserving the original max value.
/// This lets bright foreground enter a lower-star filtration first.
pub fn invert_pgm(image : PgmImage) -> PgmImage {
let maximum = image.max_value.to_double()
let grid = image.grid.map(fn(row) { row.map(fn(value) { maximum - value }) })
{ grid, max_value: image.max_value, encoding: image.encoding, }
}
///|
/// Otsu cutoff in the image's original integer scale. For equal maxima,
/// choose the smallest cutoff; a constant image uses its sole gray value.
/// The cutoff is computed on the supplied image, after any requested inversion.
pub fn otsu_threshold(image : PgmImage) -> Double {
let histogram = Array::make(image.max_value + 1, 0)
let mut pixels = 0
let mut weighted_sum = 0.0
let mut first = image.max_value
let mut last = 0
for row in image.grid {
for value in row {
let gray = value.to_int()
histogram[gray] += 1
pixels += 1
weighted_sum += value
if gray < first {
first = gray
}
if gray > last {
last = gray
}
}
}
if first == last {
return first.to_double()
}
let mut below = 0
let mut below_sum = 0.0
let mut best = -1.0
let mut cutoff = first
for gray = first; gray < last; gray = gray + 1 {
below += histogram[gray]
below_sum += gray.to_double() * histogram[gray].to_double()
if below == 0 || below == pixels {
continue
}
let numerator = weighted_sum * below.to_double() -
below_sum * pixels.to_double()
let score = numerator *
numerator /
(below.to_double() * (pixels - below).to_double())
if score > best {
best = score
cutoff = gray
}
}
cutoff.to_double()
}