use super::AnnotationColumn;
use crate::column::Presence;
pub const MISSING_BAND_CODE: u8 = u8::MAX;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PlddtBand {
VeryLow,
Low,
Confident,
VeryHigh,
}
impl PlddtBand {
pub const ALL: [Self; 4] = [Self::VeryLow, Self::Low, Self::Confident, Self::VeryHigh];
#[must_use]
pub fn classify(score: f64) -> Option<Self> {
if !score.is_finite() {
return None;
}
Some(if score > 90.0 {
Self::VeryHigh
} else if score > 70.0 {
Self::Confident
} else if score > 50.0 {
Self::Low
} else {
Self::VeryLow
})
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::VeryLow => "very_low",
Self::Low => "low",
Self::Confident => "confident",
Self::VeryHigh => "very_high",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::ALL
.into_iter()
.find(|band| band.name().eq_ignore_ascii_case(name))
}
#[must_use]
pub const fn rgb(self) -> [u8; 3] {
match self {
Self::VeryLow => [255, 125, 69],
Self::Low => [255, 219, 19],
Self::Confident => [101, 203, 243],
Self::VeryHigh => [0, 83, 214],
}
}
#[must_use]
pub const fn code(self) -> u8 {
match self {
Self::VeryLow => 0,
Self::Low => 1,
Self::Confident => 2,
Self::VeryHigh => 3,
}
}
#[must_use]
pub fn codes(column: &AnnotationColumn<f64>) -> Vec<u8> {
column
.values()
.iter()
.enumerate()
.map(|(row, score)| {
let recorded =
u32::try_from(row).is_ok_and(|atom| column.presence(atom) == Presence::Present);
Self::classify(*score)
.filter(|_| recorded)
.map_or(MISSING_BAND_CODE, Self::code)
})
.collect()
}
#[must_use]
pub const fn lower_bound(self) -> f64 {
match self {
Self::VeryLow => f64::NEG_INFINITY,
Self::Low => 50.0,
Self::Confident => 70.0,
Self::VeryHigh => 90.0,
}
}
}
#[cfg(test)]
#[path = "band_tests.rs"]
mod tests;