use image::{Rgb, RgbImage};
use imageproc::geometric_transformations::{Interpolation, rotate_about_center};
use oar_ocr_core::core::OCRError;
use oar_ocr_core::processors::BoundingBox;
use oar_ocr_core::utils::BBoxCrop;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::sync::Arc;
pub trait EdgeProcessor: Debug + Send + Sync {
type Input;
type Output;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError>;
fn name(&self) -> &str;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum EdgeProcessorConfig {
TextCropping {
#[serde(default = "default_true")]
handle_rotation: bool,
},
PerspectiveTransform {
target_width: Option<u32>,
target_height: Option<u32>,
},
ImageRotation {
#[serde(default = "default_true")]
auto_rotate: bool,
},
ImageResize {
width: u32,
height: u32,
#[serde(default)]
maintain_aspect_ratio: bool,
},
Chain {
processors: Vec<EdgeProcessorConfig>,
},
}
fn default_true() -> bool {
true
}
#[derive(Debug)]
pub struct TextCroppingProcessor {
pub(crate) handle_rotation: bool,
}
impl TextCroppingProcessor {
pub fn new(handle_rotation: bool) -> Self {
Self { handle_rotation }
}
fn crop_single(&self, image: &RgbImage, bbox: &BoundingBox) -> Result<RgbImage, OCRError> {
if self.handle_rotation && bbox.points.len() == 4 {
BBoxCrop::crop_rotated_bounding_box(image, bbox)
} else {
BBoxCrop::crop_bounding_box(image, bbox)
}
}
}
impl EdgeProcessor for TextCroppingProcessor {
type Input = (Arc<RgbImage>, Vec<BoundingBox>);
type Output = Vec<Option<Arc<RgbImage>>>;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
let (image, bboxes) = input;
let cropped_images: Vec<Option<Arc<RgbImage>>> = bboxes
.iter()
.map(|bbox| {
self.crop_single(&image, bbox)
.map(|img| Some(Arc::new(img)))
.unwrap_or_else(|_e| {
None
})
})
.collect();
Ok(cropped_images)
}
fn name(&self) -> &str {
"TextCropping"
}
}
#[derive(Debug)]
pub struct ImageRotationProcessor {
auto_rotate: bool,
}
impl ImageRotationProcessor {
pub fn new(auto_rotate: bool) -> Self {
Self { auto_rotate }
}
}
impl EdgeProcessor for ImageRotationProcessor {
type Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>);
type Output = Vec<Option<Arc<RgbImage>>>;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
let (images, angles) = input;
if !self.auto_rotate {
return Ok(images);
}
let rotated_images: Vec<Option<Arc<RgbImage>>> = images
.into_iter()
.zip(angles.iter())
.map(|(img_opt, angle_opt)| {
match (img_opt, angle_opt) {
(Some(img), Some(angle)) if angle.abs() > 0.1 => {
let angle_radians = -angle.to_radians();
let rotated = rotate_about_center(
&img,
angle_radians,
Interpolation::Bilinear,
Rgb([255u8, 255u8, 255u8]), );
Some(Arc::new(rotated))
}
(img_opt, _) => img_opt,
}
})
.collect();
Ok(rotated_images)
}
fn name(&self) -> &str {
"ImageRotation"
}
}
#[derive(Debug)]
pub struct ChainProcessor<T> {
processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>,
}
impl<T> ChainProcessor<T> {
pub fn new(processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>) -> Self {
Self { processors }
}
}
impl<T> EdgeProcessor for ChainProcessor<T>
where
T: Debug + Send + Sync,
{
type Input = T;
type Output = T;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
if self.processors.is_empty() {
return Err(OCRError::ConfigError {
message: "Empty processor chain".to_string(),
});
}
let mut current = input;
for processor in &self.processors {
current = processor.process(current)?;
}
Ok(current)
}
fn name(&self) -> &str {
"Chain"
}
}
type TextCroppingOutput = Box<
dyn EdgeProcessor<
Input = (Arc<RgbImage>, Vec<BoundingBox>),
Output = Vec<Option<Arc<RgbImage>>>,
>,
>;
type ImageRotationOutput = Box<
dyn EdgeProcessor<
Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>),
Output = Vec<Option<Arc<RgbImage>>>,
>,
>;
pub struct EdgeProcessorFactory;
impl EdgeProcessorFactory {
pub fn create_text_cropping(handle_rotation: bool) -> TextCroppingOutput {
Box::new(TextCroppingProcessor::new(handle_rotation))
}
pub fn create_image_rotation(auto_rotate: bool) -> ImageRotationOutput {
Box::new(ImageRotationProcessor::new(auto_rotate))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_cropping_processor_creation() {
let processor = TextCroppingProcessor::new(true);
assert_eq!(processor.name(), "TextCropping");
}
#[test]
fn test_image_rotation_processor_creation() {
let processor = ImageRotationProcessor::new(true);
assert_eq!(processor.name(), "ImageRotation");
}
#[test]
fn test_edge_processor_config_serialization() -> Result<(), Box<dyn std::error::Error>> {
let config = EdgeProcessorConfig::TextCropping {
handle_rotation: true,
};
let json = serde_json::to_string(&config)?;
assert!(json.contains("TextCropping"));
let deserialized: EdgeProcessorConfig = serde_json::from_str(&json)?;
if let EdgeProcessorConfig::TextCropping { handle_rotation } = deserialized {
assert!(handle_rotation);
} else {
panic!("Wrong variant");
}
Ok(())
}
#[test]
fn test_image_rotation_processor_rotates_images() -> Result<(), OCRError> {
let processor = ImageRotationProcessor::new(true);
let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
let images = vec![Some(img.clone())];
let angles = vec![Some(45.0)];
let result = processor.process((images, angles))?;
assert_eq!(result.len(), 1);
assert!(result[0].is_some());
let Some(rotated) = result[0].as_ref() else {
panic!("expected rotated image to be Some");
};
assert!(rotated.width() >= 10 || rotated.height() >= 10);
Ok(())
}
#[test]
fn test_image_rotation_processor_skips_small_angles() -> Result<(), OCRError> {
let processor = ImageRotationProcessor::new(true);
let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
let images = vec![Some(img.clone())];
let angles = vec![Some(0.05)];
let result = processor.process((images, angles))?;
assert_eq!(result.len(), 1);
assert!(result[0].is_some());
let Some(output) = result[0].as_ref() else {
panic!("expected output image to be Some");
};
assert_eq!(output.dimensions(), img.dimensions());
Ok(())
}
#[test]
fn test_image_rotation_processor_disabled() -> Result<(), OCRError> {
let processor = ImageRotationProcessor::new(false);
let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
let images = vec![Some(img.clone())];
let angles = vec![Some(45.0)];
let result = processor.process((images, angles))?;
assert_eq!(result.len(), 1);
assert!(result[0].is_some());
let Some(output) = result[0].as_ref() else {
panic!("expected output image to be Some");
};
assert_eq!(output.dimensions(), img.dimensions());
Ok(())
}
#[derive(Debug)]
struct AddProcessor {
value: i32,
}
impl EdgeProcessor for AddProcessor {
type Input = i32;
type Output = i32;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
Ok(input + self.value)
}
fn name(&self) -> &str {
"Add"
}
}
#[derive(Debug)]
struct MultiplyProcessor {
value: i32,
}
impl EdgeProcessor for MultiplyProcessor {
type Input = i32;
type Output = i32;
fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
Ok(input * self.value)
}
fn name(&self) -> &str {
"Multiply"
}
}
#[test]
fn test_chain_processor_single_processor() -> Result<(), OCRError> {
let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
vec![Box::new(AddProcessor { value: 5 })];
let chain = ChainProcessor::new(processors);
let result = chain.process(10)?;
assert_eq!(result, 15);
Ok(())
}
#[test]
fn test_chain_processor_multiple_processors() -> Result<(), OCRError> {
let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 10 }), ];
let chain = ChainProcessor::new(processors);
let result = chain.process(10)?;
assert_eq!(result, 40);
Ok(())
}
#[test]
fn test_chain_processor_empty_chain() {
let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![];
let chain = ChainProcessor::new(processors);
let result = chain.process(10);
assert!(result.is_err());
if let Err(OCRError::ConfigError { message }) = result {
assert_eq!(message, "Empty processor chain");
} else {
panic!("Expected ConfigError");
}
}
#[test]
fn test_chain_processor_name() {
let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
vec![Box::new(AddProcessor { value: 5 })];
let chain = ChainProcessor::new(processors);
assert_eq!(chain.name(), "Chain");
}
#[test]
fn test_chain_processor_order_matters() -> Result<(), OCRError> {
let processors1: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), ];
let processors2: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 5 }), ];
let chain1 = ChainProcessor::new(processors1);
let chain2 = ChainProcessor::new(processors2);
let result1 = chain1.process(10)?;
let result2 = chain2.process(10)?;
assert_eq!(result1, 30);
assert_eq!(result2, 25);
assert_ne!(result1, result2);
Ok(())
}
}