use crate::constants::{get_achromatic_color_number, is_achromatic_hue};
use crate::error::MunsellError;
use crate::mechanical_wedges::MechanicalWedgeSystem;
use super::color::IsccNbsColor;
use super::metadata::ColorMetadata;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
pub struct IsccNbsClassifier {
pub wedge_system: MechanicalWedgeSystem,
pub(super) color_metadata: HashMap<u16, ColorMetadata>,
pub(super) cache: Arc<RwLock<HashMap<(String, i32, i32), Option<u16>>>>,
pub(super) cache_order: Arc<RwLock<std::collections::VecDeque<(String, i32, i32)>>>,
pub(super) cache_max_size: usize,
}
impl IsccNbsClassifier {
pub fn new() -> Result<Self, MunsellError> {
let (colors, color_metadata) = Self::load_embedded_iscc_data()?;
let mut wedge_system = MechanicalWedgeSystem::new();
for polygon in colors {
wedge_system.distribute_polygon(polygon)?;
}
Ok(IsccNbsClassifier {
wedge_system,
color_metadata,
cache: Arc::new(RwLock::new(HashMap::new())),
cache_order: Arc::new(RwLock::new(std::collections::VecDeque::new())),
cache_max_size: 256,
})
}
pub fn from_csv(csv_path: &str) -> Result<Self, MunsellError> {
let (colors, color_metadata) = Self::load_iscc_data(csv_path)?;
let mut wedge_system = MechanicalWedgeSystem::new();
for polygon in colors {
wedge_system.distribute_polygon(polygon)?;
}
Ok(IsccNbsClassifier {
wedge_system,
color_metadata,
cache: Arc::new(RwLock::new(HashMap::new())),
cache_order: Arc::new(RwLock::new(std::collections::VecDeque::new())),
cache_max_size: 256,
})
}
#[inline]
fn is_achromatic(&self, hue: &str) -> bool {
is_achromatic_hue(hue)
}
#[inline]
fn get_achromatic_color_number(&self, value: f64) -> Option<u16> {
let color_number = get_achromatic_color_number(value)?;
if self.color_metadata.contains_key(&color_number) {
Some(color_number)
} else {
None
}
}
#[inline]
fn classify_achromatic(&self, value: f64) -> Option<u16> {
self.get_achromatic_color_number(value)
}
#[inline]
fn build_result(&self, color_number: u16) -> Option<ColorMetadata> {
self.color_metadata.get(&color_number).cloned()
}
pub fn classify_munsell(
&self,
hue: &str,
value: f64,
chroma: f64,
) -> Result<Option<ColorMetadata>, MunsellError> {
if self.is_achromatic(hue) {
if let Some(color_number) = self.classify_achromatic(value) {
return Ok(self.build_result(color_number));
}
return Ok(None);
}
let rounded_value = (value * 10000.0).round() / 10000.0;
let rounded_chroma = (chroma * 10000.0).round() / 10000.0;
let cache_key = (
hue.to_string(),
(rounded_value * 10000.0) as i32,
(rounded_chroma * 10000.0) as i32,
);
{
let cache = self.cache.read().unwrap();
if let Some(&cached_color_number) = cache.get(&cache_key) {
return Ok(cached_color_number.and_then(|num| self.build_result(num)));
}
}
if let Some(color) = self
.wedge_system
.classify_color(hue, rounded_value, rounded_chroma)
{
self.cache_result(cache_key, Some(color.color_number));
return Ok(self.build_result(color.color_number));
}
self.cache_result(cache_key, None);
Ok(None)
}
pub fn find_all_colors_at_point(
&self,
hue: &str,
value: f64,
chroma: f64,
) -> Result<Vec<u16>, MunsellError> {
if self.is_achromatic(hue) {
if let Some(color_number) = self.get_achromatic_color_number(value) {
return Ok(vec![color_number]);
}
return Ok(vec![]);
}
let rounded_value = (value * 10000.0).round() / 10000.0;
let rounded_chroma = (chroma * 10000.0).round() / 10000.0;
let colors = self
.wedge_system
.find_all_colors_at_point(hue, rounded_value, rounded_chroma);
Ok(colors)
}
pub fn classify(
&self,
hue: &str,
value: f64,
chroma: f64,
) -> Option<ColorMetadata> {
self.classify_munsell(hue, value, chroma).ok().flatten()
}
pub fn classify_with_details(
&self,
hue: &str,
value: f64,
chroma: f64,
) -> Option<(ColorMetadata, String)> {
match self.classify_munsell(hue, value, chroma) {
Ok(Some(metadata)) => {
let details = format!(
"Classified Munsell {}:{}/{} successfully",
hue, value, chroma
);
Some((metadata, details))
}
Ok(None) => None,
Err(_) => None,
}
}
pub(super) fn cache_result(&self, key: (String, i32, i32), result: Option<u16>) {
let mut cache = self.cache.write().unwrap();
let mut order = self.cache_order.write().unwrap();
if cache.len() >= self.cache_max_size {
if let Some(oldest_key) = order.pop_front() {
cache.remove(&oldest_key);
}
}
cache.insert(key.clone(), result);
order.push_back(key);
}
pub fn get_polygon_in_wedge(
&self,
hue: &str,
expected_descriptor: &str,
) -> Option<&IsccNbsColor> {
let wedge_key = self.wedge_system.find_wedge_for_hue(hue)?;
self.wedge_system
.get_wedge_polygons(&wedge_key)?
.iter()
.find(|polygon| {
if let Some(metadata) = self.color_metadata.get(&polygon.color_number) {
let constructed_descriptor = metadata.iscc_nbs_descriptor();
constructed_descriptor.to_lowercase() == expected_descriptor.to_lowercase()
} else {
false
}
})
}
pub fn classify_munsell_color(
&self,
munsell: &crate::types::MunsellColor,
) -> Result<Option<ColorMetadata>, MunsellError> {
if let (Some(hue), Some(chroma)) = (&munsell.hue, munsell.chroma) {
self.classify_munsell(hue, munsell.value, chroma)
} else {
Ok(None)
}
}
}