///|
pub fn principal_point_error(k : CameraIntrinsics, size : ImageSize) -> Double {
  let expected_x = Double::from_int(size.width) / 2.0
  let expected_y = Double::from_int(size.height) / 2.0
  @core.Vec2::new(x=k.cx - expected_x, y=k.cy - expected_y).norm()
}

///|
pub fn focal_consistency(k : CameraIntrinsics) -> Double {
  @core.relative_error(k.fx, k.fy)
}

///|
pub fn camera_model_score(k : CameraIntrinsics, size : ImageSize) -> Double {
  if !k.valid() {
    0.0
  } else {
    1.0 / (1.0 + principal_point_error(k, size) + focal_consistency(k))
  }
}

///|
pub fn residual_histogram(
  residuals : ArrayView[Double],
  bins~ : Int,
  maximum~ : Double,
) -> Array[Int] raise @core.GeometryError {
  if bins <= 0 || maximum <= 0.0 {
    raise @core.GeometryError::DegenerateInput(
      "histogram parameters are invalid",
    )
  }
  let result : Array[Int] = []
  for _ in 0..= bins { bins - 1 } else { raw }
    result[index] += 1
  }
  result
}

///|
pub fn coverage_fraction(
  observations : ArrayView[CalibrationObservation],
  size : ImageSize,
) -> Double {
  if observations.length() == 0 {
    0.0
  } else {
    let valid = valid_observations(observations, size)
    Double::from_int(valid.length()) / Double::from_int(observations.length())
  }
}

///|
pub fn calibration_acceptance(
  report : CalibrationReport,
  coverage : Double,
  minimum_coverage? : Double = 0.8,
) -> Bool {
  report.valid && coverage >= minimum_coverage && report.rms <= 2.0
}