use crate::core_modules::chunk::chunk::Chunk;
use crate::core_modules::pixel::pixel::Pixel;
use crate::core_modules::smart_pixel::smart_pixel::{
HueDifference, LuminanceDelta, SmartPixel,
};
use std::collections::VecDeque;
const HISTORY_WINDOW_SIZE: usize = 20;
const ANOMALY_THRESHOLD_STD_DEV: f64 = 3.0;
const STABLE_LUMINANCE_THRESHOLD: f64 = 2.0;
#[derive(Debug, Clone, PartialEq)]
pub struct AnomalyDetails {
pub luminance_score: f64,
pub color_score: f64,
pub hue_score: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChunkStatus {
Learning,
Stable,
PredictableMotion,
AnomalousEvent(AnomalyDetails),
}
pub struct SmartChunk {
pub chunk_x: u32,
pub chunk_y: u32,
average_pixel_history: VecDeque<Pixel>,
luminance_delta_history: VecDeque<LuminanceDelta>,
color_delta_history: VecDeque<f64>,
hue_difference_history: VecDeque<HueDifference>,
pub mean_luminance_delta: f64,
pub std_dev_luminance_delta: f64,
pub mean_color_delta: f64,
pub std_dev_color_delta: f64,
pub mean_hue_difference: f64,
pub std_dev_hue_difference: f64,
pub status: ChunkStatus,
}
impl SmartChunk {
pub fn new(chunk_x: u32, chunk_y: u32) -> Self {
Self {
chunk_x,
chunk_y,
average_pixel_history: VecDeque::with_capacity(HISTORY_WINDOW_SIZE + 1),
luminance_delta_history: VecDeque::with_capacity(HISTORY_WINDOW_SIZE),
color_delta_history: VecDeque::with_capacity(HISTORY_WINDOW_SIZE),
hue_difference_history: VecDeque::with_capacity(HISTORY_WINDOW_SIZE),
mean_luminance_delta: 0.0,
std_dev_luminance_delta: 0.0,
mean_color_delta: 0.0,
std_dev_color_delta: 0.0,
mean_hue_difference: 0.0,
std_dev_hue_difference: 0.0,
status: ChunkStatus::Learning,
}
}
pub fn update(&mut self, new_chunk: &Chunk) {
let new_average_pixel = new_chunk.average_pixel();
if let Some(previous_pixel) = self.average_pixel_history.back() {
let smart_new = SmartPixel::new(new_average_pixel.clone());
let smart_prev = SmartPixel::new(previous_pixel.clone());
let new_lum_delta = smart_new.delta_luminance(&smart_prev);
let new_col_delta = smart_new.delta_color(&smart_prev);
let new_hue_diff = smart_new.hue_difference(&smart_prev);
Self::update_history_generic(&mut self.luminance_delta_history, new_lum_delta);
Self::update_history_generic(&mut self.color_delta_history, new_col_delta as f64);
Self::update_history_generic(&mut self.hue_difference_history, new_hue_diff);
if self.luminance_delta_history.len() >= HISTORY_WINDOW_SIZE {
self.recalculate_statistics();
self.analyze_status(new_lum_delta, new_col_delta as f64, new_hue_diff);
}
}
Self::update_history_generic(&mut self.average_pixel_history, new_average_pixel);
}
fn update_history_generic<T>(history: &mut VecDeque<T>, new_value: T) {
history.push_back(new_value);
if history.len() > HISTORY_WINDOW_SIZE {
history.pop_front();
}
}
fn recalculate_statistics(&mut self) {
(self.mean_luminance_delta, self.std_dev_luminance_delta) =
Self::calculate_stats_for_history(&self.luminance_delta_history);
(self.mean_color_delta, self.std_dev_color_delta) =
Self::calculate_stats_for_history(&self.color_delta_history);
(self.mean_hue_difference, self.std_dev_hue_difference) =
Self::calculate_stats_for_history(&self.hue_difference_history);
}
fn calculate_stats_for_history(history: &VecDeque<f64>) -> (f64, f64) {
let count = history.len() as f64;
if count < 1.0 {
return (0.0, 0.0);
}
let sum: f64 = history.iter().sum();
let mean = sum / count;
let variance = history.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / count;
(mean, variance.sqrt())
}
fn analyze_status(&mut self, new_lum_delta: f64, new_col_delta: f64, new_hue_diff: f64) {
if new_lum_delta < STABLE_LUMINANCE_THRESHOLD {
self.status = ChunkStatus::Stable;
return;
}
let lum_score = Self::calculate_significance_score(
new_lum_delta,
self.mean_luminance_delta,
self.std_dev_luminance_delta,
);
if lum_score > ANOMALY_THRESHOLD_STD_DEV {
let col_score = Self::calculate_significance_score(
new_col_delta,
self.mean_color_delta,
self.std_dev_color_delta,
);
let hue_score = Self::calculate_significance_score(
new_hue_diff,
self.mean_hue_difference,
self.std_dev_hue_difference,
);
self.status = ChunkStatus::AnomalousEvent(AnomalyDetails {
luminance_score: lum_score,
color_score: col_score,
hue_score: hue_score,
});
} else {
self.status = ChunkStatus::PredictableMotion;
}
}
fn calculate_significance_score(value: f64, mean: f64, std_dev: f64) -> f64 {
if std_dev < 1e-6 {
return ANOMALY_THRESHOLD_STD_DEV * 2.0;
}
(value - mean) / std_dev
}
}