bpm_ocr/
lib.rs

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