oar_ocr_core/models/rectification/
uvdoc.rs1use crate::core::OCRError;
7use crate::core::inference::{OrtInfer, TensorInput};
8use crate::processors::{NormalizeImage, TensorLayout, UVDocPostProcess};
9use image::{DynamicImage, RgbImage, imageops::FilterType};
10use std::borrow::Cow;
11
12type PreprocessResult = Result<(ndarray::Array4<f32>, Vec<(u32, u32)>), OCRError>;
13
14#[derive(Debug, Clone)]
16pub struct UVDocPreprocessConfig {
17 pub rec_image_shape: [usize; 3],
19}
20
21impl Default for UVDocPreprocessConfig {
22 fn default() -> Self {
23 Self {
24 rec_image_shape: [3, 512, 512],
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct UVDocModelOutput {
32 pub images: Vec<RgbImage>,
34}
35
36#[derive(Debug)]
40pub struct UVDocModel {
41 inference: OrtInfer,
43 normalizer: NormalizeImage,
45 postprocessor: UVDocPostProcess,
47 rec_image_shape: [usize; 3],
49}
50
51impl UVDocModel {
52 pub fn new(
54 inference: OrtInfer,
55 normalizer: NormalizeImage,
56 postprocessor: UVDocPostProcess,
57 rec_image_shape: [usize; 3],
58 ) -> Self {
59 Self {
60 inference,
61 normalizer,
62 postprocessor,
63 rec_image_shape,
64 }
65 }
66
67 pub fn preprocess(&self, images: Vec<RgbImage>) -> PreprocessResult {
77 let image_refs: Vec<_> = images.iter().collect();
78 self.preprocess_refs(&image_refs)
79 }
80
81 pub fn preprocess_refs(&self, images: &[&RgbImage]) -> PreprocessResult {
83 let mut original_sizes = Vec::with_capacity(images.len());
84 let mut processed_images = Vec::with_capacity(images.len());
85
86 let target_height = self.rec_image_shape[1] as u32;
87 let target_width = self.rec_image_shape[2] as u32;
88 let should_resize = target_height > 0 && target_width > 0;
89
90 for &img in images {
91 let original_size = (img.width(), img.height());
92 original_sizes.push(original_size);
93
94 if should_resize && (img.width() != target_width || img.height() != target_height) {
95 let resized =
97 image::imageops::resize(img, target_width, target_height, FilterType::Triangle);
98 processed_images.push(Cow::Owned(resized));
99 } else {
100 processed_images.push(Cow::Borrowed(img));
101 }
102 }
103
104 let processed_refs: Vec<_> = processed_images.iter().map(Cow::as_ref).collect();
106 let batch_tensor = self.normalizer.normalize_batch_refs(&processed_refs)?;
107
108 Ok((batch_tensor, original_sizes))
109 }
110
111 pub fn infer(
121 &self,
122 batch_tensor: &ndarray::Array4<f32>,
123 ) -> Result<ndarray::Array4<f32>, OCRError> {
124 let input_name = self.inference.input_name();
125 let inputs = vec![(input_name, TensorInput::Array4(batch_tensor))];
126
127 let outputs = self
128 .inference
129 .infer(&inputs)
130 .map_err(|e| OCRError::Inference {
131 model_name: "UVDoc".to_string(),
132 context: format!(
133 "failed to run inference on batch with shape {:?}",
134 batch_tensor.shape()
135 ),
136 source: Box::new(e),
137 })?;
138
139 let output = outputs
140 .into_iter()
141 .next()
142 .ok_or_else(|| OCRError::InvalidInput {
143 message: "UVDoc: no output returned from inference".to_string(),
144 })?;
145
146 output
147 .1
148 .try_into_array4_f32()
149 .map_err(|e| OCRError::Inference {
150 model_name: "UVDoc".to_string(),
151 context: "failed to convert output to 4D array".to_string(),
152 source: Box::new(e),
153 })
154 }
155
156 pub fn postprocess(
167 &self,
168 predictions: &ndarray::Array4<f32>,
169 original_sizes: &[(u32, u32)],
170 ) -> Result<Vec<RgbImage>, OCRError> {
171 let mut images =
173 self.postprocessor
174 .apply_batch(predictions)
175 .map_err(|e| OCRError::ConfigError {
176 message: format!("Failed to postprocess rectification output: {}", e),
177 })?;
178
179 if images.len() != original_sizes.len() {
180 return Err(OCRError::InvalidInput {
181 message: format!(
182 "Mismatched rectification batch sizes: predictions={}, originals={}",
183 images.len(),
184 original_sizes.len()
185 ),
186 });
187 }
188
189 for (img, &(orig_w, orig_h)) in images.iter_mut().zip(original_sizes) {
191 if orig_w == 0 || orig_h == 0 {
192 continue;
193 }
194
195 if img.width() != orig_w || img.height() != orig_h {
196 let resized = DynamicImage::ImageRgb8(std::mem::take(img)).resize_exact(
198 orig_w,
199 orig_h,
200 FilterType::Triangle,
201 );
202 *img = resized.into_rgb8();
203 }
204 }
205
206 Ok(images)
207 }
208
209 pub fn forward(&self, images: Vec<RgbImage>) -> Result<UVDocModelOutput, OCRError> {
219 let image_refs: Vec<_> = images.iter().collect();
220 self.forward_refs(&image_refs)
221 }
222
223 pub fn forward_refs(&self, images: &[&RgbImage]) -> Result<UVDocModelOutput, OCRError> {
225 let (batch_tensor, original_sizes) = self.preprocess_refs(images)?;
226 let predictions = self.infer(&batch_tensor)?;
227 let rectified_images = self.postprocess(&predictions, &original_sizes)?;
228
229 Ok(UVDocModelOutput {
230 images: rectified_images,
231 })
232 }
233}
234
235#[derive(Debug, Default)]
237pub struct UVDocModelBuilder {
238 preprocess_config: UVDocPreprocessConfig,
240 ort_config: Option<crate::core::config::OrtSessionConfig>,
242}
243
244impl UVDocModelBuilder {
245 pub fn new() -> Self {
247 Self {
248 preprocess_config: UVDocPreprocessConfig::default(),
249 ort_config: None,
250 }
251 }
252
253 pub fn preprocess_config(mut self, config: UVDocPreprocessConfig) -> Self {
255 self.preprocess_config = config;
256 self
257 }
258
259 pub fn rec_image_shape(mut self, shape: [usize; 3]) -> Self {
261 self.preprocess_config.rec_image_shape = shape;
262 self
263 }
264
265 pub fn with_ort_config(mut self, config: crate::core::config::OrtSessionConfig) -> Self {
267 self.ort_config = Some(config);
268 self
269 }
270
271 pub fn build(
281 self,
282 model_source: impl Into<crate::core::ModelSource>,
283 ) -> Result<UVDocModel, OCRError> {
284 let inference = if self.ort_config.is_some() {
286 use crate::core::config::ModelInferenceConfig;
287 let common_config = ModelInferenceConfig {
288 ort_session: self.ort_config,
289 ..Default::default()
290 };
291 OrtInfer::from_config(&common_config, model_source, Some("image"))?
292 } else {
293 OrtInfer::new(model_source, Some("image"))?
294 };
295
296 let normalizer = NormalizeImage::with_color_order(
298 Some(1.0 / 255.0),
299 Some(vec![0.0, 0.0, 0.0]),
300 Some(vec![1.0, 1.0, 1.0]),
301 Some(TensorLayout::CHW),
302 Some(crate::processors::types::ColorOrder::BGR),
303 )?;
304
305 let postprocessor = UVDocPostProcess::new(255.0);
307
308 Ok(UVDocModel::new(
309 inference,
310 normalizer,
311 postprocessor,
312 self.preprocess_config.rec_image_shape,
313 ))
314 }
315}