///|
pub(all) struct HistogramBin {
  lower : Double
  upper : Double
  count : Int
} derive(Debug, Eq, ToJson)

///|
pub fn ThermalMatrix::histogram(
  matrix : ThermalMatrix,
  bins~ : Int,
) -> Array[HistogramBin] raise ThermalError {
  let count = Int::max(1, bins)
  let range = matrix.range()
  let span = range.span()
  let step = if span == 0.0 { 1.0 } else { span / count.to_double() }
  let counts = Array::make(count, 0)
  for value in matrix.values {
    let index = if span == 0.0 {
      0
    } else {
      ((value - range.min) / step).floor().to_int().clamp(min=0, max=count - 1)
    }
    counts[index] += 1
  }
  let result : Array[HistogramBin] = []
  for i in 0.. Array[Anomaly] raise ThermalError {
  let stats = matrix.stats()
  let limit = Double::max(0.0, z_limit)
  let result : Array[Anomaly] = []
  for y in 0..= limit {
        result.push({ point: ThermalPoint::new(x~, y~), value, z_score: score })
      }
    }
  }
  result.sort_by(fn(a, b) { b.z_score.abs().compare(a.z_score.abs()) })
  result
}

///|
pub fn ThermalMatrix::hot_fraction(
  matrix : ThermalMatrix,
  threshold~ : Double,
) -> Double {
  let count = matrix.values.count_if(fn(value) { value >= threshold })
  count.to_double() / matrix.values.length().to_double()
}

///|
pub fn ThermalRegion::perimeter(region : ThermalRegion) -> Int {
  let pixels = region.pixels
  let mut perimeter = 0
  for point in pixels {
    let mut neighbors = 0
    for other in pixels {
      if (other.x - point.x).abs() + (other.y - point.y).abs() == 1 {
        neighbors += 1
      }
    }
    perimeter += 4 - neighbors
  }
  perimeter
}