use crate::core::geometry::{FCoord, ICoord};
use crate::recognition::tesseract_blob::{ChainOutline, TBlob};
use crate::utils::Result;
#[derive(Debug, Clone, Copy)]
pub struct IntFeature {
pub x: u8,
pub y: u8,
pub theta: u8,
}
impl IntFeature {
pub fn new(x: u8, y: u8, theta: u8) -> Self {
Self { x, y, theta }
}
}
#[derive(Debug, Clone)]
pub struct FeatureExtractionResult {
pub num_bl: usize,
pub num_cn: usize,
pub y_bottom: i32,
pub y_top: i32,
pub width: i32,
pub length: i32,
pub x_mean: i32,
pub y_mean: i32,
pub rx: i32,
pub ry: i32,
}
impl Default for FeatureExtractionResult {
fn default() -> Self {
Self {
num_bl: 0,
num_cn: 0,
y_bottom: 0,
y_top: 0,
width: 0,
length: 0,
x_mean: 0,
y_mean: 0,
rx: 0,
ry: 0,
}
}
}
pub fn extract_features(
blob: &TBlob,
nonlinear_norm: bool,
) -> Result<(Vec<IntFeature>, Vec<IntFeature>, FeatureExtractionResult)> {
let mut bl_features = Vec::new();
let mut cn_features = Vec::new();
let mut fx_result = FeatureExtractionResult::default();
let bbox = blob.bounding_box();
fx_result.y_bottom = bbox.bottom();
fx_result.y_top = bbox.top();
fx_result.width = bbox.width();
let (center, second_moments, length) = compute_moments(blob)?;
fx_result.length = length;
fx_result.x_mean = center.x as i32;
fx_result.y_mean = center.y as i32;
fx_result.rx = second_moments.y as i32;
fx_result.ry = second_moments.x as i32;
for outline in blob.outlines() {
extract_features_from_outline(
outline,
¢er,
&second_moments,
nonlinear_norm,
&mut bl_features,
&mut cn_features,
)?;
}
fx_result.num_bl = bl_features.len();
fx_result.num_cn = cn_features.len();
Ok((bl_features, cn_features, fx_result))
}
fn compute_moments(blob: &TBlob) -> Result<(FCoord, FCoord, i32)> {
let mut total_length = 0i32;
let mut sum_x = 0.0f32;
let mut sum_y = 0.0f32;
let mut sum_x2 = 0.0f32;
let mut sum_y2 = 0.0f32;
for outline in blob.outlines() {
let mut pos = outline.start;
total_length += outline.steps.len() as i32;
for &step in &outline.steps {
let (dx, dy) = step.to_offset();
pos = ICoord::new(pos.x + dx, pos.y + dy);
let x = pos.x as f32;
let y = pos.y as f32;
sum_x += x;
sum_y += y;
sum_x2 += x * x;
sum_y2 += y * y;
}
}
if total_length == 0 {
return Ok((FCoord::zero(), FCoord::zero(), 0));
}
let n = total_length as f32;
let center = FCoord::new(sum_x / n, sum_y / n);
let second_x = (sum_x2 / n) - (center.x * center.x);
let second_y = (sum_y2 / n) - (center.y * center.y);
let second_moments = FCoord::new(second_x.max(0.0), second_y.max(0.0));
Ok((center, second_moments, total_length))
}
fn extract_features_from_outline(
outline: &ChainOutline,
center: &FCoord,
second_moments: &FCoord,
nonlinear_norm: bool,
bl_features: &mut Vec<IntFeature>,
cn_features: &mut Vec<IntFeature>,
) -> Result<()> {
let mut pos = FCoord::new(outline.start.x as f32, outline.start.y as f32);
for &step in &outline.steps {
let (dx, dy) = step.to_offset();
pos = FCoord::new(pos.x + dx as f32, pos.y + dy as f32);
let direction = FCoord::new(dx as f32, dy as f32);
let angle = direction.angle();
let theta = ((angle + std::f32::consts::PI) * 128.0 / std::f32::consts::PI) as u8;
let bl_x = ((pos.x - center.x) + 128.0) as u8;
let bl_y = ((pos.y - center.y) + 128.0) as u8;
bl_features.push(IntFeature::new(bl_x, bl_y, theta));
let scale_x = if second_moments.x > 0.0 {
(51.2 / second_moments.x.sqrt()).min(10.0)
} else {
1.0
};
let scale_y = if second_moments.y > 0.0 {
(51.2 / second_moments.y.sqrt()).min(10.0)
} else {
1.0
};
let cn_x = ((pos.x - center.x) * scale_x + 128.0) as u8;
let cn_y = ((pos.y - center.y) * scale_y + 128.0) as u8;
cn_features.push(IntFeature::new(cn_x, cn_y, theta));
}
Ok(())
}