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