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 rayon::prelude::*;
12use serde::{Deserialize, Serialize};
13use std::fmt::Debug;
14use std::sync::Arc;
15
16pub trait EdgeProcessor: Debug + Send + Sync {
18 type Input;
20
21 type Output;
23
24 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError>;
26
27 fn name(&self) -> &str;
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(tag = "type")]
34pub enum EdgeProcessorConfig {
35 TextCropping {
37 #[serde(default = "default_true")]
39 handle_rotation: bool,
40 },
41
42 PerspectiveTransform {
44 target_width: Option<u32>,
46 target_height: Option<u32>,
48 },
49
50 ImageRotation {
52 #[serde(default = "default_true")]
54 auto_rotate: bool,
55 },
56
57 ImageResize {
59 width: u32,
61 height: u32,
63 #[serde(default)]
65 maintain_aspect_ratio: bool,
66 },
67
68 Chain {
70 processors: Vec<EdgeProcessorConfig>,
72 },
73}
74
75fn default_true() -> bool {
76 true
77}
78
79#[derive(Debug)]
81pub struct TextCroppingProcessor {
82 pub(crate) handle_rotation: bool,
83}
84
85const PARALLEL_CROP_MIN_REGIONS: usize = 16;
88
89impl TextCroppingProcessor {
90 pub fn new(handle_rotation: bool) -> Self {
91 Self { handle_rotation }
92 }
93
94 fn crop_single(&self, image: &RgbImage, bbox: &BoundingBox) -> Result<RgbImage, OCRError> {
96 if self.handle_rotation && bbox.points.len() == 4 {
97 get_rotate_crop_image(image, &bbox.points)
98 } else {
99 BBoxCrop::crop_bounding_box(image, bbox)
101 }
102 }
103
104 fn crop_optional(&self, image: &RgbImage, bbox: &BoundingBox) -> Option<Arc<RgbImage>> {
105 self.crop_single(image, bbox).ok().map(Arc::new)
106 }
107}
108
109impl EdgeProcessor for TextCroppingProcessor {
110 type Input = (Arc<RgbImage>, Vec<BoundingBox>);
111 type Output = Vec<Option<Arc<RgbImage>>>;
112
113 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
114 let (image, bboxes) = input;
115
116 let cropped_images = if bboxes.len() >= PARALLEL_CROP_MIN_REGIONS {
117 bboxes
120 .par_iter()
121 .map(|bbox| self.crop_optional(&image, bbox))
122 .collect()
123 } else {
124 bboxes
125 .iter()
126 .map(|bbox| self.crop_optional(&image, bbox))
127 .collect()
128 };
129
130 Ok(cropped_images)
131 }
132
133 fn name(&self) -> &str {
134 "TextCropping"
135 }
136}
137
138#[derive(Debug)]
140pub struct ImageRotationProcessor {
141 auto_rotate: bool,
142}
143
144impl ImageRotationProcessor {
145 pub fn new(auto_rotate: bool) -> Self {
146 Self { auto_rotate }
147 }
148}
149
150impl EdgeProcessor for ImageRotationProcessor {
151 type Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>);
152 type Output = Vec<Option<Arc<RgbImage>>>;
153
154 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
155 let (images, angles) = input;
156
157 if !self.auto_rotate {
158 return Ok(images);
159 }
160
161 let rotated_images: Vec<Option<Arc<RgbImage>>> = images
162 .into_iter()
163 .zip(angles.iter())
164 .map(|(img_opt, angle_opt)| {
165 match (img_opt, angle_opt) {
166 (Some(img), Some(angle)) if angle.abs() > 0.1 => {
167 let angle_radians = -angle.to_radians(); let rotated = rotate_about_center(
173 &img,
174 angle_radians,
175 Interpolation::Bilinear,
176 Border::Constant(Rgb([255u8, 255u8, 255u8])), );
180
181 Some(Arc::new(rotated))
182 }
183 (img_opt, _) => img_opt,
184 }
185 })
186 .collect();
187
188 Ok(rotated_images)
189 }
190
191 fn name(&self) -> &str {
192 "ImageRotation"
193 }
194}
195
196#[derive(Debug)]
201pub struct ChainProcessor<T> {
202 processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>,
203}
204
205impl<T> ChainProcessor<T> {
206 pub fn new(processors: Vec<Box<dyn EdgeProcessor<Input = T, Output = T>>>) -> Self {
208 Self { processors }
209 }
210}
211
212impl<T> EdgeProcessor for ChainProcessor<T>
213where
214 T: Debug + Send + Sync,
215{
216 type Input = T;
217 type Output = T;
218
219 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
220 if self.processors.is_empty() {
221 return Err(OCRError::ConfigError {
222 message: "Empty processor chain".to_string(),
223 });
224 }
225
226 let mut current = input;
228
229 for processor in &self.processors {
230 current = processor.process(current)?;
231 }
232
233 Ok(current)
234 }
235
236 fn name(&self) -> &str {
237 "Chain"
238 }
239}
240
241type TextCroppingOutput = Box<
243 dyn EdgeProcessor<
244 Input = (Arc<RgbImage>, Vec<BoundingBox>),
245 Output = Vec<Option<Arc<RgbImage>>>,
246 >,
247>;
248
249type ImageRotationOutput = Box<
251 dyn EdgeProcessor<
252 Input = (Vec<Option<Arc<RgbImage>>>, Vec<Option<f32>>),
253 Output = Vec<Option<Arc<RgbImage>>>,
254 >,
255>;
256
257pub struct EdgeProcessorFactory;
259
260impl EdgeProcessorFactory {
261 pub fn create_text_cropping(handle_rotation: bool) -> TextCroppingOutput {
263 Box::new(TextCroppingProcessor::new(handle_rotation))
264 }
265
266 pub fn create_image_rotation(auto_rotate: bool) -> ImageRotationOutput {
268 Box::new(ImageRotationProcessor::new(auto_rotate))
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_text_cropping_processor_creation() {
278 let processor = TextCroppingProcessor::new(true);
279 assert_eq!(processor.name(), "TextCropping");
280 }
281
282 #[test]
283 fn test_parallel_text_cropping_preserves_detection_order() -> Result<(), OCRError> {
284 let processor = TextCroppingProcessor::new(true);
285 let image = Arc::new(RgbImage::from_fn(64, 4, |x, _| Rgb([(x / 4) as u8, 0, 0])));
286 let bboxes = (0..PARALLEL_CROP_MIN_REGIONS)
287 .map(|index| {
288 let x = (index * 4) as f32;
289 BoundingBox::from_coords(x, 0.0, x + 4.0, 4.0)
290 })
291 .collect();
292
293 let crops = processor.process((image, bboxes))?;
294
295 assert_eq!(crops.len(), PARALLEL_CROP_MIN_REGIONS);
296 for (index, crop) in crops.iter().enumerate() {
297 let crop = crop.as_ref().expect("crop should succeed");
298 assert_eq!(crop.dimensions(), (4, 4));
299 assert_eq!(crop.get_pixel(0, 0), &Rgb([index as u8, 0, 0]));
300 }
301 Ok(())
302 }
303
304 #[test]
305 fn test_image_rotation_processor_creation() {
306 let processor = ImageRotationProcessor::new(true);
307 assert_eq!(processor.name(), "ImageRotation");
308 }
309
310 #[test]
311 fn test_edge_processor_config_serialization() -> Result<(), Box<dyn std::error::Error>> {
312 let config = EdgeProcessorConfig::TextCropping {
313 handle_rotation: true,
314 };
315
316 let json = serde_json::to_string(&config)?;
317 assert!(json.contains("TextCropping"));
318
319 let deserialized: EdgeProcessorConfig = serde_json::from_str(&json)?;
320 if let EdgeProcessorConfig::TextCropping { handle_rotation } = deserialized {
321 assert!(handle_rotation);
322 } else {
323 panic!("Wrong variant");
324 }
325 Ok(())
326 }
327
328 #[test]
329 fn test_image_rotation_processor_rotates_images() -> Result<(), OCRError> {
330 let processor = ImageRotationProcessor::new(true);
331
332 let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
334
335 let images = vec![Some(img.clone())];
337 let angles = vec![Some(45.0)]; let result = processor.process((images, angles))?;
340
341 assert_eq!(result.len(), 1);
343 assert!(result[0].is_some());
344
345 let Some(rotated) = result[0].as_ref() else {
347 panic!("expected rotated image to be Some");
348 };
349 assert!(rotated.width() >= 10 || rotated.height() >= 10);
351 Ok(())
352 }
353
354 #[test]
355 fn test_image_rotation_processor_skips_small_angles() -> Result<(), OCRError> {
356 let processor = ImageRotationProcessor::new(true);
357
358 let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
359 let images = vec![Some(img.clone())];
360 let angles = vec![Some(0.05)]; let result = processor.process((images, angles))?;
363
364 assert_eq!(result.len(), 1);
366 assert!(result[0].is_some());
367 let Some(output) = result[0].as_ref() else {
368 panic!("expected output image to be Some");
369 };
370 assert_eq!(output.dimensions(), img.dimensions());
371 Ok(())
372 }
373
374 #[test]
375 fn test_image_rotation_processor_disabled() -> Result<(), OCRError> {
376 let processor = ImageRotationProcessor::new(false); let img = Arc::new(RgbImage::from_pixel(10, 10, Rgb([255u8, 255u8, 255u8])));
379 let images = vec![Some(img.clone())];
380 let angles = vec![Some(45.0)];
381
382 let result = processor.process((images, angles))?;
383
384 assert_eq!(result.len(), 1);
386 assert!(result[0].is_some());
387 let Some(output) = result[0].as_ref() else {
388 panic!("expected output image to be Some");
389 };
390 assert_eq!(output.dimensions(), img.dimensions());
391 Ok(())
392 }
393
394 #[derive(Debug)]
396 struct AddProcessor {
397 value: i32,
398 }
399
400 impl EdgeProcessor for AddProcessor {
401 type Input = i32;
402 type Output = i32;
403
404 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
405 Ok(input + self.value)
406 }
407
408 fn name(&self) -> &str {
409 "Add"
410 }
411 }
412
413 #[derive(Debug)]
415 struct MultiplyProcessor {
416 value: i32,
417 }
418
419 impl EdgeProcessor for MultiplyProcessor {
420 type Input = i32;
421 type Output = i32;
422
423 fn process(&self, input: Self::Input) -> Result<Self::Output, OCRError> {
424 Ok(input * self.value)
425 }
426
427 fn name(&self) -> &str {
428 "Multiply"
429 }
430 }
431
432 #[test]
433 fn test_chain_processor_single_processor() -> Result<(), OCRError> {
434 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
435 vec![Box::new(AddProcessor { value: 5 })];
436
437 let chain = ChainProcessor::new(processors);
438 let result = chain.process(10)?;
439
440 assert_eq!(result, 15);
442 Ok(())
443 }
444
445 #[test]
446 fn test_chain_processor_multiple_processors() -> Result<(), OCRError> {
447 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
448 Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 10 }), ];
452
453 let chain = ChainProcessor::new(processors);
454 let result = chain.process(10)?;
455
456 assert_eq!(result, 40);
458 Ok(())
459 }
460
461 #[test]
462 fn test_chain_processor_empty_chain() {
463 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![];
464
465 let chain = ChainProcessor::new(processors);
466 let result = chain.process(10);
467
468 assert!(result.is_err());
470 if let Err(OCRError::ConfigError { message }) = result {
471 assert_eq!(message, "Empty processor chain");
472 } else {
473 panic!("Expected ConfigError");
474 }
475 }
476
477 #[test]
478 fn test_chain_processor_name() {
479 let processors: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> =
480 vec![Box::new(AddProcessor { value: 5 })];
481
482 let chain = ChainProcessor::new(processors);
483 assert_eq!(chain.name(), "Chain");
484 }
485
486 #[test]
487 fn test_chain_processor_order_matters() -> Result<(), OCRError> {
488 let processors1: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
490 Box::new(AddProcessor { value: 5 }), Box::new(MultiplyProcessor { value: 2 }), ];
493
494 let processors2: Vec<Box<dyn EdgeProcessor<Input = i32, Output = i32>>> = vec![
495 Box::new(MultiplyProcessor { value: 2 }), Box::new(AddProcessor { value: 5 }), ];
498
499 let chain1 = ChainProcessor::new(processors1);
500 let chain2 = ChainProcessor::new(processors2);
501
502 let result1 = chain1.process(10)?;
503 let result2 = chain2.process(10)?;
504
505 assert_eq!(result1, 30);
507 assert_eq!(result2, 25);
509 assert_ne!(result1, result2);
511 Ok(())
512 }
513}