///|
pub fn integral_image(image : GrayImage) -> GrayImage {
  let result = GrayImage(width=image.width, height=image.height)
  for y in 0.. Double {
  let right = integral.at(bounds.max_x, bounds.max_y)
  let left = integral.at(bounds.min_x - 1, bounds.max_y)
  let top = integral.at(bounds.max_x, bounds.min_y - 1)
  let corner = integral.at(bounds.min_x - 1, bounds.min_y - 1)
  right - left - top + corner
}

///|
pub fn local_mean_image(image : GrayImage, radius : Int) -> GrayImage {
  let integral = integral_image(image)
  let result = GrayImage(width=image.width, height=image.height)
  let safe = radius.max(0)
  for y in 0.. GrayImage {
  let squared = GrayImage(width=image.width, height=image.height)
  for i in 0.. Double {
  let histogram_value = histogram(image, bins)
  let total = image.pixels.length()
  if total == 0 {
    0.0
  } else {
    let mut result = 0.0
    for bin in histogram_value.bins {
      if bin > 0 {
        let probability = Double::from_int(bin) / Double::from_int(total)
        result -= probability * @math.ln(probability)
      }
    }
    result
  }
}

///|
pub fn percentile_threshold(image : GrayImage, q : Double) -> Double {
  percentile_value(image.pixels, q)
}

///|
pub fn adaptive_threshold(
  image : GrayImage,
  radius : Int,
  offset : Double,
) -> BinaryMask {
  let local_mean = local_mean_image(image, radius)
  let result = BinaryMask(width=image.width, height=image.height)
  for y in 0..= local_mean.at(x, y) - offset)
    }
  }
  result
}

///|
pub fn local_contrast_normalized(image : GrayImage, radius : Int) -> GrayImage {
  let mean = local_mean_image(image, radius)
  let variance = local_variance_image(image, radius)
  let result = GrayImage(width=image.width, height=image.height)
  for y in 0.. Array[StatsSummary] {
  let result : Array[StatsSummary] = []
  for y in 0.. Array[StatsSummary] {
  let result : Array[StatsSummary] = []
  for x in 0.. Double {
  let bounds = image_minmax(image)
  bounds.max - bounds.min
}

///|
pub fn image_median(image : GrayImage) -> Double {
  median_value(image.pixels)
}

///|
pub fn image_mad(image : GrayImage) -> Double {
  mean_absolute_deviation(image.pixels)
}

///|
pub fn image_signal_to_noise(image : GrayImage, background : Double) -> Double {
  let signal = (image_mean(image) - background).abs()
  let noise = texture_noise(image)
  if noise <= 0.0000001 {
    signal
  } else {
    signal / noise
  }
}

///|
pub fn image_range_report(image : GrayImage) -> String {
  let bounds = image_minmax(image)
  "min=" +
  bounds.min.to_string() +
  "; max=" +
  bounds.max.to_string() +
  "; range=" +
  (bounds.max - bounds.min).to_string()
}