oar_ocr_core/core/traits/standard.rs
1//! Core traits for sampling and image reading.
2//!
3//! This module provides traits used throughout the OCR pipeline for batch
4//! sampling and image I/O operations.
5
6use crate::core::errors::OCRError;
7use image::RgbImage;
8use std::path::Path;
9
10/// Trait for sampling data into batches.
11///
12/// This trait defines the interface for sampling data into batches for processing.
13pub trait Sampler<T> {
14 /// The type of batch data produced by this sampler.
15 type BatchData;
16
17 /// Samples the given data into batches.
18 ///
19 /// # Arguments
20 ///
21 /// * `data` - The data to sample.
22 ///
23 /// # Returns
24 ///
25 /// A vector of batch data.
26 fn sample(&self, data: Vec<T>) -> Vec<Self::BatchData>;
27
28 /// Samples the given slice of data into batches.
29 ///
30 /// # Arguments
31 ///
32 /// * `data` - The slice of data to sample.
33 ///
34 /// # Returns
35 ///
36 /// A vector of batch data.
37 ///
38 /// # Constraints
39 ///
40 /// * `T` must implement Clone.
41 fn sample_slice(&self, data: &[T]) -> Vec<Self::BatchData>
42 where
43 T: Clone,
44 {
45 self.sample(data.to_vec())
46 }
47
48 /// Samples the given iterator of data into batches.
49 ///
50 /// # Arguments
51 ///
52 /// * `data` - The iterator of data to sample.
53 ///
54 /// # Returns
55 ///
56 /// A vector of batch data.
57 ///
58 /// # Constraints
59 ///
60 /// * `I` must implement IntoIterator<Item = T>.
61 fn sample_iter<I>(&self, data: I) -> Vec<Self::BatchData>
62 where
63 I: IntoIterator<Item = T>,
64 {
65 self.sample(data.into_iter().collect())
66 }
67}
68
69/// Trait for reading images.
70///
71/// This trait defines the interface for reading images from paths.
72pub trait ImageReader {
73 /// The error type of this image reader.
74 type Error;
75
76 /// Applies the image reader to the given paths.
77 ///
78 /// # Arguments
79 ///
80 /// * `imgs` - An iterator of paths to the images to read.
81 ///
82 /// # Returns
83 ///
84 /// A Result containing a vector of RGB images or an error.
85 ///
86 /// # Constraints
87 ///
88 /// * `P` must implement `AsRef<Path>` + Send + Sync.
89 fn apply<P: AsRef<Path> + Send + Sync>(
90 &self,
91 imgs: impl IntoIterator<Item = P>,
92 ) -> Result<Vec<RgbImage>, Self::Error>;
93
94 /// Reads a single image from the given path.
95 ///
96 /// # Arguments
97 ///
98 /// * `img_path` - The path to the image to read.
99 ///
100 /// # Returns
101 ///
102 /// A Result containing the RGB image or an error.
103 ///
104 /// # Constraints
105 ///
106 /// * `P` must implement `AsRef<Path>` + Send + Sync.
107 fn read_single<P: AsRef<Path> + Send + Sync>(
108 &self,
109 img_path: P,
110 ) -> Result<RgbImage, Self::Error>
111 where
112 Self::Error: From<OCRError>,
113 {
114 let mut results = self.apply(std::iter::once(img_path))?;
115 results.pop().ok_or_else(|| {
116 // Create a proper error instead of panicking
117 OCRError::invalid_input("ImageReader::apply returned empty result for single image")
118 .into()
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::core::OCRError;
127 use image::RgbImage;
128 use std::path::Path;
129
130 /// Mock ImageReader that always returns empty results to test error handling
131 struct MockEmptyImageReader;
132
133 impl ImageReader for MockEmptyImageReader {
134 type Error = OCRError;
135
136 fn apply<P: AsRef<Path> + Send + Sync>(
137 &self,
138 _imgs: impl IntoIterator<Item = P>,
139 ) -> Result<Vec<RgbImage>, Self::Error> {
140 // Always return empty vector to trigger the error condition
141 Ok(Vec::new())
142 }
143 }
144
145 #[test]
146 fn test_read_single_handles_empty_result_properly() {
147 let reader = MockEmptyImageReader;
148 let result = reader.read_single("test_path.jpg");
149
150 // Should return an error instead of panicking
151 assert!(result.is_err());
152
153 // Check that it's the correct error type
154 let err = result.unwrap_err();
155 if let OCRError::InvalidInput { message } = err {
156 assert!(message.contains("ImageReader::apply returned empty result for single image"));
157 } else {
158 panic!("Expected InvalidInput error, got {:?}", err);
159 }
160 }
161}