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