use crate::types::{
BackgroundMetrics, FrameQualityMetrics, QualityScores, QualityWeights, StarStats,
};
use std::path::Path;
pub fn calculate_quality_scores(
star_stats: &StarStats,
background: &BackgroundMetrics,
) -> QualityScores {
let fwhm_base_score = (1.0 - (star_stats.median_fwhm / 10.0).min(1.0)).max(0.0);
let fwhm_consistency = if star_stats.median_fwhm > 0.0 {
(1.0 - (star_stats.fwhm_std_dev / star_stats.median_fwhm).min(1.0)).max(0.0)
} else {
0.0
};
let fwhm_score = fwhm_base_score * 0.7 + fwhm_consistency * 0.3;
let elongation_score = (1.0 - ((star_stats.median_elongation - 1.0) / 2.0).min(1.0)).max(0.0);
let eccentricity_score = elongation_score;
let noise_score = (1.0 - (background.rms / 10.0).min(1.0)).max(0.0);
let background_score = background.uniformity * 0.7 + noise_score * 0.3;
let kron_score = (1.0 - (star_stats.median_kron_radius / 10.0).min(1.0)).max(0.0);
let snr_score = (1.0 - 10.0 / (10.0 + star_stats.median_snr)).max(0.0);
let flag_score = 1.0 - star_stats.flagged_fraction;
let weights = QualityWeights::default();
let overall = calculate_overall_score(
fwhm_score,
eccentricity_score,
background_score,
kron_score,
snr_score,
flag_score,
&weights,
);
QualityScores {
fwhm: fwhm_score,
eccentricity: eccentricity_score,
background: background_score,
kron_radius: kron_score,
snr: snr_score,
flag: flag_score,
overall,
}
}
pub fn calculate_overall_score(
fwhm_score: f32,
eccentricity_score: f32,
background_score: f32,
kron_score: f32,
snr_score: f32,
flag_score: f32,
weights: &QualityWeights,
) -> f32 {
let sum = weights.fwhm
+ weights.eccentricity
+ weights.background
+ weights.kron_radius
+ weights.snr
+ weights.flag;
if sum == 0.0 {
return 0.0;
}
(fwhm_score * weights.fwhm
+ eccentricity_score * weights.eccentricity
+ background_score * weights.background
+ kron_score * weights.kron_radius
+ snr_score * weights.snr
+ flag_score * weights.flag)
/ sum
}
pub fn create_frame_metrics(
path: &Path,
star_stats: StarStats,
background: BackgroundMetrics,
) -> FrameQualityMetrics {
let frame_id = path
.file_name()
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
let scores = calculate_quality_scores(&star_stats, &background);
FrameQualityMetrics {
frame_id,
star_stats,
background,
scores,
}
}
pub fn create_frame_metrics_with_weights(
path: &Path,
star_stats: StarStats,
background: BackgroundMetrics,
weights: QualityWeights,
) -> FrameQualityMetrics {
let frame_id = path
.file_name()
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
let fwhm_base_score = (1.0 - (star_stats.median_fwhm / 10.0).min(1.0)).max(0.0);
let fwhm_consistency = if star_stats.median_fwhm > 0.0 {
(1.0 - (star_stats.fwhm_std_dev / star_stats.median_fwhm).min(1.0)).max(0.0)
} else {
0.0
};
let fwhm_score = fwhm_base_score * 0.7 + fwhm_consistency * 0.3;
let elongation_score = (1.0 - ((star_stats.median_elongation - 1.0) / 2.0).min(1.0)).max(0.0);
let eccentricity_score = elongation_score;
let noise_score = (1.0 - (background.rms / 10.0).min(1.0)).max(0.0);
let background_score = background.uniformity * 0.7 + noise_score * 0.3;
let kron_score = (1.0 - (star_stats.median_kron_radius / 10.0).min(1.0)).max(0.0);
let snr_score = (1.0 - 10.0 / (10.0 + star_stats.median_snr)).max(0.0);
let flag_score = 1.0 - star_stats.flagged_fraction;
let overall = calculate_overall_score(
fwhm_score,
eccentricity_score,
background_score,
kron_score,
snr_score,
flag_score,
&weights,
);
let scores = QualityScores {
fwhm: fwhm_score,
eccentricity: eccentricity_score,
background: background_score,
kron_radius: kron_score,
snr: snr_score,
flag: flag_score,
overall,
};
FrameQualityMetrics {
frame_id,
star_stats,
background,
scores,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_quality_scores() {
let star_stats = StarStats {
count: 100,
median_fwhm: 3.0,
median_eccentricity: 0.2,
fwhm_std_dev: 0.5,
eccentricity_std_dev: 0.05,
median_kron_radius: 5.0,
median_flux: 1000.0,
median_snr: 50.0,
median_elongation: 1.2,
flagged_fraction: 0.05,
kron_radius_std_dev: 1.0,
flux_std_dev: 200.0,
snr_std_dev: 10.0,
};
let background = BackgroundMetrics {
median: 100.0,
rms: 5.0,
min: 90.0,
max: 110.0,
uniformity: 0.9,
};
let scores = calculate_quality_scores(&star_stats, &background);
assert!(scores.fwhm >= 0.0 && scores.fwhm <= 1.0);
assert!(scores.eccentricity >= 0.0 && scores.eccentricity <= 1.0);
assert!(scores.background >= 0.0 && scores.background <= 1.0);
assert!(scores.kron_radius >= 0.0 && scores.kron_radius <= 1.0);
assert!(scores.snr >= 0.0 && scores.snr <= 1.0);
assert!(scores.flag >= 0.0 && scores.flag <= 1.0);
assert!(scores.overall >= 0.0 && scores.overall <= 1.0);
assert_eq!(scores.flag, 0.95); }
#[test]
fn test_calculate_overall_score() {
let weights = QualityWeights {
fwhm: 0.3,
eccentricity: 0.2,
background: 0.2,
kron_radius: 0.15,
snr: 0.1,
flag: 0.05,
};
let overall = calculate_overall_score(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, &weights);
assert_eq!(overall, 0.5);
let overall = calculate_overall_score(1.0, 0.0, 0.5, 0.5, 0.5, 0.5, &weights);
let expected =
(1.0 * 0.3 + 0.0 * 0.2 + 0.5 * 0.2 + 0.5 * 0.15 + 0.5 * 0.1 + 0.5 * 0.05) / 1.0;
assert_eq!(overall, expected);
}
}