use crate::core_modules::smart_blob::{Point, SmartBlob};
use crate::core_modules::smart_chunk::{AnomalyDetails, ChunkStatus};
pub mod blob_detector {
use super::*;
pub fn find_blobs(
status_map: &[ChunkStatus],
grid_width: u32,
grid_height: u32,
) -> Vec<SmartBlob> {
let mut heatmap = vec![vec![0.0; grid_width as usize]; grid_height as usize];
for (i, status) in status_map.iter().enumerate() {
if let ChunkStatus::AnomalousEvent(details) = status {
let y = i / grid_width as usize;
let x = i % grid_width as usize;
heatmap[y][x] = details.luminance_score;
}
}
let mut peaks: Vec<Point> = Vec::new();
for y in 0..grid_height as usize {
for x in 0..grid_width as usize {
let heat = heatmap[y][x];
if heat == 0.0 {
continue;
}
let mut is_peak = true;
for dy in -1..=1 {
for dx in -1..=1 {
if dy == 0 && dx == 0 {
continue;
}
let ny = y as i32 + dy;
let nx = x as i32 + dx;
if ny >= 0 && ny < grid_height as i32 && nx >= 0 && nx < grid_width as i32 {
if heatmap[ny as usize][nx as usize] > heat {
is_peak = false;
break;
}
}
}
if !is_peak {
break;
}
}
if is_peak {
peaks.push(Point {
x: x as u32,
y: y as u32,
});
}
}
}
let mut visited = vec![vec![false; grid_width as usize]; grid_height as usize];
let mut blobs: Vec<SmartBlob> = Vec::new();
let mut blob_id_counter = 0;
for peak in peaks {
if visited[peak.y as usize][peak.x as usize] {
continue;
}
let new_blob = grow_blob_from_peak(
peak,
&heatmap,
&mut visited,
blob_id_counter,
status_map,
grid_width,
);
blobs.push(new_blob);
blob_id_counter += 1;
}
blobs
}
const REGION_GROW_THRESHOLD: f64 = 1.0;
fn grow_blob_from_peak(
peak: Point,
heatmap: &[Vec<f64>],
visited: &mut [Vec<bool>],
blob_id: u64,
status_map: &[ChunkStatus],
grid_width: u32,
) -> SmartBlob {
let mut blob_chunks: Vec<Point> = Vec::new();
let mut queue: Vec<Point> = vec![peak];
visited[peak.y as usize][peak.x as usize] = true;
let grid_height = heatmap.len() as i32;
let grid_width_i32 = heatmap[0].len() as i32;
while let Some(current) = queue.pop() {
blob_chunks.push(current);
for (dx, dy) in &[(0, 1), (0, -1), (1, 0), (-1, 0)] {
let nx = current.x as i32 + dx;
let ny = current.y as i32 + dy;
if nx >= 0 && nx < grid_width_i32 && ny >= 0 && ny < grid_height {
let nx_u = nx as usize;
let ny_u = ny as usize;
if !visited[ny_u][nx_u] && heatmap[ny_u][nx_u] >= REGION_GROW_THRESHOLD {
visited[ny_u][nx_u] = true;
queue.push(Point {
x: nx_u as u32,
y: ny_u as u32,
});
}
}
}
}
let mut min_x = u32::MAX;
let mut min_y = u32::MAX;
let mut max_x = 0;
let mut max_y = 0;
let mut total_lum_score = 0.0;
let mut total_col_score = 0.0;
let mut total_hue_score = 0.0;
let mut total_heat = 0.0;
let mut center_x = 0.0;
let mut center_y = 0.0;
for point in &blob_chunks {
min_x = min_x.min(point.x);
min_y = min_y.min(point.y);
max_x = max_x.max(point.x);
max_y = max_y.max(point.y);
let index = (point.y * grid_width + point.x) as usize;
if let ChunkStatus::AnomalousEvent(details) = &status_map[index] {
let heat = details.luminance_score;
total_lum_score += details.luminance_score;
total_col_score += details.color_score;
total_hue_score += details.hue_score;
total_heat += heat;
center_x += point.x as f64 * heat;
center_y += point.y as f64 * heat;
}
}
let num_chunks = blob_chunks.len();
SmartBlob {
id: blob_id,
bounding_box: (Point { x: min_x, y: min_y }, Point { x: max_x, y: max_y }),
chunk_coords: blob_chunks,
size_in_chunks: num_chunks,
average_anomaly: AnomalyDetails {
luminance_score: total_lum_score / num_chunks as f64,
color_score: total_col_score / num_chunks as f64,
hue_score: total_hue_score / num_chunks as f64,
},
center_of_mass: (center_x / total_heat, center_y / total_heat),
}
}
}