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 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 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}