Skip to main content

bpm_ocr/
debug.rs

1use std::{env, fs::create_dir_all};
2
3use opencv::{
4    core::{AccessFlag, CV_8U, Mat, Point, Rect2i, Scalar, UMat, UMatTraitConst, Vector},
5    imgcodecs::imwrite_def,
6    imgproc::{COLOR_GRAY2RGB, LINE_8, cvt_color, cvt_color_def, draw_contours, rectangle_def},
7};
8
9use crate::models;
10
11use models::{
12    LcdScreenCandidate, ProcessingError, ReadingIdentificationError, RejectedLcdScreenCandidate,
13};
14
15pub struct TempFolderDebugger {
16    debug_enabled: bool,
17}
18
19pub struct NoDebug {}
20
21pub trait BpmOcrDebugOutputter {
22    fn new(debug_enabled: bool) -> Self;
23    fn output(
24        &self,
25        unique_trace_id: &str,
26        image: &Mat,
27        stage_description: &str,
28    ) -> Result<(), ProcessingError>;
29    fn debug_enabled(&self) -> bool;
30
31    fn debug_original_picture(
32        &self,
33        unique_trace_id: &str,
34        image: &Mat,
35    ) -> Result<(), ProcessingError> {
36        if !self.debug_enabled() {
37            return Ok(());
38        }
39
40        self.output(unique_trace_id, &image, "original_image")
41    }
42
43    fn debug_after_canny(
44        &self,
45        unique_trace_id: &str,
46        image: &UMat,
47    ) -> Result<(), ProcessingError> {
48        if !self.debug_enabled() {
49            return Ok(());
50        }
51
52        let converted_to_mat = image.get_mat(AccessFlag::ACCESS_READ)?;
53
54        self.output(unique_trace_id, &converted_to_mat, "after_canny")
55    }
56
57    fn debug_lcd_contour_candidates(
58        &self,
59        unique_trace_id: &str,
60        image: &Mat,
61        candidates: &Vec<LcdScreenCandidate>,
62        rejections: Vec<RejectedLcdScreenCandidate>,
63    ) -> Result<(), ProcessingError> {
64        if !self.debug_enabled() {
65            return Ok(());
66        }
67
68        let mut colour: Mat = Mat::default();
69
70        cvt_color(&image, &mut colour, COLOR_GRAY2RGB, 0)?;
71
72        for rejection in rejections {
73            let mut x: Vector<Vector<Point>> = Vector::new();
74            x.push(rejection.contour);
75
76            draw_contours(
77                &mut colour,
78                &x,
79                0,
80                Scalar::new(255.0, 0.0, 0.0, 0.1),
81                1,
82                LINE_8.into(),
83                &Mat::default(),
84                i32::MAX,
85                Point::default(),
86            )?;
87        }
88
89        for candidate in candidates {
90            let mut x: Vector<Vector<Point>> = Vector::new();
91            x.push(candidate.contour.clone());
92
93            draw_contours(
94                &mut colour,
95                &x,
96                0,
97                Scalar::new(0., 255.0, 0.0, 0.1),
98                1,
99                LINE_8.into(),
100                &Mat::default(),
101                i32::MAX,
102                Point::default(),
103            )?;
104        }
105
106        self.output(unique_trace_id, &colour, "contour_candidates")
107    }
108
109    fn debug_after_perspective_transform(
110        &self,
111        unique_trace_id: &str,
112        image: &Mat,
113    ) -> Result<(), ProcessingError> {
114        if !self.debug_enabled() {
115            return Ok(());
116        }
117
118        self.output(unique_trace_id, &image, "after_perspective_transform")
119    }
120
121    fn debug_digits_before_morph(
122        &self,
123        unique_trace_id: &str,
124        image: &Mat,
125    ) -> Result<(), ProcessingError> {
126        if !self.debug_enabled() {
127            return Ok(());
128        }
129
130        self.output(unique_trace_id, image, "digits_before_morph")
131    }
132
133    fn debug_digits_after_dilation(
134        &self,
135        unique_trace_id: &str,
136        image: &Mat,
137    ) -> Result<(), ProcessingError> {
138        if !self.debug_enabled() {
139            return Ok(());
140        }
141
142        self.output(unique_trace_id, &image, "digits_after_dilation")
143    }
144
145    fn debug_digit_locations(
146        &self,
147        unique_trace_id: &str,
148        image: &Mat,
149        digit_locations: &Vec<Rect2i>,
150    ) -> Result<(), ProcessingError> {
151        if !self.debug_enabled() {
152            return Ok(());
153        }
154
155        let mut temp_image = Mat::default();
156        cvt_color_def(&image, &mut temp_image, CV_8U)?;
157
158        for b in digit_locations {
159            rectangle_def(&mut temp_image, *b, Scalar::new(0.0, 255.0, 0.0, 0.0))?;
160        }
161
162        self.output(unique_trace_id, &temp_image, "digit_locations")
163    }
164}
165
166impl BpmOcrDebugOutputter for TempFolderDebugger {
167    fn new(debug_enabled: bool) -> Self {
168        TempFolderDebugger {
169            debug_enabled: debug_enabled,
170        }
171    }
172
173    fn output(
174        &self,
175        unique_trace_id: &str,
176        image: &Mat,
177        stage_description: &str,
178    ) -> Result<(), ProcessingError> {
179        let folder_path = env::temp_dir().join("bpm-ocr").join(&unique_trace_id);
180
181        // TODO: create at construction
182        create_dir_all(&folder_path).map_err(|_| {
183            ProcessingError::AppError(ReadingIdentificationError::InternalError(
184                "Could not create a temporary folder for debugging image processing",
185            ))
186        })?;
187
188        let file_name = format!("{}.jpeg", &stage_description);
189
190        let binding = folder_path.join(file_name);
191
192        let file_path = binding.to_str().ok_or_else(|| {
193            ProcessingError::AppError(ReadingIdentificationError::InternalError(
194                &"Could not create a temporary folder for debugging image processing",
195            ))
196        })?;
197
198        imwrite_def(&file_path, &image)?;
199        Ok(())
200    }
201
202    fn debug_enabled(&self) -> bool {
203        self.debug_enabled
204    }
205}
206
207impl BpmOcrDebugOutputter for NoDebug {
208    fn new(_: bool) -> Self {
209        NoDebug {}
210    }
211
212    fn output(&self, _: &str, _: &Mat, _: &str) -> Result<(), ProcessingError> {
213        Ok(())
214    }
215
216    fn debug_enabled(&self) -> bool {
217        false
218    }
219}