///|
/// Deterministic image-window operations for feature scoring and calibration.
pub(all) struct ImageWindowStats {
count : Int
minimum : Double
maximum : Double
mean : Double
variance : Double
energy : Double
} derive(Debug, Eq)
///|
pub fn image_window_index(width : Int, x : Int, y : Int) -> Int {
y * width + x
}
///|
pub fn image_window_valid(width : Int, height : Int, x : Int, y : Int) -> Bool {
width > 0 && height > 0 && x >= 0 && x < width && y >= 0 && y < height
}
///|
pub fn image_window_clamp(
value : Double,
minimum : Double,
maximum : Double,
) -> Double {
value.max(minimum).min(maximum)
}
///|
pub fn image_window_stats(values : ArrayView[Double]) -> ImageWindowStats {
if values.length() == 0 {
return {
count: 0,
minimum: 0.0,
maximum: 0.0,
mean: 0.0,
variance: 0.0,
energy: 0.0,
}
}
let mut minimum = values[0]
let mut maximum = values[0]
let mut sum = 0.0
let mut energy = 0.0
for value in values {
minimum = minimum.min(value)
maximum = maximum.max(value)
sum += value
energy += value * value
}
let mean = sum / values.length().to_double()
let mut square_error = 0.0
for value in values {
let error = value - mean
square_error += error * error
}
{
count: values.length(),
minimum,
maximum,
mean,
variance: square_error / values.length().to_double(),
energy,
}
}
///|
pub fn image_window_normalize(values : ArrayView[Double]) -> Array[Double] {
let stats = image_window_stats(values)
let result : Array[Double] = []
let scale = if stats.maximum - stats.minimum <= 0.0 {
1.0
} else {
1.0 / (stats.maximum - stats.minimum)
}
for value in values {
result.push((value - stats.minimum) * scale)
}
result
}
///|
pub fn image_window_standardize(values : ArrayView[Double]) -> Array[Double] {
let stats = image_window_stats(values)
let scale = if stats.variance <= 0.0 {
1.0
} else {
1.0 / stats.variance.sqrt()
}
let result : Array[Double] = []
for value in values {
result.push((value - stats.mean) * scale)
}
result
}
///|
pub fn image_window_clip(
values : ArrayView[Double],
minimum : Double,
maximum : Double,
) -> Array[Double] {
let result : Array[Double] = []
for value in values {
result.push(image_window_clamp(value, minimum, maximum))
}
result
}
///|
pub fn image_window_threshold(
values : ArrayView[Double],
threshold : Double,
) -> Array[Bool] {
let result : Array[Bool] = []
for value in values {
result.push(value >= threshold)
}
result
}
///|
pub fn image_window_binary_count(
values : ArrayView[Double],
threshold : Double,
) -> Int {
let mut count = 0
for value in values {
if value >= threshold {
count += 1
}
}
count
}
///|
pub fn image_window_mean_abs_difference(
left : ArrayView[Double],
right : ArrayView[Double],
) -> Double {
let count = left.length().min(right.length())
if count == 0 {
return 0.0
}
let mut total = 0.0
for i in 0.. Double {
let count = left.length().min(right.length())
if count == 0 {
return 0.0
}
let mut total = 0.0
for i in 0.. Double {
let count = left.length().min(right.length())
if count == 0 {
return 0.0
}
let left_stats = image_window_stats(left[:count])
let right_stats = image_window_stats(right[:count])
let mut numerator = 0.0
let mut left_energy = 0.0
let mut right_energy = 0.0
for i in 0.. Double {
if width <= 0 || height <= 0 {
return 0.0
}
let mut total = 0.0
let r = radius.max(0)
let start_y = (y - r).max(0)
let end_y = (y + r + 1).min(height)
let start_x = (x - r).max(0)
let end_x = (x + r + 1).min(width)
for yy in start_y..= 0 && index < values.length() {
total += values[index]
}
}
}
total
}
///|
pub fn image_window_box_mean(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
radius : Int,
) -> Double {
let r = radius.max(0)
let start_x = (x - r).max(0)
let end_x = (x + r + 1).min(width)
let start_y = (y - r).max(0)
let end_y = (y + r + 1).min(height)
let count = (end_x - start_x).max(0) * (end_y - start_y).max(0)
if count <= 0 {
0.0
} else {
image_window_box_sum(values, width, height, x, y, r) / count.to_double()
}
}
///|
pub fn image_window_gradient_x(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
) -> Double {
if !image_window_valid(width, height, x, y) {
return 0.0
}
let left = values[image_window_index(width, (x - 1).max(0), y)]
let right = values[image_window_index(width, (x + 1).min(width - 1), y)]
(right - left) * 0.5
}
///|
pub fn image_window_gradient_y(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
) -> Double {
if !image_window_valid(width, height, x, y) {
return 0.0
}
let top = values[image_window_index(width, x, (y - 1).max(0))]
let bottom = values[image_window_index(width, x, (y + 1).min(height - 1))]
(bottom - top) * 0.5
}
///|
pub fn image_window_gradient_magnitude(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
) -> Double {
let dx = image_window_gradient_x(values, width, height, x, y)
let dy = image_window_gradient_y(values, width, height, x, y)
(dx * dx + dy * dy).sqrt()
}
///|
pub fn image_window_gradient_field(
values : ArrayView[Double],
width : Int,
height : Int,
) -> Array[Double] {
let result : Array[Double] = []
for y in 0.. Double {
if !image_window_valid(width, height, x, y) {
return 0.0
}
let center = values[image_window_index(width, x, y)]
let left = values[image_window_index(width, (x - 1).max(0), y)]
let right = values[image_window_index(width, (x + 1).min(width - 1), y)]
let top = values[image_window_index(width, x, (y - 1).max(0))]
let bottom = values[image_window_index(width, x, (y + 1).min(height - 1))]
left + right + top + bottom - 4.0 * center
}
///|
pub fn image_window_corner_score(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
) -> Double {
let dx = image_window_gradient_x(values, width, height, x, y)
let dy = image_window_gradient_y(values, width, height, x, y)
let laplacian = image_window_laplacian(values, width, height, x, y)
(dx * dx + dy * dy) / (1.0 + laplacian.abs())
}
///|
pub fn image_window_patch(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
radius : Int,
) -> Array[Double] {
let result : Array[Double] = []
let r = radius.max(0)
let start_y = (y - r).max(0)
let end_y = (y + r + 1).min(height)
let start_x = (x - r).max(0)
let end_x = (x + r + 1).min(width)
for yy in start_y..= 0 && index < values.length() {
result.push(values[index])
}
}
}
result
}
///|
pub fn image_window_patch_score(
values : ArrayView[Double],
width : Int,
height : Int,
x : Int,
y : Int,
radius : Int,
) -> Double {
image_window_stats(image_window_patch(values, width, height, x, y, radius)).variance
}
///|
pub fn image_window_nonmaximum_points(
values : ArrayView[Double],
width : Int,
height : Int,
threshold : Double,
radius : Int,
) -> Array[@core.Point2] {
let result : Array[@core.Point2] = []
for y in 0..= threshold &&
score >= image_window_patch_score(values, width, height, x, y, radius) {
result.push(@core.Point2::new(x=x.to_double(), y=y.to_double()))
}
}
}
result
}
///|
pub fn image_window_downsample(
values : ArrayView[Double],
width : Int,
height : Int,
factor : Int,
) -> (Array[Double], Int, Int) {
let step = factor.max(1)
let output_width = (width.max(0) + step - 1) / step
let output_height = (height.max(0) + step - 1) / step
let result : Array[Double] = []
for y in 0.. Array[Double] {
let result : Array[Double] = []
if output_width <= 0 || output_height <= 0 {
return result
}
for y in 0..= 0 && index < values.length() {
values[index]
} else {
0.0
},
)
}
}
result
}
///|
pub fn image_window_add(
left : ArrayView[Double],
right : ArrayView[Double],
) -> Array[Double] {
let result : Array[Double] = []
let count = left.length().min(right.length())
for i in 0.. Array[Double] {
let result : Array[Double] = []
let count = left.length().min(right.length())
for i in 0.. Array[Double] {
let result : Array[Double] = []
for value in values {
result.push(value * scale)
}
result
}
///|
pub fn image_window_accumulate(
target : Array[Double],
source : ArrayView[Double],
scale : Double,
) -> Unit {
let count = target.length().min(source.length())
for i in 0.. Double {
let stats = image_window_stats(values)
let texture = if minimum_variance <= 0.0 {
1.0
} else {
(stats.variance / minimum_variance).min(1.0)
}
let brightness = if maximum_mean_abs <= 0.0 {
0.0
} else {
(stats.mean.abs() / maximum_mean_abs).min(1.0)
}
texture * (1.0 - brightness)
}