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_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        // TODO: create at construction
172        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}