///|
/// Aggregated reprojection diagnostics for one calibration batch.
pub(all) struct CalibrationBatchReport {
count : Int
mean : Double
rms : Double
maximum : Double
valid_count : Int
} derive(Debug, Eq)
///|
pub fn calibration_batch_report(
points : ArrayView[@core.Point3],
pixels : ArrayView[@core.Point2],
intrinsics : CameraIntrinsics,
pose? : CameraPose = CameraPose::identity(),
) -> CalibrationBatchReport raise @core.GeometryError {
if points.length() != pixels.length() {
raise @core.GeometryError::DegenerateInput(
"calibration arrays have different lengths",
)
}
let mut sum = 0.0
let mut square = 0.0
let mut maximum = 0.0
let mut valid = 0
for i in 0.. Array[CalibrationResidual] raise @core.GeometryError {
if points.length() != pixels.length() {
raise @core.GeometryError::DegenerateInput(
"calibration arrays have different lengths",
)
}
let result : Array[CalibrationResidual] = []
for i in 0.. pixels[i]
}
let residual = predicted.minus(pixels[i])
result.push({
index: i,
residual,
norm: residual.norm(),
valid: points[i].z > 0.0,
})
}
result
}
///|
pub fn residual_norms(
residuals : ArrayView[CalibrationResidual],
) -> Array[Double] {
let result : Array[Double] = []
for residual in residuals {
result.push(residual.norm)
}
result
}
///|
pub fn calibration_inlier_mask(
residuals : ArrayView[CalibrationResidual],
threshold : Double,
) -> Array[Bool] {
let result : Array[Bool] = []
for residual in residuals {
result.push(residual.valid && residual.norm <= threshold)
}
result
}
///|
pub fn calibration_inlier_count(
residuals : ArrayView[CalibrationResidual],
threshold : Double,
) -> Int {
let mut count = 0
for residual in residuals {
if residual.valid && residual.norm <= threshold {
count += 1
}
}
count
}
///|
pub fn calibration_inlier_ratio(
residuals : ArrayView[CalibrationResidual],
threshold : Double,
) -> Double {
if residuals.length() == 0 {
0.0
} else {
calibration_inlier_count(residuals, threshold).to_double() /
residuals.length().to_double()
}
}
///|
pub fn calibration_report_is_usable(
report : CalibrationBatchReport,
maximum_rms : Double,
minimum_valid : Int,
) -> Bool {
report.valid_count >= minimum_valid && report.rms <= maximum_rms
}
///|
pub fn split_calibration_residuals(
residuals : ArrayView[CalibrationResidual],
threshold : Double,
) -> (Array[CalibrationResidual], Array[CalibrationResidual]) {
let inliers : Array[CalibrationResidual] = []
let outliers : Array[CalibrationResidual] = []
for residual in residuals {
if residual.valid && residual.norm <= threshold {
inliers.push(residual)
} else {
outliers.push(residual)
}
}
(inliers, outliers)
}
///|
pub fn weighted_calibration_error(
residuals : ArrayView[CalibrationResidual],
weights : ArrayView[Double],
) -> Double raise @core.GeometryError {
if residuals.length() != weights.length() {
raise @core.GeometryError::DegenerateInput(
"residual weights have different lengths",
)
}
let mut numerator = 0.0
let mut denominator = 0.0
for i in 0.. Double raise @core.GeometryError {
let values = residual_norms(residuals)
@core.quantile_linear(values, probability)
}
///|
pub fn calibration_quality_score(
report : CalibrationBatchReport,
expected_count : Int,
target_rms : Double,
) -> Double {
let support = if expected_count <= 0 {
0.0
} else {
report.valid_count.to_double() / expected_count.to_double()
}
let precision = if target_rms <= 0.0 {
0.0
} else {
(1.0 - report.rms / target_rms).max(0.0)
}
(support * precision).max(0.0).min(1.0)
}
///|
pub fn calibration_batch_chunks(
points : ArrayView[@core.Point3],
chunk_size : Int,
) -> Array[Array[@core.Point3]] {
let result : Array[Array[@core.Point3]] = []
if chunk_size <= 0 {
return result
}
let mut start = 0
while start < points.length() {
let chunk : Array[@core.Point3] = []
let end = (start + chunk_size).min(points.length())
for i in start.. Double {
let mut result = 0.0
for residual in residuals {
result += residual.norm
}
result
}
///|
pub fn reprojection_residual_rms(
residuals : ArrayView[CalibrationResidual],
) -> Double {
if residuals.length() == 0 {
0.0
} else {
let mut result = 0.0
for residual in residuals {
result += residual.norm * residual.norm
}
(result / residuals.length().to_double()).sqrt()
}
}