1use image::{Rgb, RgbImage};
7use imageproc::geometric_transformations::{Border, Interpolation, rotate_about_center};
8use oar_ocr_core::core::OCRError;
9use oar_ocr_core::processors::BoundingBox;
10use oar_ocr_core::utils::{BBoxCrop, get_rotate_crop_image};
11use serde::{Deserialize, Serialize};
12use std::fmt::Debug;
13use std::sync::Arc;
14
15pub trait EdgeProcessor: Debug + Send + Sync {
17 type Input;
19
20 type Output;
22
23 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError>;
25
26 fn name(&self) -> &str;
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(tag = "type")]
33pub enum EdgeProcessorConfig {
34 TextCropping {
36 #[serde(default = "default_true")]
38 handle_rotation: bool,
39 },
40
41 PerspectiveTransform {
43 target_width: Option<u32>,
45 target_height: Option<u32>,
47 },
48
49 ImageRotation {
51 #[serde(default = "default_true")]
53 auto_rotate: bool,
54 },
55
56 ImageResize {
58 width: u32,
60 height: u32,
62 #[serde(default)]
64 maintain_aspect_ratio: bool,
65 },
66
67 Chain {
69 processors: Vec<EdgeProcessorConfig>,
71 },
72}
73
74fn default_true() -> bool {
75 true
76}
77
78#[derive(Debug)]
80pub struct TextCroppingProcessor {
81 pub(crate) handle_rotation: bool,
82}
83
84impl TextCroppingProcessor {
85 pub fn new(handle_rotation: bool) -> Self {
86 Self { handle_rotation }
87 }
88
89 fn crop_single(&self, image: &RgbImage, bbox: &BoundingBox) -> Result<RgbImage, OCRError> {
91 if self.handle_rotation && bbox.points.len() == 4 {
92 get_rotate_crop_image(image, &bbox.points)
93 } else {
94 BBoxCrop::crop_bounding_box(image, bbox)
96 }
97 }
98}
99
100impl EdgeProcessor for TextCroppingProcessor {
101 type Input = (Arc<RgbImage>, Vec<BoundingBox>);
102 type Output = Vec<Option<Arc<RgbImage>>>;
103
104 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
105 let (image, bboxes) = input;
106
107 let cropped_images: Vec<Option<Arc<RgbImage>>> = bboxes
108 .iter()
109 .map(|bbox| {
110 self.crop_single(&image, bbox)
111 .map(|img| Some(Arc::new(img)))
112 .unwrap_or_else(|_e| {
113 None
115 })
116 })
117 .collect();
118
119 Ok(cropped_images)
120 }
121
122 fn name(&self) -> &str {
123 "TextCropping"
124 }
125}
126
127#[derive(Debug)]
129pub struct ImageRotationProcessor {
130 auto_rotate: bool,
131}
132
133impl ImageRotationProcessor {
134 pub fn new(auto_rotate: bool) -> Self {
135 Self { auto_rotate }
136 }
137}
138
139impl EdgeProcessor for ImageRotationProcessor {
140 type Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>);
141 type Output = Vec<Option<Arc<RgbImage>>>;
142
143 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
144 let (images, angles) = input;
145
146 if !self.auto_rotate {
147 return Ok(images);
148 }
149
150 let rotated_images: Vec<Option<Arc<RgbImage>>> = images
151 .into_iter()
152 .zip(angles.iter())
153 .map(|(img_opt, angle_opt)| {
154 match (img_opt, angle_opt) {
155 (Some(img), Some(angle)) if angle.abs() > 0.1 => {
156 let angle_radians = -angle.to_radians(); let rotated = rotate_about_center(
162 &img,
163 angle_radians,
164 Interpolation::Bilinear,
165 Border::Constant(Rgb([255u8, 255u8, 255u8])), );
169
170 Some(Arc::new(rotated))
171 }
172 (img_opt, _) => img_opt,
173 }
174 })
175 .collect();
176
177 Ok(rotated_images)
178 }
179
180 fn name(&self) -> &str {
181 "ImageRotation"
182 }
183}
184
185#[derive(Debug)]
190pub struct ChainProcessor<T> {
191 processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>,
192}
193
194impl<T> ChainProcessor<T> {
195 pub fn new(processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>) -> Self {
197 Self { processors }
198 }
199}
200
201impl<T> EdgeProcessor for ChainProcessor<T>
202where
203 T: Debug + Send + Sync,
204{
205 type Input = T;
206 type Output = T;
207
208 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
209 if self.processors.is_empty() {
210 return Err(OCRError::ConfigError {
211 message: "Empty processor chain".to_string(),
212 });
213 }
214
215 let mut current = input;
217
218 for processor in &self.processors {
219 current = processor.process(current)?;
220 }
221
222 Ok(current)
223 }
224
225 fn name(&self) -> &str {
226 "Chain"
227 }
228}
229
230type TextCroppingOutput = Box<
232 dyn EdgeProcessor<
233 Input = (Arc<RgbImage>, Vec<BoundingBox>),
234 Output = Vec<Option<Arc<RgbImage>>>,
235 >,
236>;
237
238type ImageRotationOutput = Box<
240 dyn EdgeProcessor<
241 Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>),
242 Output = Vec<Option<Arc<RgbImage>>>,
243 >,
244>;
245
246pub struct EdgeProcessorFactory;
248
249impl EdgeProcessorFactory {
250 pub fn create_text_cropping(handle_rotation: bool) -> TextCroppingOutput {
252 Box::new(TextCroppingProcessor::new(handle_rotation))
253 }
254
255 pub fn create_image_rotation(auto_rotate: bool) -> ImageRotationOutput {
257 Box::new(ImageRotationProcessor::new(auto_rotate))
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_text_cropping_processor_creation() {
267 let processor = TextCroppingProcessor::new(true);
268 assert_eq!(processor.name(), "TextCropping");
269 }
270
271 #[test]
272 fn test_image_rotation_processor_creation() {
273 let processor = ImageRotationProcessor::new(true);
274 assert_eq!(processor.name(), "ImageRotation");
275 }
276
277 #[test]
278 fn test_edge_processor_config_serialization() -> Result<(), Box<dyn std::error::Error>> {
279 let config = EdgeProcessorConfig::TextCropping {
280 handle_rotation: true,
281 };
282
283 let json = serde_json::to_string(&config)?;
284 assert!(json.contains("TextCropping"));
285
286 let deserialized: EdgeProcessorConfig = serde_json::from_str(&json)?;
287 if let EdgeProcessorConfig::TextCropping { handle_rotation } = deserialized {
288 assert!(handle_rotation);
289 } else {
290 panic!("Wrong variant");
291 }
292 Ok(())
293 }
294
295 #[test]
296 fn test_image_rotation_processor_rotates_images() -> Result<(), OCRError> {
297 let processor = ImageRotationProcessor::new(true);
298
299 let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
301
302 let images = vec![Some(img.clone())];
304 let angles = vec![Some(45.0)]; let result = processor.process((images, angles))?;
307
308 assert_eq!(result.len(), 1);
310 assert!(result[0].is_some());
311
312 let Some(rotated) = result[0].as_ref() else {
314 panic!("expected rotated image to be Some");
315 };
316 assert!(rotated.width() >= 10 || rotated.height() >= 10);
318 Ok(())
319 }
320
321 #[test]
322 fn test_image_rotation_processor_skips_small_angles() -> Result<(), OCRError> {
323 let processor = ImageRotationProcessor::new(true);
324
325 let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
326 let images = vec![Some(img.clone())];
327 let angles = vec![Some(0.05)]; let result = processor.process((images, angles))?;
330
331 assert_eq!(result.len(), 1);
333 assert!(result[0].is_some());
334 let Some(output) = result[0].as_ref() else {
335 panic!("expected output image to be Some");
336 };
337 assert_eq!(output.dimensions(), img.dimensions());
338 Ok(())
339 }
340
341 #[test]
342 fn test_image_rotation_processor_disabled() -> Result<(), OCRError> {
343 let processor = ImageRotationProcessor::new(false); let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
346 let images = vec![Some(img.clone())];
347 let angles = vec![Some(45.0)];
348
349 let result = processor.process((images, angles))?;
350
351 assert_eq!(result.len(), 1);
353 assert!(result[0].is_some());
354 let Some(output) = result[0].as_ref() else {
355 panic!("expected output image to be Some");
356 };
357 assert_eq!(output.dimensions(), img.dimensions());
358 Ok(())
359 }
360
361 #[derive(Debug)]
363 struct AddProcessor {
364 value: i32,
365 }
366
367 impl EdgeProcessor for AddProcessor {
368 type Input = i32;
369 type Output = i32;
370
371 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
372 Ok(input + self.value)
373 }
374
375 fn name(&self) -> &str {
376 "Add"
377 }
378 }
379
380 #[derive(Debug)]
382 struct MultiplyProcessor {
383 value: i32,
384 }
385
386 impl EdgeProcessor for MultiplyProcessor {
387 type Input = i32;
388 type Output = i32;
389
390 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
391 Ok(input * self.value)
392 }
393
394 fn name(&self) -> &str {
395 "Multiply"
396 }
397 }
398
399 #[test]
400 fn test_chain_processor_single_processor() -> Result<(), OCRError> {
401 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
402 vec![Box::new(AddProcessor { value: 5 })];
403
404 let chain = ChainProcessor::new(processors);
405 let result = chain.process(10)?;
406
407 assert_eq!(result, 15);
409 Ok(())
410 }
411
412 #[test]
413 fn test_chain_processor_multiple_processors() -> Result<(), OCRError> {
414 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
415 Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 10 }), ];
419
420 let chain = ChainProcessor::new(processors);
421 let result = chain.process(10)?;
422
423 assert_eq!(result, 40);
425 Ok(())
426 }
427
428 #[test]
429 fn test_chain_processor_empty_chain() {
430 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![];
431
432 let chain = ChainProcessor::new(processors);
433 let result = chain.process(10);
434
435 assert!(result.is_err());
437 if let Err(OCRError::ConfigError { message }) = result {
438 assert_eq!(message, "Empty processor chain");
439 } else {
440 panic!("Expected ConfigError");
441 }
442 }
443
444 #[test]
445 fn test_chain_processor_name() {
446 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
447 vec![Box::new(AddProcessor { value: 5 })];
448
449 let chain = ChainProcessor::new(processors);
450 assert_eq!(chain.name(), "Chain");
451 }
452
453 #[test]
454 fn test_chain_processor_order_matters() -> Result<(), OCRError> {
455 let processors1: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
457 Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), ];
460
461 let processors2: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
462 Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 5 }), ];
465
466 let chain1 = ChainProcessor::new(processors1);
467 let chain2 = ChainProcessor::new(processors2);
468
469 let result1 = chain1.process(10)?;
470 let result2 = chain2.process(10)?;
471
472 assert_eq!(result1, 30);
474 assert_eq!(result2, 25);
476 assert_ne!(result1, result2);
478 Ok(())
479 }
480}