///|
pub(all) struct PointCloudReport {
  total : Int
  positive_depth : Int
  minimum_depth : Double
  maximum_depth : Double
  centroid : @core.Point3
} derive(Debug, Eq)

///|
pub fn PointCloudReport::from_points(
  points : ArrayView[@core.Point3],
) -> PointCloudReport {
  let mut positive = 0
  let mut minimum = 0.0
  let mut maximum = 0.0
  if points.length() > 0 {
    minimum = points[0].z
    maximum = points[0].z
  }
  for point in points {
    if point.z > 0.0 {
      positive += 1
    }
    if point.z < minimum {
      minimum = point.z
    }
    if point.z > maximum {
      maximum = point.z
    }
  }
  let centroid = if points.length() == 0 {
    @core.Point3::new(x=0.0, y=0.0, z=0.0)
  } else {
    @core.centroid3(points) catch {
      _ => @core.Point3::new(x=0.0, y=0.0, z=0.0)
    }
  }
  {
    total: points.length(),
    positive_depth: positive,
    minimum_depth: minimum,
    maximum_depth: maximum,
    centroid,
  }
}

///|
pub fn PointCloudReport::positive_ratio(report : PointCloudReport) -> Double {
  if report.total == 0 {
    0.0
  } else {
    Double::from_int(report.positive_depth) / Double::from_int(report.total)
  }
}

///|
pub fn PointCloudReport::depth_span(report : PointCloudReport) -> Double {
  report.maximum_depth - report.minimum_depth
}

///|
pub fn PointCloudReport::is_usable(
  report : PointCloudReport,
  minimum_ratio? : Double = 0.8,
) -> Bool {
  report.total > 0 &&
  report.positive_ratio() >= minimum_ratio &&
  report.depth_span() > 0.0
}

///|
pub(all) struct ReprojectionSummary {
  total : Int
  inliers : Int
  outliers : Int
  rms : Double
  mean_inlier : Double
} derive(Debug, Eq)

///|
pub fn reprojection_summary(
  residuals : ArrayView[Double],
  outlier_threshold~ : Double,
) -> ReprojectionSummary {
  let mut inliers = 0
  let mut sum = 0.0
  let mut square = 0.0
  for residual in residuals {
    square += residual * residual
    if residual <= outlier_threshold {
      inliers += 1
      sum += residual
    }
  }
  let total = residuals.length()
  {
    total,
    inliers,
    outliers: total - inliers,
    rms: if total == 0 {
      0.0
    } else {
      (square / Double::from_int(total)).sqrt()
    },
    mean_inlier: if inliers == 0 {
      0.0
    } else {
      sum / Double::from_int(inliers)
    },
  }
}

///|
pub fn ReprojectionSummary::inlier_ratio(
  summary : ReprojectionSummary,
) -> Double {
  if summary.total == 0 {
    0.0
  } else {
    Double::from_int(summary.inliers) / Double::from_int(summary.total)
  }
}

///|
pub fn ReprojectionSummary::is_good(
  summary : ReprojectionSummary,
  maximum_rms? : Double = 2.0,
) -> Bool {
  summary.total > 0 && summary.rms <= maximum_rms && summary.inliers > 0
}

///|
pub(all) struct ViewGraph {
  adjacency : Array[Array[Bool]]
} derive(Debug, Eq)

///|
pub fn ViewGraph::new(view_count : Int) -> ViewGraph {
  let rows : Array[Array[Bool]] = []
  for _ in 0.. Int {
  graph.adjacency.length()
}

///|
pub fn ViewGraph::add_edge(graph : ViewGraph, left : Int, right : Int) -> Unit {
  if left >= 0 &&
    right >= 0 &&
    left < graph.view_count() &&
    right < graph.view_count() {
    graph.adjacency[left][right] = true
    graph.adjacency[right][left] = true
  }
}

///|
pub fn ViewGraph::has_edge(graph : ViewGraph, left : Int, right : Int) -> Bool {
  left >= 0 &&
  right >= 0 &&
  left < graph.view_count() &&
  right < graph.view_count() &&
  graph.adjacency[left][right]
}

///|
pub fn ViewGraph::component_count(graph : ViewGraph) -> Int {
  let visited : Array[Bool] = []
  for _ in 0..