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
15/**  
16 * Named unsafe as if the same debugger instance is used across different executions of BloodPressureReadingExtractor
17 * then the files will be overwritten with new values and if it is used across concurrent executions then the files may
18 * be corrupted or an error may be thrown. A new BloodPressureReadingExtractor should be constructed for each run when
19 * using this debugger.
20 * 
21 * This debugger writes the debug image files to the system temporary folder. The folder lives inside bpm-ocr/${folder_name}
22*/
23pub 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        // TODO: create at construction
164        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}