1use image::{DynamicImage, GenericImageView, RgbImage};
6use ndarray::{Array4, ArrayBase, Dim, OwnedRepr};
7
8use crate::error::{OcrError, OcrResult};
9
10#[derive(Debug, Clone)]
12pub struct NormalizeParams {
13 pub mean: [f32; 3],
15 pub std: [f32; 3],
17}
18
19impl Default for NormalizeParams {
20 fn default() -> Self {
21 Self {
23 mean: [0.485, 0.456, 0.406],
24 std: [0.229, 0.224, 0.225],
25 }
26 }
27}
28
29impl NormalizeParams {
30 pub fn paddle_det() -> Self {
32 Self {
33 mean: [0.485, 0.456, 0.406],
34 std: [0.229, 0.224, 0.225],
35 }
36 }
37
38 pub fn paddle_rec() -> Self {
40 Self {
41 mean: [0.5, 0.5, 0.5],
42 std: [0.5, 0.5, 0.5],
43 }
44 }
45}
46
47#[inline]
49pub fn get_padded_size(size: u32) -> u32 {
50 ((size + 31) / 32) * 32
51}
52
53pub fn resize_to_max_side(img: &DynamicImage, max_side_len: u32) -> OcrResult<DynamicImage> {
57 let (w, h) = img.dimensions();
58 let max_dim = w.max(h);
59
60 if max_dim <= max_side_len {
61 return Ok(img.clone());
62 }
63
64 let scale = max_side_len as f64 / max_dim as f64;
65 let new_w = (w as f64 * scale).round() as u32;
66 let new_h = (h as f64 * scale).round() as u32;
67
68 fast_resize(img, new_w, new_h)
69}
70
71pub fn resize_to_height(img: &DynamicImage, target_height: u32) -> OcrResult<DynamicImage> {
75 let (w, h) = img.dimensions();
76
77 if h == target_height {
78 return Ok(img.clone());
79 }
80
81 let scale = target_height as f64 / h as f64;
82 let new_w = (w as f64 * scale).round() as u32;
83
84 fast_resize(img, new_w, target_height)
85}
86
87fn fast_resize(img: &DynamicImage, new_w: u32, new_h: u32) -> OcrResult<DynamicImage> {
90 use fast_image_resize::{images::Image, IntoImageView, PixelType, Resizer};
91
92 let converted: DynamicImage;
96 let (src, pixel_type) = match img.pixel_type() {
97 Some(PixelType::U8x3) => (img, PixelType::U8x3),
98 Some(PixelType::U8x4) => (img, PixelType::U8x4),
99 _ => {
100 converted = DynamicImage::ImageRgb8(img.to_rgb8());
101 (&converted, PixelType::U8x3)
102 }
103 };
104
105 let mut dst_image = Image::new(new_w, new_h, pixel_type);
107
108 let mut resizer = Resizer::new();
110 resizer
111 .resize(src, &mut dst_image, None)
112 .map_err(|e| OcrError::PreprocessError(format!("Image resize failed: {e}")))?;
113
114 match pixel_type {
116 PixelType::U8x3 => RgbImage::from_raw(new_w, new_h, dst_image.into_vec())
117 .map(DynamicImage::ImageRgb8)
118 .ok_or_else(|| {
119 OcrError::PreprocessError("RGB buffer size mismatch after resize".into())
120 }),
121 PixelType::U8x4 => image::RgbaImage::from_raw(new_w, new_h, dst_image.into_vec())
122 .map(DynamicImage::ImageRgba8)
123 .ok_or_else(|| {
124 OcrError::PreprocessError("RGBA buffer size mismatch after resize".into())
125 }),
126 _ => unreachable!("pixel_type is constrained to U8x3 or U8x4 above"),
127 }
128}
129
130pub fn preprocess_for_det(
134 img: &DynamicImage,
135 params: &NormalizeParams,
136) -> OcrResult<ArrayBase<OwnedRepr<f32>, Dim<[usize; 4]>>> {
137 let (w, h) = img.dimensions();
138 let pad_w = get_padded_size(w) as usize;
139 let pad_h = get_padded_size(h) as usize;
140 let (w, h) = (w as usize, h as usize);
141
142 let mut input = Array4::<f32>::zeros((1, 3, pad_h, pad_w));
143 let rgb_img = img.to_rgb8();
144 let rgb = rgb_img.as_raw();
145
146 let plane_size = pad_h * pad_w;
147 let data = input
148 .as_slice_mut()
149 .expect("Array4 created by zeros should be contiguous");
150 let scales = [
151 1.0 / (255.0 * params.std[0]),
152 1.0 / (255.0 * params.std[1]),
153 1.0 / (255.0 * params.std[2]),
154 ];
155 let offsets = [
156 -params.mean[0] / params.std[0],
157 -params.mean[1] / params.std[1],
158 -params.mean[2] / params.std[2],
159 ];
160
161 for y in 0..h {
163 let src_row = &rgb[y * w * 3..(y + 1) * w * 3];
164 let dst_row = y * pad_w;
165
166 for (x, pixel) in src_row.chunks_exact(3).enumerate() {
167 let dst = dst_row + x;
168 data[dst] = pixel[0] as f32 * scales[0] + offsets[0];
169 data[plane_size + dst] = pixel[1] as f32 * scales[1] + offsets[1];
170 data[plane_size * 2 + dst] = pixel[2] as f32 * scales[2] + offsets[2];
171 }
172 }
173
174 Ok(input)
175}
176
177pub fn preprocess_for_rec(
182 img: &DynamicImage,
183 target_height: u32,
184 params: &NormalizeParams,
185) -> OcrResult<ArrayBase<OwnedRepr<f32>, Dim<[usize; 4]>>> {
186 let (_, h) = img.dimensions();
187
188 let resized = if h == target_height {
189 img.clone()
190 } else {
191 resize_to_height(img, target_height)?
192 };
193
194 let rgb_img = resized.to_rgb8();
195 let rgb = rgb_img.as_raw();
196 let (w, h) = (rgb_img.width() as usize, rgb_img.height() as usize);
197
198 let mut input = Array4::<f32>::zeros((1, 3, h, w));
199 let plane_size = h * w;
200 let data = input
201 .as_slice_mut()
202 .expect("Array4 created by zeros should be contiguous");
203 let scales = [
204 1.0 / (255.0 * params.std[0]),
205 1.0 / (255.0 * params.std[1]),
206 1.0 / (255.0 * params.std[2]),
207 ];
208 let offsets = [
209 -params.mean[0] / params.std[0],
210 -params.mean[1] / params.std[1],
211 -params.mean[2] / params.std[2],
212 ];
213
214 for y in 0..h {
215 let src_row = &rgb[y * w * 3..(y + 1) * w * 3];
216 let dst_row = y * w;
217
218 for (x, pixel) in src_row.chunks_exact(3).enumerate() {
219 let dst = dst_row + x;
220 data[dst] = pixel[0] as f32 * scales[0] + offsets[0];
221 data[plane_size + dst] = pixel[1] as f32 * scales[1] + offsets[1];
222 data[plane_size * 2 + dst] = pixel[2] as f32 * scales[2] + offsets[2];
223 }
224 }
225
226 Ok(input)
227}
228
229pub fn preprocess_batch_for_rec(
233 images: &[DynamicImage],
234 target_height: u32,
235 params: &NormalizeParams,
236) -> OcrResult<ArrayBase<OwnedRepr<f32>, Dim<[usize; 4]>>> {
237 if images.is_empty() {
238 return Ok(Array4::<f32>::zeros((0, 3, target_height as usize, 0)));
239 }
240
241 let widths: Vec<u32> = images
243 .iter()
244 .map(|img| {
245 let (w, h) = img.dimensions();
246 let scale = target_height as f64 / h as f64;
247 (w as f64 * scale).round() as u32
248 })
249 .collect();
250
251 let max_width = *widths.iter().max().unwrap() as usize;
253 let batch_size = images.len();
254
255 let mut batch = Array4::<f32>::zeros((batch_size, 3, target_height as usize, max_width));
256 let sample_size = 3 * target_height as usize * max_width;
257 let plane_size = target_height as usize * max_width;
258 let data = batch
259 .as_slice_mut()
260 .expect("Array4 created by zeros should be contiguous");
261 let scales = [
262 1.0 / (255.0 * params.std[0]),
263 1.0 / (255.0 * params.std[1]),
264 1.0 / (255.0 * params.std[2]),
265 ];
266 let offsets = [
267 -params.mean[0] / params.std[0],
268 -params.mean[1] / params.std[1],
269 -params.mean[2] / params.std[2],
270 ];
271
272 for (i, img) in images.iter().enumerate() {
273 let resized = resize_to_height(img, target_height)?;
274 let rgb_img = resized.to_rgb8();
275 let rgb = rgb_img.as_raw();
276 let w = rgb_img.width() as usize;
277 let h = target_height as usize;
278 let sample_offset = i * sample_size;
279
280 for y in 0..h {
281 let src_row = &rgb[y * w * 3..(y + 1) * w * 3];
282 let dst_row = y * max_width;
283
284 for (x, pixel) in src_row.chunks_exact(3).enumerate() {
285 let dst = sample_offset + dst_row + x;
286 data[dst] = pixel[0] as f32 * scales[0] + offsets[0];
287 data[sample_offset + plane_size + dst_row + x] =
288 pixel[1] as f32 * scales[1] + offsets[1];
289 data[sample_offset + plane_size * 2 + dst_row + x] =
290 pixel[2] as f32 * scales[2] + offsets[2];
291 }
292 }
293 }
294
295 Ok(batch)
296}
297
298pub fn crop_image(img: &DynamicImage, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
300 img.crop_imm(x, y, width, height)
301}
302
303pub fn split_into_blocks(
313 img: &DynamicImage,
314 block_size: u32,
315 overlap: u32,
316) -> Vec<(DynamicImage, u32, u32)> {
317 let (width, height) = img.dimensions();
318 let mut blocks = Vec::new();
319
320 let step = block_size - overlap;
321
322 let mut y = 0u32;
323 while y < height {
324 let mut x = 0u32;
325 while x < width {
326 let block_w = (block_size).min(width - x);
327 let block_h = (block_size).min(height - y);
328
329 let block = img.crop_imm(x, y, block_w, block_h);
330 blocks.push((block, x, y));
331
332 x += step;
333 if x + overlap >= width && x < width {
334 break;
335 }
336 }
337
338 y += step;
339 if y + overlap >= height && y < height {
340 break;
341 }
342 }
343
344 blocks
345}
346
347pub fn threshold_mask(mask: &[f32], threshold: f32) -> Vec<u8> {
349 mask.iter()
350 .map(|&v| if v > threshold { 255u8 } else { 0u8 })
351 .collect()
352}
353
354pub fn create_gray_image(data: &[u8], width: u32, height: u32) -> image::GrayImage {
356 image::GrayImage::from_raw(width, height, data.to_vec())
357 .unwrap_or_else(|| image::GrayImage::new(width, height))
358}
359
360pub fn to_rgb(img: &DynamicImage) -> RgbImage {
362 img.to_rgb8()
363}
364
365pub fn rgb_to_image(data: &[u8], width: u32, height: u32) -> DynamicImage {
367 let rgb = RgbImage::from_raw(width, height, data.to_vec())
368 .unwrap_or_else(|| RgbImage::new(width, height));
369 DynamicImage::ImageRgb8(rgb)
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn test_padded_size() {
378 assert_eq!(get_padded_size(100), 128);
379 assert_eq!(get_padded_size(32), 32);
380 assert_eq!(get_padded_size(33), 64);
381 assert_eq!(get_padded_size(0), 0);
382 assert_eq!(get_padded_size(1), 32);
383 assert_eq!(get_padded_size(31), 32);
384 assert_eq!(get_padded_size(64), 64);
385 assert_eq!(get_padded_size(65), 96);
386 }
387
388 #[test]
389 fn test_normalize_params() {
390 let params = NormalizeParams::default();
391 assert_eq!(params.mean[0], 0.485);
392
393 let paddle = NormalizeParams::paddle_det();
394 assert_eq!(paddle.mean[0], 0.485);
395 assert_eq!(paddle.std[0], 0.229);
396 }
397
398 #[test]
399 fn test_normalize_params_paddle_rec() {
400 let params = NormalizeParams::paddle_rec();
401 assert_eq!(params.mean[0], 0.5);
402 assert_eq!(params.mean[1], 0.5);
403 assert_eq!(params.mean[2], 0.5);
404 assert_eq!(params.std[0], 0.5);
405 assert_eq!(params.std[1], 0.5);
406 assert_eq!(params.std[2], 0.5);
407 }
408
409 #[test]
410 fn test_resize_to_max_side_no_resize() {
411 let img = DynamicImage::new_rgb8(100, 50);
412 let resized = resize_to_max_side(&img, 200).unwrap();
413
414 assert_eq!(resized.width(), 100);
416 assert_eq!(resized.height(), 50);
417 }
418
419 #[test]
420 fn test_resize_to_max_side_width_limited() {
421 let img = DynamicImage::new_rgb8(1000, 500);
422 let resized = resize_to_max_side(&img, 500).unwrap();
423
424 assert_eq!(resized.width(), 500);
426 assert_eq!(resized.height(), 250);
427 }
428
429 #[test]
430 fn test_resize_to_max_side_height_limited() {
431 let img = DynamicImage::new_rgb8(500, 1000);
432 let resized = resize_to_max_side(&img, 500).unwrap();
433
434 assert_eq!(resized.width(), 250);
436 assert_eq!(resized.height(), 500);
437 }
438
439 #[test]
440 fn test_resize_to_height() {
441 let img = DynamicImage::new_rgb8(200, 100);
442 let resized = resize_to_height(&img, 48).unwrap();
443
444 assert_eq!(resized.height(), 48);
445 assert_eq!(resized.width(), 96);
447 }
448
449 #[test]
450 fn test_resize_to_height_no_resize() {
451 let img = DynamicImage::new_rgb8(200, 48);
452 let resized = resize_to_height(&img, 48).unwrap();
453
454 assert_eq!(resized.height(), 48);
456 assert_eq!(resized.width(), 200);
457 }
458
459 #[test]
460 fn test_preprocess_for_det_shape() {
461 let img = DynamicImage::new_rgb8(100, 50);
462 let params = NormalizeParams::paddle_det();
463 let tensor = preprocess_for_det(&img, ¶ms).unwrap();
464
465 assert_eq!(tensor.shape()[0], 1);
467 assert_eq!(tensor.shape()[1], 3);
468 assert_eq!(tensor.shape()[2], 64); assert_eq!(tensor.shape()[3], 128); }
471
472 #[test]
473 fn test_preprocess_for_rec_shape() {
474 let img = DynamicImage::new_rgb8(200, 100);
475 let params = NormalizeParams::paddle_rec();
476 let tensor = preprocess_for_rec(&img, 48, ¶ms).unwrap();
477
478 assert_eq!(tensor.shape()[0], 1);
480 assert_eq!(tensor.shape()[1], 3);
481 assert_eq!(tensor.shape()[2], 48);
482 assert_eq!(tensor.shape()[3], 96);
484 }
485
486 #[test]
487 fn test_preprocess_batch_for_rec_empty() {
488 let images: Vec<DynamicImage> = vec![];
489 let params = NormalizeParams::paddle_rec();
490 let tensor = preprocess_batch_for_rec(&images, 48, ¶ms).unwrap();
491
492 assert_eq!(tensor.shape()[0], 0);
493 }
494
495 #[test]
496 fn test_preprocess_batch_for_rec_single() {
497 let images = vec![DynamicImage::new_rgb8(200, 100)];
498 let params = NormalizeParams::paddle_rec();
499 let tensor = preprocess_batch_for_rec(&images, 48, ¶ms).unwrap();
500
501 assert_eq!(tensor.shape()[0], 1);
502 assert_eq!(tensor.shape()[1], 3);
503 assert_eq!(tensor.shape()[2], 48);
504 }
505
506 #[test]
507 fn test_preprocess_batch_for_rec_multiple() {
508 let images = vec![
509 DynamicImage::new_rgb8(200, 100),
510 DynamicImage::new_rgb8(300, 100),
511 ];
512 let params = NormalizeParams::paddle_rec();
513 let tensor = preprocess_batch_for_rec(&images, 48, ¶ms).unwrap();
514
515 assert_eq!(tensor.shape()[0], 2);
516 assert_eq!(tensor.shape()[1], 3);
517 assert_eq!(tensor.shape()[2], 48);
518 assert_eq!(tensor.shape()[3], 144);
520 }
521
522 #[test]
523 fn test_crop_image() {
524 let img = DynamicImage::new_rgb8(200, 100);
525 let cropped = crop_image(&img, 50, 25, 100, 50);
526
527 assert_eq!(cropped.width(), 100);
528 assert_eq!(cropped.height(), 50);
529 }
530
531 #[test]
532 fn test_split_into_blocks() {
533 let img = DynamicImage::new_rgb8(500, 500);
534 let blocks = split_into_blocks(&img, 200, 50);
535
536 assert!(!blocks.is_empty());
538
539 for (block, x, y) in &blocks {
541 assert!(block.width() <= 200);
542 assert!(block.height() <= 200);
543 assert!(*x < 500);
544 assert!(*y < 500);
545 }
546 }
547
548 #[test]
549 fn test_split_into_blocks_small_image() {
550 let img = DynamicImage::new_rgb8(100, 100);
551 let blocks = split_into_blocks(&img, 200, 50);
552
553 assert_eq!(blocks.len(), 1);
555 assert_eq!(blocks[0].1, 0); assert_eq!(blocks[0].2, 0); }
558
559 #[test]
560 fn test_threshold_mask() {
561 let mask = vec![0.1, 0.3, 0.5, 0.7, 0.9];
562 let binary = threshold_mask(&mask, 0.5);
563
564 assert_eq!(binary, vec![0, 0, 0, 255, 255]);
565 }
566
567 #[test]
568 fn test_threshold_mask_all_below() {
569 let mask = vec![0.1, 0.2, 0.3, 0.4];
570 let binary = threshold_mask(&mask, 0.5);
571
572 assert_eq!(binary, vec![0, 0, 0, 0]);
573 }
574
575 #[test]
576 fn test_threshold_mask_all_above() {
577 let mask = vec![0.6, 0.7, 0.8, 0.9];
578 let binary = threshold_mask(&mask, 0.5);
579
580 assert_eq!(binary, vec![255, 255, 255, 255]);
581 }
582
583 #[test]
584 fn test_create_gray_image() {
585 let data = vec![128u8; 100];
586 let gray = create_gray_image(&data, 10, 10);
587
588 assert_eq!(gray.width(), 10);
589 assert_eq!(gray.height(), 10);
590 }
591
592 #[test]
593 fn test_to_rgb() {
594 let img = DynamicImage::new_rgb8(100, 50);
595 let rgb = to_rgb(&img);
596
597 assert_eq!(rgb.width(), 100);
598 assert_eq!(rgb.height(), 50);
599 }
600
601 #[test]
602 fn test_rgb_to_image() {
603 let data = vec![128u8; 300]; let img = rgb_to_image(&data, 10, 10);
605
606 assert_eq!(img.width(), 10);
607 assert_eq!(img.height(), 10);
608 }
609}