oar_ocr/core/traits/granular.rs
1//! Granular traits for composable predictor components.
2//!
3//! This module provides granular traits that separate the concerns of the StandardPredictor
4//! trait, making it easier to compose, test, and extend individual pipeline components.
5//!
6//! The design focuses on practical composability and clean separation of concerns:
7//! - **ImageReader**: Handles I/O operations (loading images from files/memory)
8//! - **Preprocessor**: Handles image preprocessing (resize, normalize, tensor conversion)
9//! - **InferenceEngine**: Handles model inference (ONNX, TensorRT, etc.)
10//! - **Postprocessor**: Handles result processing (decoding, formatting, filtering)
11//!
12//! # Architecture
13//!
14//! ```text
15//! ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────-┐
16//! │ImageReader │───▶│Preprocessor │───▶│InferenceEng │───▶│Postprocessor │
17//! │ │ │ │ │ │ │ │
18//! │• read_images│ │• preprocess │ │• infer │ │• postprocess │
19//! │• validate │ │• validate │ │• engine_info│ │• empty_result│
20//! └─────────────┘ └─────────────┘ └─────────────┘ └─────────────-┘
21//! ```
22//!
23//! # Examples
24//!
25//! ```rust,no_run
26//! use oar_ocr::core::traits::granular::Preprocessor;
27//! use image::RgbImage;
28//!
29//! // Example of how you would implement and use a custom preprocessor
30//! // (This is a conceptual example - actual implementations would be in separate modules)
31//!
32//! # #[derive(Debug)]
33//! # struct MyPreprocessor;
34//! # #[derive(Debug)]
35//! # struct MyConfig { brightness_factor: f32 }
36//! # impl Preprocessor for MyPreprocessor {
37//! # type Config = MyConfig;
38//! # type Output = Vec<RgbImage>;
39//! # fn preprocess(&self, images: Vec<RgbImage>, config: Option<&Self::Config>) -> Result<Self::Output, oar_ocr::core::OCRError> {
40//! # Ok(images)
41//! # }
42//! # fn preprocessing_info(&self) -> String { "MyPreprocessor".to_string() }
43//! # }
44//!
45//! let preprocessor = MyPreprocessor;
46//! let test_image = RgbImage::new(32, 32);
47//! let images = vec![test_image];
48//! let config = MyConfig { brightness_factor: 1.5 };
49//!
50//! // Apply preprocessing
51//! let result = preprocessor.preprocess(images, Some(&config));
52//! assert!(result.is_ok());
53//! ```
54
55use crate::core::traits::StandardPredictor;
56use crate::core::{BatchData, OCRError};
57use image::RgbImage;
58use std::fmt::Debug;
59
60/// Trait for image reading and I/O operations.
61///
62/// This trait handles loading images from various sources (file paths, URLs, memory)
63/// and converting them to a standard RGB format for processing.
64pub trait ImageReader: Send + Sync + Debug {
65 /// Read images from file paths.
66 ///
67 /// # Arguments
68 ///
69 /// * `paths` - Iterator over file paths to read
70 ///
71 /// # Returns
72 ///
73 /// Vector of loaded RGB images or an error
74 fn read_images<'a>(
75 &self,
76 paths: impl Iterator<Item = &'a str>,
77 ) -> Result<Vec<RgbImage>, OCRError>;
78
79 /// Validate that images can be read from the given paths.
80 ///
81 /// # Arguments
82 ///
83 /// * `paths` - Iterator over file paths to validate
84 ///
85 /// # Returns
86 ///
87 /// Result indicating success or validation error
88 fn validate_paths<'a>(&self, paths: impl Iterator<Item = &'a str>) -> Result<(), OCRError> {
89 // Default implementation: try to read first path
90 if let Some(path) = paths.into_iter().next() {
91 let images = self.read_images(std::iter::once(path))?;
92 if images.is_empty() {
93 return Err(OCRError::InvalidInput {
94 message: "Failed to read any images".to_string(),
95 });
96 }
97 }
98 Ok(())
99 }
100}
101
102/// Trait for image preprocessing operations.
103///
104/// This trait handles transforming raw images into the format required by
105/// the inference engine (resizing, normalization, tensor conversion, etc.).
106pub trait Preprocessor: Send + Sync + Debug {
107 /// Configuration type for preprocessing
108 type Config: Send + Sync + Debug;
109
110 /// Output type after preprocessing
111 type Output: Send + Sync + Debug;
112
113 /// Preprocess input images into inference-ready format.
114 ///
115 /// # Arguments
116 ///
117 /// * `images` - Input images to preprocess
118 /// * `config` - Optional configuration for preprocessing
119 ///
120 /// # Returns
121 ///
122 /// Preprocessed output ready for inference or an error
123 fn preprocess(
124 &self,
125 images: Vec<RgbImage>,
126 config: Option<&Self::Config>,
127 ) -> Result<Self::Output, OCRError>;
128
129 /// Get information about the preprocessing requirements.
130 ///
131 /// # Returns
132 ///
133 /// String describing preprocessing requirements (input size, format, etc.)
134 fn preprocessing_info(&self) -> String {
135 "Generic preprocessing".to_string()
136 }
137
138 /// Validate that the input images are suitable for preprocessing.
139 ///
140 /// # Arguments
141 ///
142 /// * `images` - Input images to validate
143 ///
144 /// # Returns
145 ///
146 /// Result indicating success or validation error
147 fn validate_input(&self, images: &[RgbImage]) -> Result<(), OCRError> {
148 if images.is_empty() {
149 return Err(OCRError::InvalidInput {
150 message: "No images provided for preprocessing".to_string(),
151 });
152 }
153 Ok(())
154 }
155}
156
157/// Trait for inference engine operations.
158///
159/// This trait handles running the actual model inference, whether through
160/// ONNX Runtime, TensorRT, PyTorch, or other backends.
161pub trait InferenceEngine: Send + Sync + Debug {
162 /// Input type for inference (typically a tensor)
163 type Input: Send + Sync + Debug;
164
165 /// Output type from inference (typically a tensor)
166 type Output: Send + Sync + Debug;
167
168 /// Perform inference on preprocessed input.
169 ///
170 /// # Arguments
171 ///
172 /// * `input` - Preprocessed input ready for inference
173 ///
174 /// # Returns
175 ///
176 /// Raw inference output or an error
177 fn infer(&self, input: &Self::Input) -> Result<Self::Output, OCRError>;
178
179 /// Get information about the inference engine.
180 ///
181 /// # Returns
182 ///
183 /// String describing the inference engine (model type, backend, etc.)
184 fn engine_info(&self) -> String;
185
186 /// Validate that the input is suitable for inference.
187 ///
188 /// # Arguments
189 ///
190 /// * `input` - Input to validate
191 ///
192 /// # Returns
193 ///
194 /// Result indicating success or validation error
195 fn validate_inference_input(&self, _input: &Self::Input) -> Result<(), OCRError> {
196 // Default implementation - basic validation
197 Ok(())
198 }
199}
200
201/// Trait for postprocessing operations.
202///
203/// This trait handles converting raw inference outputs into meaningful results
204/// (decoding, filtering, formatting, etc.).
205pub trait Postprocessor: Send + Sync + Debug {
206 /// Configuration type for postprocessing
207 type Config: Send + Sync + Debug;
208
209 /// Input type from inference engine
210 type InferenceOutput: Send + Sync + Debug;
211
212 /// Preprocessed data type for context
213 type PreprocessOutput: Send + Sync + Debug;
214
215 /// Final result type after postprocessing
216 type Result: Send + Sync + Debug;
217
218 /// Postprocess inference output into final results.
219 ///
220 /// # Arguments
221 ///
222 /// * `inference_output` - Raw output from inference engine
223 /// * `preprocess_output` - Optional preprocessed data for context
224 /// * `batch_data` - Batch metadata
225 /// * `raw_images` - Original input images
226 /// * `config` - Optional configuration for postprocessing
227 ///
228 /// # Returns
229 ///
230 /// Final processed result or an error
231 fn postprocess(
232 &self,
233 inference_output: Self::InferenceOutput,
234 preprocess_output: Option<&Self::PreprocessOutput>,
235 batch_data: &BatchData,
236 raw_images: Vec<RgbImage>,
237 config: Option<&Self::Config>,
238 ) -> crate::core::OcrResult<Self::Result>;
239
240 /// Create an empty result for when no input is provided.
241 ///
242 /// # Returns
243 ///
244 /// Empty result instance
245 fn empty_result(&self) -> Result<Self::Result, OCRError>;
246
247 /// Get information about the postprocessing operations.
248 ///
249 /// # Returns
250 ///
251 /// String describing postprocessing operations
252 fn postprocessing_info(&self) -> String {
253 "Generic postprocessing".to_string()
254 }
255}
256
257/// A modular predictor that composes granular components.
258///
259/// This struct demonstrates how to build a complete predictor using the granular traits.
260/// It provides the same interface as StandardPredictor but with composable components.
261#[derive(Debug)]
262pub struct ModularPredictor<R, P, I, O> {
263 /// Image reader component
264 pub image_reader: R,
265 /// Preprocessor component
266 pub preprocessor: P,
267 /// Inference engine component
268 pub inference_engine: I,
269 /// Postprocessor component
270 pub postprocessor: O,
271}
272
273impl<R, P, I, O> ModularPredictor<R, P, I, O>
274where
275 R: ImageReader,
276 P: Preprocessor,
277 I: InferenceEngine<Input = P::Output>,
278 O: Postprocessor<InferenceOutput = I::Output, PreprocessOutput = P::Output>,
279{
280 /// Create a new modular predictor with the given components.
281 pub fn new(image_reader: R, preprocessor: P, inference_engine: I, postprocessor: O) -> Self {
282 Self {
283 image_reader,
284 preprocessor,
285 inference_engine,
286 postprocessor,
287 }
288 }
289}
290
291impl<R, P, I, O> StandardPredictor for ModularPredictor<R, P, I, O>
292where
293 R: ImageReader,
294 P: Preprocessor,
295 I: InferenceEngine<Input = P::Output>,
296 O: Postprocessor<InferenceOutput = I::Output, PreprocessOutput = P::Output, Config = P::Config>,
297{
298 type Config = P::Config;
299 type Result = O::Result;
300 type PreprocessOutput = P::Output;
301 type InferenceOutput = I::Output;
302
303 fn read_images<'a>(
304 &self,
305 paths: impl Iterator<Item = &'a str>,
306 ) -> Result<Vec<RgbImage>, OCRError> {
307 self.image_reader.read_images(paths)
308 }
309
310 fn preprocess(
311 &self,
312 images: Vec<RgbImage>,
313 config: Option<&Self::Config>,
314 ) -> Result<Self::PreprocessOutput, OCRError> {
315 self.preprocessor.preprocess(images, config)
316 }
317
318 fn infer(&self, input: &Self::PreprocessOutput) -> Result<Self::InferenceOutput, OCRError> {
319 self.inference_engine.infer(input)
320 }
321
322 fn postprocess(
323 &self,
324 output: Self::InferenceOutput,
325 preprocessed: &Self::PreprocessOutput,
326 batch_data: &BatchData,
327 raw_images: Vec<RgbImage>,
328 config: Option<&Self::Config>,
329 ) -> crate::core::OcrResult<Self::Result> {
330 self.postprocessor
331 .postprocess(output, Some(preprocessed), batch_data, raw_images, config)
332 }
333
334 fn empty_result(&self) -> crate::core::OcrResult<Self::Result> {
335 self.postprocessor.empty_result()
336 }
337}