Skip to main content

bpm_ocr/
lib.rs

1use std::sync::Arc;
2
3use opencv::core::{Mat, Size, Vector};
4use opencv::imgcodecs::ImreadModes;
5use opencv::{imgcodecs, imgproc};
6
7use crate::debug::BpmOcrDebugOutputter;
8use crate::lcd_number_extractor::LcdNumberExtractor;
9use crate::lcd_screen_extractor::LcdScreenExtractor;
10use crate::models::{BloodPressureReading, DebuggerTrace, ProcessingError};
11pub mod debug;
12mod digit_extractor;
13mod lcd_number_extractor;
14mod lcd_screen_extractor;
15pub mod models;
16mod rectangle;
17
18pub struct BloodPressureReadingExtractor<T: BpmOcrDebugOutputter> {
19    screen_extractor: LcdScreenExtractor<T>,
20    screen_number_extractor: LcdNumberExtractor<T>,
21    debugging_session: DebuggerTrace<T>,
22}
23
24impl<T: BpmOcrDebugOutputter> BloodPressureReadingExtractor<T> {
25    pub fn new(debugger_session: DebuggerTrace<T>) -> Self {
26        let screen_extractor = LcdScreenExtractor::new(
27            Arc::clone(&debugger_session.debugger),
28            &debugger_session.unique_trace_name,
29        );
30        let screen_number_extractor = LcdNumberExtractor::new(
31            Arc::clone(&debugger_session.debugger),
32            &debugger_session.unique_trace_name,
33        );
34
35        BloodPressureReadingExtractor {
36            screen_extractor,
37            screen_number_extractor,
38            debugging_session: debugger_session,
39        }
40    }
41
42    fn process_image(self: &Self, image: &Mat) -> Result<BloodPressureReading, ProcessingError> {
43        self.debugging_session
44            .debugger
45            .debug_original_picture(&self.debugging_session.unique_trace_name, &image)?;
46
47        let mut resized_image = Mat::default();
48
49        let interpolation: i32 = 0;
50        imgproc::resize(
51            &image,
52            &mut resized_image,
53            Size::new(800, 800),
54            0.,
55            0.,
56            interpolation,
57        )?;
58
59        let birdseye_lcd_only = self.screen_extractor.extract_lcd(&resized_image)?;
60
61        let reading = self
62            .screen_number_extractor
63            .extract_reading(&birdseye_lcd_only)?;
64
65        Ok(reading)
66    }
67}
68
69/// Attempts to extract a blood pressure reading from a photo file of a blood pressure monitor screen
70/// * `filename` - the path to the photo file
71/// * `debugger` - the debugger trace session to output debug images with
72pub fn get_reading_from_file<T: BpmOcrDebugOutputter>(
73    filename: &str,
74    debugger: DebuggerTrace<T>,
75) -> Result<BloodPressureReading, ProcessingError> {
76    let extractor: BloodPressureReadingExtractor<T> = BloodPressureReadingExtractor::new(debugger);
77
78    let gray_scale_mode: i32 = ImreadModes::IMREAD_GRAYSCALE.into();
79    let image = imgcodecs::imread(filename, gray_scale_mode)?;
80
81    extractor.process_image(&image)
82}
83
84/// Attempts to extract a blood pressure reading from a byte buffer containing a photo file of a blood pressure monitor screen
85/// * `filename` - the byte buffer with the photo file
86/// * `debugger` - the debugger trace session to output debug images with
87pub fn get_reading_from_buffer<T: BpmOcrDebugOutputter>(
88    file_contents: Vec<u8>,
89    debugger: DebuggerTrace<T>,
90) -> Result<BloodPressureReading, ProcessingError> {
91    let extractor: BloodPressureReadingExtractor<T> = BloodPressureReadingExtractor::new(debugger);
92
93    let contents = Vector::from_slice(&file_contents);
94    let image = imgcodecs::imdecode(&contents, ImreadModes::IMREAD_GRAYSCALE.into())?;
95
96    extractor.process_image(&image)
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::debug::TempFolderDebugger;
103
104    #[test]
105    fn test_success_photo_at_angle() {
106        let debug_session: DebuggerTrace<TempFolderDebugger> =
107            DebuggerTrace::temp_folder_session("test_success");
108
109        let testfile = Vec::from(include_bytes!("./test_resources/example_at_angle.jpg"));
110
111        let expected_result = BloodPressureReading {
112            systolic: 133,
113            diastolic: 93,
114            pulse: 65,
115        };
116
117        let result = get_reading_from_buffer(testfile, debug_session).unwrap();
118
119        assert_eq!(result, expected_result);
120    }
121
122    #[test]
123    fn test_success_topdown_photo() {
124        let debug_session: DebuggerTrace<TempFolderDebugger> =
125            DebuggerTrace::temp_folder_session("test_topdown_photo");
126
127        let testfile = Vec::from(include_bytes!("./test_resources/example_top_down.jpg"));
128
129        let expected_result = BloodPressureReading {
130            systolic: 131,
131            diastolic: 88,
132            pulse: 77,
133        };
134
135        let result = get_reading_from_buffer(testfile, debug_session).unwrap();
136
137        assert_eq!(result, expected_result);
138    }
139
140    #[test]
141    fn test_with_2_digit() {
142        let debug_session: DebuggerTrace<TempFolderDebugger> =
143            DebuggerTrace::temp_folder_session("contour_candidates");
144
145        let testfile = Vec::from(include_bytes!("./test_resources/contour_candidates.jpeg"));
146
147        let expected_result = BloodPressureReading {
148            systolic: 123,
149            diastolic: 85,
150            pulse: 68,
151        };
152
153        let result = get_reading_from_buffer(testfile, debug_session).unwrap();
154
155        assert_eq!(result, expected_result);
156    }
157}