///|
pub(all) struct ConsensusReport {
total : Int
inliers : Int
ratio : Double
mean_inlier_error : Double
max_inlier_error : Double
} derive(Debug, Eq)
///|
pub fn report_from_residuals(
residuals : ArrayView[Double],
threshold~ : Double,
) -> ConsensusReport raise @core.GeometryError {
if threshold <= 0.0 {
raise @core.GeometryError::DegenerateInput(
"report threshold must be positive",
)
}
let mut count = 0
let mut total = 0.0
let mut maximum = 0.0
for residual in residuals {
if residual <= threshold {
count += 1
total += residual
if residual > maximum {
maximum = residual
}
}
}
let mean = if count == 0 { 0.0 } else { total / Double::from_int(count) }
let ratio = if residuals.length() == 0 {
0.0
} else {
Double::from_int(count) / Double::from_int(residuals.length())
}
{
total: residuals.length(),
inliers: count,
ratio,
mean_inlier_error: mean,
max_inlier_error: maximum,
}
}
///|
pub fn ConsensusReport::acceptable(
report : ConsensusReport,
minimum_ratio~ : Double,
maximum_mean_error~ : Double,
) -> Bool {
report.ratio >= minimum_ratio &&
report.mean_inlier_error <= maximum_mean_error
}
///|
pub fn ConsensusReport::summary_score(report : ConsensusReport) -> Double {
report.ratio / (1.0 + report.mean_inlier_error + report.max_inlier_error)
}
///|
pub fn clipped_residuals(
residuals : ArrayView[Double],
cutoff~ : Double,
) -> Array[Double] {
let result : Array[Double] = []
for residual in residuals {
result.push(if residual < cutoff { residual } else { cutoff })
}
result
}
///|
pub fn inlier_weight(residual~ : Double, threshold~ : Double) -> Double {
if threshold <= 0.0 || residual >= threshold {
0.0
} else {
let ratio = residual / threshold
1.0 - ratio * ratio
}
}
///|
pub fn weighted_consensus(
residuals : ArrayView[Double],
threshold~ : Double,
) -> Double {
let mut total = 0.0
for residual in residuals {
total += inlier_weight(residual~, threshold~)
}
total
}
///|
pub fn validate_mask(mask : InlierMask, expected_length~ : Int) -> Bool {
mask.length() == expected_length && mask.count() <= expected_length
}