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 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}