FatemaKotb commited on
Commit
4cdc77a
·
1 Parent(s): cfbaa51

CPR Partial Code Cleaning

Browse files
CPR_Module/Common/chest_initializer.py CHANGED
@@ -5,7 +5,7 @@ from CPR_Module.Common.keypoints import CocoKeypoints
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
7
  class ChestInitializer:
8
- """Handles chest point detection with validations in estimation."""
9
 
10
  def __init__(self):
11
  self.chest_params = None
@@ -13,7 +13,7 @@ class ChestInitializer:
13
  self.expected_chest_params = None
14
 
15
  def estimate_chest_region(self, keypoints, bounding_box, frame_width, frame_height):
16
- """Estimate and validate chest region. Returns (cx, cy, cw, ch) or None."""
17
  try:
18
  # Unpack bounding box and calculate shoulder dimensions
19
  bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bounding_box
@@ -24,71 +24,62 @@ class ChestInitializer:
24
  right_shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
25
 
26
  # Midpoints calculation
27
- shoulder_center = np.array([(left_shoulder[0] + right_shoulder[0]) / 2,
28
- (left_shoulder[1] + right_shoulder[1]) / 2])
 
 
29
 
30
- #& Handing different patient positions
31
  # If the x-coordinate shoulder center is closer to that of the Bottom-Right bbox corner (2)
32
  # then the orientation is "right"
33
  # If the x-coordinate shoulder center is closer to that of the Top-Left bbox corner (1)
34
  # then the orientation is "left"
35
-
36
- if abs(shoulder_center[0] - bbox_x2) < abs(shoulder_center[0] - bbox_x1): # Orientation is "right"
37
- chest_center_from_shoulder_x = shoulder_center[0] - 0.3 * bbox_delta_y
38
- chest_center_from_shoulder_y = shoulder_center[1] - 0.1 * bbox_delta_y
39
- chest_center_from_shoulder = np.array([chest_center_from_shoulder_x, chest_center_from_shoulder_y])
40
- else: # Orientation is "left"
41
- chest_center_from_shoulder_x = shoulder_center[0] + 1.0 * bbox_delta_y
42
- chest_center_from_shoulder_y = shoulder_center[1] - 0.1 * bbox_delta_y
43
- chest_center_from_shoulder = np.array([chest_center_from_shoulder_x, chest_center_from_shoulder_y])
44
-
45
- # Chest dimensions (85% of shoulder width, 40% height)
46
  chest_dx = bbox_delta_y * 0.8
47
  chest_dy = bbox_delta_y * 1.75
48
 
49
  # Calculate region coordinates
50
- x1 = chest_center_from_shoulder[0] - chest_dx / 2
51
- y1 = chest_center_from_shoulder[1] - chest_dy / 2
52
- x2 = chest_center_from_shoulder[0] + chest_dx / 2
53
- y2 = chest_center_from_shoulder[1] + chest_dy / 2
54
-
55
- # Clamp to frame boundaries
56
- x1 = max(0, min(x1, frame_width - 1))
57
- y1 = max(0, min(y1, frame_height - 1))
58
- x2 = max(0, min(x2, frame_width - 1))
59
- y2 = max(0, min(y2, frame_height - 1))
60
-
61
- # Check validity
 
 
 
 
 
 
62
  if x2 <= x1 or y2 <= y1:
63
  return None
64
 
65
- # Adjusted parameters
66
- cx = (x1 + x2) / 2
67
- cy = (y1 + y2) / 2
68
- cw = x2 - x1
69
- ch = y2 - y1
70
-
71
- return (cx, cy, cw, ch)
72
 
73
  except (IndexError, TypeError, ValueError) as e:
74
  cpr_logger.error(f"Chest estimation error: {e}")
75
  return None
76
 
77
  def estimate_chest_region_weighted_avg(self, frame_width, frame_height, window_size=60, min_samples=3):
78
- """
79
- Calculate stabilized chest parameters using weighted averaging with boundary checks.
80
-
81
- Args:
82
- self.chest_params_history: List of recent chest parameters [(cx, cy, cw, ch), ...]
83
- frame_width: Width of the video frame
84
- frame_height: Height of the video frame
85
- window_size: Number of recent frames to consider (default: 5)
86
- min_samples: Minimum valid samples required (default: 3)
87
-
88
- Returns:
89
- Tuple of (cx, cy, cw, ch) as integers within frame boundaries,
90
- or None if insufficient data or invalid rectangle
91
- """
92
  if not self.chest_params_history:
93
  return None
94
 
@@ -134,7 +125,7 @@ class ChestInitializer:
134
  return None
135
 
136
  def draw_expected_chest_region(self, frame):
137
- """Draws the chest region without validation."""
138
  if self.expected_chest_params is None:
139
  return frame
140
 
@@ -149,7 +140,7 @@ class ChestInitializer:
149
 
150
  cv2.circle(frame, (int(cx), int(cy)), 8, (128, 128, 0), -1)
151
 
152
- cv2.putText(frame, "EXPECTED CHEST", (x1, max(10, y1 - 5)),
153
  cv2.FONT_HERSHEY_SIMPLEX, 0.8, (128, 128, 0), 2)
154
 
155
  return frame
 
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
7
  class ChestInitializer:
8
+ """Estimates and stabilizes chest region parameters based on keypoints and bounding box."""
9
 
10
  def __init__(self):
11
  self.chest_params = None
 
13
  self.expected_chest_params = None
14
 
15
  def estimate_chest_region(self, keypoints, bounding_box, frame_width, frame_height):
16
+ """Estimate chest region parameters based on keypoints and bounding box."""
17
  try:
18
  # Unpack bounding box and calculate shoulder dimensions
19
  bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bounding_box
 
24
  right_shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
25
 
26
  # Midpoints calculation
27
+ shoulder_center = np.array([
28
+ (left_shoulder[0] + right_shoulder[0]) / 2,
29
+ (left_shoulder[1] + right_shoulder[1]) / 2
30
+ ])
31
 
32
+ #& Handling different patient positions
33
  # If the x-coordinate shoulder center is closer to that of the Bottom-Right bbox corner (2)
34
  # then the orientation is "right"
35
  # If the x-coordinate shoulder center is closer to that of the Top-Left bbox corner (1)
36
  # then the orientation is "left"
37
+ if abs(shoulder_center[0] - bbox_x2) < abs(shoulder_center[0] - bbox_x1): # Right orientation
38
+ chest_center_x = shoulder_center[0] - 0.3 * bbox_delta_y
39
+ chest_center_y = shoulder_center[1] - 0.1 * bbox_delta_y
40
+ else: # Left orientation
41
+ chest_center_x = shoulder_center[0] + 1.0 * bbox_delta_y
42
+ chest_center_y = shoulder_center[1] - 0.1 * bbox_delta_y
43
+
44
+ # Chest dimensions
 
 
 
45
  chest_dx = bbox_delta_y * 0.8
46
  chest_dy = bbox_delta_y * 1.75
47
 
48
  # Calculate region coordinates
49
+ x1 = chest_center_x - chest_dx / 2
50
+ y1 = chest_center_y - chest_dy / 2
51
+ x2 = chest_center_x + chest_dx / 2
52
+ y2 = chest_center_y + chest_dy / 2
53
+
54
+ # First clamp the bounding box to frame boundaries
55
+ bbox_x1 = max(0, min(bbox_x1, frame_width - 1))
56
+ bbox_y1 = max(0, min(bbox_y1, frame_height - 1))
57
+ bbox_x2 = max(0, min(bbox_x2, frame_width - 1))
58
+ bbox_y2 = max(0, min(bbox_y2, frame_height - 1))
59
+
60
+ # Clamp to bounding box (which is already clamped to frame)
61
+ x1 = max(bbox_x1, min(x1, bbox_x2))
62
+ y1 = max(bbox_y1, min(y1, bbox_y2))
63
+ x2 = max(bbox_x1, min(x2, bbox_x2))
64
+ y2 = max(bbox_y1, min(y2, bbox_y2))
65
+
66
+ # Final validity check
67
  if x2 <= x1 or y2 <= y1:
68
  return None
69
 
70
+ return (
71
+ (x1 + x2) / 2, # cx
72
+ (y1 + y2) / 2, # cy
73
+ x2 - x1, # cw
74
+ y2 - y1 # ch
75
+ )
 
76
 
77
  except (IndexError, TypeError, ValueError) as e:
78
  cpr_logger.error(f"Chest estimation error: {e}")
79
  return None
80
 
81
  def estimate_chest_region_weighted_avg(self, frame_width, frame_height, window_size=60, min_samples=3):
82
+ """Estimate chest region using weighted average of recent parameters."""
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  if not self.chest_params_history:
84
  return None
85
 
 
125
  return None
126
 
127
  def draw_expected_chest_region(self, frame):
128
+ """Draw the expected chest region on the frame."""
129
  if self.expected_chest_params is None:
130
  return frame
131
 
 
140
 
141
  cv2.circle(frame, (int(cx), int(cy)), 8, (128, 128, 0), -1)
142
 
143
+ cv2.putText(frame, "CHEST", (x1, max(10, y1 - 5)),
144
  cv2.FONT_HERSHEY_SIMPLEX, 0.8, (128, 128, 0), 2)
145
 
146
  return frame
CPR_Module/Common/keypoints.py CHANGED
@@ -1,4 +1,3 @@
1
- # keypoints.py
2
  from enum import Enum
3
 
4
  class CocoKeypoints(Enum):
 
 
1
  from enum import Enum
2
 
3
  class CocoKeypoints(Enum):
CPR_Module/Common/logging_config.py CHANGED
@@ -1,4 +1,3 @@
1
- # logging_config.py
2
  import logging
3
 
4
  # 1. Set default log level here (change this value as needed)
 
 
1
  import logging
2
 
3
  # 1. Set default log level here (change this value as needed)
CPR_Module/Common/posture_analyzer.py CHANGED
@@ -1,13 +1,11 @@
1
- # posture_analyzer.py
2
  import math
3
- import cv2
4
  import numpy as np
5
 
6
  from CPR_Module.Common.keypoints import CocoKeypoints
7
  from CPR_Module.Common.logging_config import cpr_logger
8
 
9
  class PostureAnalyzer:
10
- """Posture analysis and visualization with comprehensive validation"""
11
 
12
  def __init__(self, right_arm_angle_threshold, left_arm_angle_threshold, wrist_distance_threshold, history_length_to_average):
13
  self.history_length_to_average = history_length_to_average
@@ -79,7 +77,7 @@ class PostureAnalyzer:
79
  return warnings
80
 
81
  def _check_hands_on_chest(self, keypoints, chest_params):
82
- """Check individual hand positions and return specific warnings"""
83
 
84
  # Get the wrist keypoints
85
  left_wrist = keypoints[CocoKeypoints.LEFT_WRIST.value]
@@ -87,8 +85,9 @@ class PostureAnalyzer:
87
 
88
  warnings = []
89
  try:
 
90
  if chest_params is None:
91
- return ["Both hands not on chest!"] # Fallback warning
92
 
93
  cx, cy, cw, ch = chest_params
94
  left_in = right_in = False
@@ -117,7 +116,6 @@ class PostureAnalyzer:
117
 
118
  return warnings
119
 
120
-
121
  def validate_posture(self, keypoints, chest_params):
122
  """Run all posture validations (returns aggregated warnings)"""
123
  warnings = []
 
 
1
  import math
 
2
  import numpy as np
3
 
4
  from CPR_Module.Common.keypoints import CocoKeypoints
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
7
  class PostureAnalyzer:
8
+ """Analyzes the Rescuer's posture during CPR based on the rescuer's keypoints and the patient's chest region."""
9
 
10
  def __init__(self, right_arm_angle_threshold, left_arm_angle_threshold, wrist_distance_threshold, history_length_to_average):
11
  self.history_length_to_average = history_length_to_average
 
77
  return warnings
78
 
79
  def _check_hands_on_chest(self, keypoints, chest_params):
80
+ """Check if both hands are on the chest (returns warnings)"""
81
 
82
  # Get the wrist keypoints
83
  left_wrist = keypoints[CocoKeypoints.LEFT_WRIST.value]
 
85
 
86
  warnings = []
87
  try:
88
+ # Fallback
89
  if chest_params is None:
90
+ return ["Both hands not on chest!"]
91
 
92
  cx, cy, cw, ch = chest_params
93
  left_in = right_in = False
 
116
 
117
  return warnings
118
 
 
119
  def validate_posture(self, keypoints, chest_params):
120
  """Run all posture validations (returns aggregated warnings)"""
121
  warnings = []
CPR_Module/Common/role_classifier.py CHANGED
@@ -1,12 +1,9 @@
1
- # role_classifier.py
2
- import cv2
3
- import numpy as np
4
  from ultralytics.utils.plotting import Annotator
5
 
6
  from CPR_Module.Common.logging_config import cpr_logger
7
 
8
  class RoleClassifier:
9
- """Role classification and tracking using image processing"""
10
 
11
  def __init__(self, proximity_thresh=0.3):
12
  self.proximity_thresh = proximity_thresh
@@ -25,15 +22,14 @@ class RoleClassifier:
25
  if width == 0 or height == 0:
26
  return -1
27
 
28
- return 1 if height > width else 0 # 1 for vertical, 0 for horizontal
29
 
30
  except (TypeError, ValueError) as e:
31
  cpr_logger.error(f"Verticality score calculation error: {e}")
32
  return -1
33
 
34
  def _calculate_bounding_box_center(self, bounding_box):
35
- """Calculate the center coordinates of a bounding box.
36
- """
37
  x1, y1, x2, y2 = bounding_box
38
  return (x1 + x2) / 2, (y1 + y2) / 2
39
 
@@ -42,29 +38,18 @@ class RoleClassifier:
42
  return ((point1[0]-point2[0])**2 + (point1[1]-point2[1])**2)**0.5
43
 
44
  def _calculate_bbox_areas(self, rescuer_bbox, patient_bbox):
45
- """
46
- Calculate bounding box areas for rescuer and patient.
47
-
48
- Args:
49
- rescuer_bbox: [x1, y1, x2, y2] coordinates of rescuer's bounding box
50
- patient_bbox: [x1, y1, x2, y2] coordinates of patient's bounding box
51
-
52
- Returns:
53
- Tuple: (rescuer_area, patient_area) in pixels
54
- """
55
  def compute_area(bbox):
56
  if bbox is None:
57
  return 0
58
- width = bbox[2] - bbox[0] # x2 - x1
59
  height = bbox[3] - bbox[1] # y2 - y1
60
  return abs(width * height) # Absolute value to handle negative coordinates
61
 
62
  return compute_area(rescuer_bbox), compute_area(patient_bbox)
63
 
64
  def classify_roles(self, results, prev_rescuer_processed_results=None, prev_patient_processed_results=None):
65
- """
66
- Classify roles of rescuer and patient based on detected keypoints and bounding boxes.
67
- """
68
 
69
  processed_results = []
70
 
@@ -74,12 +59,10 @@ class RoleClassifier:
74
  prev_rescuer_bbox = prev_rescuer_processed_results["bounding_box"]
75
  prev_patient_bbox = prev_patient_processed_results["bounding_box"]
76
 
77
- rescuer_area = (prev_rescuer_bbox[2]-prev_rescuer_bbox[0])*(prev_rescuer_bbox[3]-prev_rescuer_bbox[1])
78
- patient_area = (prev_patient_bbox[2]-prev_patient_bbox[0])*(prev_patient_bbox[3]-prev_patient_bbox[1])
79
  threshold = rescuer_area + patient_area
80
 
81
- for i, (box, keypoints) in enumerate(zip(results.boxes.xywh.cpu().numpy(),
82
- results.keypoints.xy.cpu().numpy())):
83
  try:
84
  # Convert box to [x1,y1,x2,y2] format
85
  x_center, y_center, width, height = box
@@ -99,6 +82,7 @@ class RoleClassifier:
99
 
100
  # Calculate features
101
  verticality_score = self._calculate_verticality_score(bounding_box)
 
102
  #!We already have the center coordinates from the bounding box, no need to recalculate it.
103
  bounding_box_center = self._calculate_bounding_box_center(bounding_box)
104
 
@@ -115,24 +99,23 @@ class RoleClassifier:
115
  cpr_logger.error(f"Error processing detection {i}: {e}")
116
  continue
117
 
118
- # Step 2: Identify the patient (horizontal posture)
119
- patient_candidates = [res for res in processed_results
120
- if res['verticality_score'] == 0]
121
 
122
  # If more than one horizontal person, select person with lowest center (likely lying down)
123
  if len(patient_candidates) > 1:
124
- patient_candidates = sorted(patient_candidates,
125
- key=lambda x: x['bounding_box_center'][1])[:1] # Sort by y-coordinate
126
 
127
  patient = patient_candidates[0] if patient_candidates else None
128
 
129
- # Step 3: Identify the rescuer
130
  rescuer = None
131
  if patient:
132
  # Find vertical people who aren't the patient
133
  potential_rescuers = [
134
  res for res in processed_results
135
  if res['verticality_score'] == 1
 
136
  #! Useless condition because the patient was horizontal
137
  and res['original_index'] != patient['original_index']
138
  ]
@@ -150,11 +133,11 @@ class RoleClassifier:
150
  # Create annotator object
151
  annotator = Annotator(frame)
152
 
153
- # Draw rescuer (A) with green box and keypoints
154
  if self.rescuer_processed_results:
155
  try:
156
  x1, y1, x2, y2 = map(int, self.rescuer_processed_results["bounding_box"])
157
- annotator.box_label((x1, y1, x2, y2), "Rescuer A", color=(0, 255, 0))
158
 
159
  if "keypoints" in self.rescuer_processed_results:
160
  keypoints = self.rescuer_processed_results["keypoints"]
@@ -162,11 +145,11 @@ class RoleClassifier:
162
  except Exception as e:
163
  cpr_logger.error(f"Error drawing rescuer: {str(e)}")
164
 
165
- # Draw patient (B) with red box and keypoints
166
  if self.patient_processed_results:
167
  try:
168
  x1, y1, x2, y2 = map(int, self.patient_processed_results["bounding_box"])
169
- annotator.box_label((x1, y1, x2, y2), "Patient B", color=(0, 0, 255))
170
 
171
  if "keypoints" in self.patient_processed_results:
172
  keypoints = self.patient_processed_results["keypoints"]
 
 
 
 
1
  from ultralytics.utils.plotting import Annotator
2
 
3
  from CPR_Module.Common.logging_config import cpr_logger
4
 
5
  class RoleClassifier:
6
+ """Classify roles of rescuer and patient based on detected keypoints and bounding boxes."""
7
 
8
  def __init__(self, proximity_thresh=0.3):
9
  self.proximity_thresh = proximity_thresh
 
22
  if width == 0 or height == 0:
23
  return -1
24
 
25
+ return 1 if height > width else 0
26
 
27
  except (TypeError, ValueError) as e:
28
  cpr_logger.error(f"Verticality score calculation error: {e}")
29
  return -1
30
 
31
  def _calculate_bounding_box_center(self, bounding_box):
32
+ """Calculate the center coordinates of a bounding box."""
 
33
  x1, y1, x2, y2 = bounding_box
34
  return (x1 + x2) / 2, (y1 + y2) / 2
35
 
 
38
  return ((point1[0]-point2[0])**2 + (point1[1]-point2[1])**2)**0.5
39
 
40
  def _calculate_bbox_areas(self, rescuer_bbox, patient_bbox):
41
+ """ Calculate areas of rescuer and patient bounding boxes."""
 
 
 
 
 
 
 
 
 
42
  def compute_area(bbox):
43
  if bbox is None:
44
  return 0
45
+ width = bbox[2] - bbox[0] # x2 - x1
46
  height = bbox[3] - bbox[1] # y2 - y1
47
  return abs(width * height) # Absolute value to handle negative coordinates
48
 
49
  return compute_area(rescuer_bbox), compute_area(patient_bbox)
50
 
51
  def classify_roles(self, results, prev_rescuer_processed_results=None, prev_patient_processed_results=None):
52
+ """Classify rescuer and patient roles based on detection results."""
 
 
53
 
54
  processed_results = []
55
 
 
59
  prev_rescuer_bbox = prev_rescuer_processed_results["bounding_box"]
60
  prev_patient_bbox = prev_patient_processed_results["bounding_box"]
61
 
62
+ rescuer_area, patient_area = self._calculate_bbox_areas(prev_rescuer_bbox, prev_patient_bbox)
 
63
  threshold = rescuer_area + patient_area
64
 
65
+ for i, (box, keypoints) in enumerate(zip(results.boxes.xywh.cpu().numpy(), results.keypoints.xy.cpu().numpy())):
 
66
  try:
67
  # Convert box to [x1,y1,x2,y2] format
68
  x_center, y_center, width, height = box
 
82
 
83
  # Calculate features
84
  verticality_score = self._calculate_verticality_score(bounding_box)
85
+
86
  #!We already have the center coordinates from the bounding box, no need to recalculate it.
87
  bounding_box_center = self._calculate_bounding_box_center(bounding_box)
88
 
 
99
  cpr_logger.error(f"Error processing detection {i}: {e}")
100
  continue
101
 
102
+ # Identify the patient (horizontal posture)
103
+ patient_candidates = [res for res in processed_results if res['verticality_score'] == 0]
 
104
 
105
  # If more than one horizontal person, select person with lowest center (likely lying down)
106
  if len(patient_candidates) > 1:
107
+ patient_candidates = sorted(patient_candidates, key=lambda x: x['bounding_box_center'][1])[:1] # Sort by y-coordinate
 
108
 
109
  patient = patient_candidates[0] if patient_candidates else None
110
 
111
+ # Identify the rescuer
112
  rescuer = None
113
  if patient:
114
  # Find vertical people who aren't the patient
115
  potential_rescuers = [
116
  res for res in processed_results
117
  if res['verticality_score'] == 1
118
+
119
  #! Useless condition because the patient was horizontal
120
  and res['original_index'] != patient['original_index']
121
  ]
 
133
  # Create annotator object
134
  annotator = Annotator(frame)
135
 
136
+ # Draw rescuer
137
  if self.rescuer_processed_results:
138
  try:
139
  x1, y1, x2, y2 = map(int, self.rescuer_processed_results["bounding_box"])
140
+ annotator.box_label((x1, y1, x2, y2), "Rescuer", color=(0, 255, 0))
141
 
142
  if "keypoints" in self.rescuer_processed_results:
143
  keypoints = self.rescuer_processed_results["keypoints"]
 
145
  except Exception as e:
146
  cpr_logger.error(f"Error drawing rescuer: {str(e)}")
147
 
148
+ # Draw patient
149
  if self.patient_processed_results:
150
  try:
151
  x1, y1, x2, y2 = map(int, self.patient_processed_results["bounding_box"])
152
+ annotator.box_label((x1, y1, x2, y2), "Patient", color=(0, 0, 255))
153
 
154
  if "keypoints" in self.patient_processed_results:
155
  keypoints = self.patient_processed_results["keypoints"]
CPR_Module/Common/shoulders_analyzer.py CHANGED
@@ -4,7 +4,7 @@ from CPR_Module.Common.keypoints import CocoKeypoints
4
  from CPR_Module.Common.logging_config import cpr_logger
5
 
6
  class ShouldersAnalyzer:
7
- """Analyzes shoulder distances and posture"""
8
 
9
  def __init__(self):
10
  self.shoulder_distance = None
 
4
  from CPR_Module.Common.logging_config import cpr_logger
5
 
6
  class ShouldersAnalyzer:
7
+ """Calculate and store shoulder distance for CPR rescuer"""
8
 
9
  def __init__(self):
10
  self.shoulder_distance = None
CPR_Module/Common/threaded_camera.py CHANGED
@@ -26,7 +26,7 @@ class ThreadedCamera:
26
 
27
  cpr_logger.info(f"[VIDEO CAPTURE] Requested FPS: {requested_fps}, Set Success: {set_success}, Actual FPS: {actual_fps}")
28
 
29
- # The buffer should be able to hold a lag of up to 2 seconds
30
  number_of_seconds_to_buffer = 5
31
  queue_size = int(actual_fps * number_of_seconds_to_buffer)
32
  self.q = Queue(maxsize=queue_size)
 
26
 
27
  cpr_logger.info(f"[VIDEO CAPTURE] Requested FPS: {requested_fps}, Set Success: {set_success}, Actual FPS: {actual_fps}")
28
 
29
+ # The buffer should be able to hold a lag of up to "number_of_seconds_to_buffer" seconds
30
  number_of_seconds_to_buffer = 5
31
  queue_size = int(actual_fps * number_of_seconds_to_buffer)
32
  self.q = Queue(maxsize=queue_size)
CPR_Module/Common/warnings_overlayer.py CHANGED
@@ -1,7 +1,6 @@
1
  import cv2
2
  import numpy as np
3
  import os
4
- import sys
5
 
6
  from CPR_Module.Common.logging_config import cpr_logger
7
 
@@ -108,7 +107,6 @@ class WarningsOverlayer:
108
  writer.release()
109
  cpr_logger.info(f"\n[POST-PROCESS] Final output saved to: {final_path}")
110
 
111
-
112
  def _draw_warnings(self, frame, active_warnings):
113
  """Draw all warnings in a single vertical drawer"""
114
  frame_height = frame.shape[0]
 
1
  import cv2
2
  import numpy as np
3
  import os
 
4
 
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
 
107
  writer.release()
108
  cpr_logger.info(f"\n[POST-PROCESS] Final output saved to: {final_path}")
109
 
 
110
  def _draw_warnings(self, frame, active_warnings):
111
  """Draw all warnings in a single vertical drawer"""
112
  frame_height = frame.shape[0]
CPR_Module/Common/wrists_midpoint_analyzer.py CHANGED
@@ -5,7 +5,7 @@ from CPR_Module.Common.keypoints import CocoKeypoints
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
7
  class WristsMidpointAnalyzer:
8
- """Analyzes and tracks wrist midpoints for rescuer"""
9
 
10
  def __init__(self, allowed_distance_between_wrists=170):
11
  self.allowed_distance_between_wrists = allowed_distance_between_wrists
 
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
7
  class WristsMidpointAnalyzer:
8
+ """Calculate and visualize midpoint between wrists for CPR rescuer"""
9
 
10
  def __init__(self, allowed_distance_between_wrists=170):
11
  self.allowed_distance_between_wrists = allowed_distance_between_wrists
CPR_Module/Educational_Mode/CPRAnalyzer.py CHANGED
@@ -1,10 +1,8 @@
1
- # main.py
2
  import cv2
3
  import time
4
  import math
5
  import numpy as np
6
  import os
7
- import sys
8
 
9
  from CPR_Module.Educational_Mode.pose_estimation import PoseEstimator
10
  from CPR_Module.Educational_Mode.metrics_calculator import MetricsCalculator
@@ -20,7 +18,7 @@ from CPR_Module.Common.warnings_overlayer import WarningsOverlayer
20
  from CPR_Module.Common.logging_config import cpr_logger
21
 
22
  class CPRAnalyzer:
23
- """Main CPR analysis pipeline with execution tracing"""
24
 
25
  def __init__(self, input_video, video_output_path, plot_output_path, requested_fps):
26
 
@@ -85,11 +83,11 @@ class CPRAnalyzer:
85
  cpr_logger.info("[INIT] Previous results initialized")
86
 
87
  #& Fundamental timing parameters (in seconds)
88
- self.MIN_ERROR_DURATION = 0.5 # Require sustained errors zfor 1 second
89
- self.REPORTING_INTERVAL = 5.0 # Generate reports every 5 seconds
90
- self.SAMPLING_INTERVAL = 0.2 # Analyze every 0.2 seconds
91
- self.KEEP_RATE_AND_DEPTH_WARNINGS_INTERVAL = 3.0
92
- self.MIN_CHUNK_LENGTH_TO_REPORT = 3.0
93
 
94
  # Derived frame counts
95
  self.sampling_interval_frames = int(round(self.fps * self.SAMPLING_INTERVAL))
@@ -126,7 +124,6 @@ class CPRAnalyzer:
126
  self.consecutive_frames_with_posture_errors_counters = {warning: 0 for warning in self.possible_warnings}
127
 
128
  #& Initialize variables for reporting warnings
129
-
130
  self.rate_and_depth_warnings_from_the_last_report = []
131
  cpr_logger.info("[INIT] Rate and depth warnings from the last report initialized")
132
 
@@ -151,75 +148,6 @@ class CPRAnalyzer:
151
  self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames
152
  cpr_logger.info("[INIT] Formatted warnings initialized")
153
 
154
- def _initialize_video_writer(self, frame):
155
- """Initialize writer with safe fallback options"""
156
- height, width = frame.shape[:2]
157
- effective_fps = self.fps / max(1, self.sampling_interval_frames)
158
-
159
- # Try different codec/container combinations
160
- for codec, ext, fmt in [('avc1', 'mp4', 'mp4v'), # H.264
161
- ('MJPG', 'avi', 'avi'),
162
- ('XVID', 'avi', 'avi')]:
163
- fourcc = cv2.VideoWriter_fourcc(*codec)
164
- writer = cv2.VideoWriter(self.video_output_path, fourcc, effective_fps, (width, height))
165
-
166
- if writer.isOpened():
167
- self.video_writer = writer
168
- self._writer_initialized = True
169
- cpr_logger.info(f"[VIDEO WRITER] Initialized with {codec} codec")
170
- return
171
- else:
172
- writer.release()
173
-
174
- cpr_logger.info("[ERROR] Failed to initialize any video writer!")
175
- self._writer_initialized = False
176
-
177
- def _handle_chunk_end(self):
178
- """Helper to handle chunk termination logic"""
179
- self._calculate_rate_and_depth_for_chunk()
180
- cpr_logger.info(f"[RUN ANALYSIS] Calculated rate and depth for the chunk")
181
-
182
- rate_and_depth_warnings = self._get_rate_and_depth_warnings()
183
-
184
- # If the chunk is too short, we don't want to report any warnings it might contain.
185
- if (self.chunk_end_frame_index - self.chunk_start_frame_index) < self.min_chunk_length_to_report_frames:
186
- rate_and_depth_warnings = []
187
-
188
- self.cached_rate_and_depth_warnings = rate_and_depth_warnings
189
- self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames
190
- cpr_logger.info(f"[RUN ANALYSIS] Retrieved rate and depth warnings for the chunk")
191
-
192
- self.rate_and_depth_warnings.append({
193
- 'start_frame': self.chunk_start_frame_index,
194
- 'end_frame': self.chunk_end_frame_index,
195
- 'rate_and_depth_warnings': rate_and_depth_warnings,
196
- })
197
- cpr_logger.info(f"[RUN ANALYSIS] Assigned rate and depth warnings region data")
198
-
199
- self.shoulders_analyzer.reset_shoulder_distances()
200
- self.wrists_midpoint_analyzer.reset_midpoint_history()
201
- cpr_logger.info(f"[RUN ANALYSIS] Reset shoulder distances and midpoint history for the chunk")
202
-
203
- def _handle_posture_warnings_region_end(self):
204
- """Helper to handle posture warnings region termination"""
205
- self.posture_warnings.append({
206
- 'start_frame': self.posture_warnings_region_start_frame_index,
207
- 'end_frame': self.posture_warnings_region_end_frame_index,
208
- 'posture_warnings': self.cached_posture_warnings.copy(),
209
- })
210
- cpr_logger.info(f"[RUN ANALYSIS] Assigned posture warnings region data")
211
-
212
- def _start_new_chunk(self, chunk_type="chunk"):
213
- """Helper to initialize new chunk"""
214
- self.chunk_start_frame_index = self.frame_counter
215
- self.waiting_to_start_new_chunk = False
216
- cpr_logger.info(f"[CHUNK] New {chunk_type} started at {self.frame_counter}")
217
-
218
- def _start_new_posture_warnings_region(self):
219
- """Helper to initialize new posture warnings region"""
220
- self.posture_warnings_region_start_frame_index = self.frame_counter
221
- cpr_logger.info(f"[POSTURE WARNINGS] New region started at {self.frame_counter}")
222
-
223
  def run_analysis_video(self):
224
  try:
225
  cpr_logger.info("[RUN ANALYSIS] Starting analysis")
@@ -437,36 +365,7 @@ class CPRAnalyzer:
437
  cpr_logger.info(f"[TIMING] Report and plot elapsed time: {report_and_plot_elapsed_time:.2f}s")
438
  return self.graph_plotter._chunks_json_data
439
 
440
- def _format_warnings(self):
441
- """Combine warnings into a simple structured response"""
442
-
443
- if self.cached_posture_warnings:
444
- return {
445
- "status": "warning",
446
- "posture_warnings": self.cached_posture_warnings,
447
- "rate_and_depth_warnings": [],
448
- }
449
-
450
- if (self.cached_rate_and_depth_warnings) and (self.return_rate_and_depth_warnings_interval_frames_counter > 0):
451
- self.return_rate_and_depth_warnings_interval_frames_counter -= 1
452
-
453
- return {
454
- "status": "warning",
455
- "posture_warnings": [],
456
- "rate_and_depth_warnings": self.cached_rate_and_depth_warnings,
457
- }
458
-
459
- return {
460
- "status": "ok",
461
- "posture_warnings": [],
462
- "rate_and_depth_warnings": [],
463
- }
464
-
465
- def _handle_frame_rotation(self, frame):
466
- if frame.shape[1] > frame.shape[0]: # Width > Height
467
- frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
468
- return frame
469
-
470
  def _process_frame(self, frame):
471
  #* Warnings for real time feedback
472
  warnings = []
@@ -594,7 +493,6 @@ class CPRAnalyzer:
594
  return warnings, has_appended_midpoint
595
 
596
  def _compose_frame(self, frame, is_part_of_a_posture_warnings_region):
597
- # Chest Region
598
  if frame is not None:
599
  frame = self.chest_initializer.draw_expected_chest_region(frame)
600
  cpr_logger.info(f"[VISUALIZATION] Drawn chest region")
@@ -606,6 +504,58 @@ class CPRAnalyzer:
606
 
607
  return frame
608
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  def _calculate_rate_and_depth_for_chunk(self):
610
  try:
611
  result = self.metrics_calculator.handle_chunk(np.array(self.wrists_midpoint_analyzer.midpoint_history), self.chunk_start_frame_index, self.chunk_end_frame_index, self.fps, np.array(self.shoulders_analyzer.shoulder_distance_history), self.sampling_interval_frames)
@@ -645,41 +595,33 @@ class CPRAnalyzer:
645
 
646
  return rate_and_depth_warnings
647
 
648
- if __name__ == "__main__":
649
- cpr_logger.info(f"[MAIN] CPR Analysis Started")
650
-
651
- # Configuration
652
- requested_fps = 30
653
- input_video = r"D:\CPR_education\CPR\End to End\Code Refactor\video_2.mp4"
654
- # Validate input file exists
655
- if not os.path.exists(input_video):
656
- cpr_logger.error(f"[ERROR] Input video not found at: {input_video}")
657
- sys.exit(1)
658
-
659
- output_dir = r"D:\CPR_education\CPR\End to End\Code Refactor\Output"
660
 
661
- # Set output paths using original name
662
- video_output_path = os.path.join(output_dir, f"Myoutput.mp4")
663
- plot_output_path = os.path.join(output_dir, f"Myoutput.png")
664
-
665
- # Log paths for verification
666
- cpr_logger.info(f"[CONFIG] Input video: {input_video}")
667
- cpr_logger.info(f"[CONFIG] Video output: {video_output_path}")
668
- cpr_logger.info(f"[CONFIG] Plot output: {plot_output_path}")
669
-
670
- # Initialize and run analyzer
671
- initialization_start_time = time.time()
672
- analyzer = CPRAnalyzer(input_video, video_output_path, plot_output_path, requested_fps)
673
-
674
- # Set plot output path in the analyzer
675
- analyzer.plot_output_path = plot_output_path
676
-
677
- initialization_end_time = time.time()
678
- initialization_elapsed_time = initialization_end_time - initialization_start_time
679
- cpr_logger.info(f"[TIMING] Initialization time: {initialization_elapsed_time:.2f}s")
680
-
681
- try:
682
- data = analyzer.run_analysis()
683
- print(data)
684
- finally:
685
- cpr_logger.info(f"[MAIN] CPR Analysis Terminated")
 
 
 
1
  import cv2
2
  import time
3
  import math
4
  import numpy as np
5
  import os
 
6
 
7
  from CPR_Module.Educational_Mode.pose_estimation import PoseEstimator
8
  from CPR_Module.Educational_Mode.metrics_calculator import MetricsCalculator
 
18
  from CPR_Module.Common.logging_config import cpr_logger
19
 
20
  class CPRAnalyzer:
21
+ """Main class for analyzing CPR performance from video input."""
22
 
23
  def __init__(self, input_video, video_output_path, plot_output_path, requested_fps):
24
 
 
83
  cpr_logger.info("[INIT] Previous results initialized")
84
 
85
  #& Fundamental timing parameters (in seconds)
86
+ self.MIN_ERROR_DURATION = 0.5 # Require sustained errors for X second
87
+ self.REPORTING_INTERVAL = 5.0 # Generate reports every Y seconds
88
+ self.SAMPLING_INTERVAL = 0.2 # Analyze every Z seconds
89
+ self.KEEP_RATE_AND_DEPTH_WARNINGS_INTERVAL = 3.0 # Keep rate and depth warnings for W seconds
90
+ self.MIN_CHUNK_LENGTH_TO_REPORT = 3.0 # Minimum chunk length to report warnings
91
 
92
  # Derived frame counts
93
  self.sampling_interval_frames = int(round(self.fps * self.SAMPLING_INTERVAL))
 
124
  self.consecutive_frames_with_posture_errors_counters = {warning: 0 for warning in self.possible_warnings}
125
 
126
  #& Initialize variables for reporting warnings
 
127
  self.rate_and_depth_warnings_from_the_last_report = []
128
  cpr_logger.info("[INIT] Rate and depth warnings from the last report initialized")
129
 
 
148
  self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames
149
  cpr_logger.info("[INIT] Formatted warnings initialized")
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  def run_analysis_video(self):
152
  try:
153
  cpr_logger.info("[RUN ANALYSIS] Starting analysis")
 
365
  cpr_logger.info(f"[TIMING] Report and plot elapsed time: {report_and_plot_elapsed_time:.2f}s")
366
  return self.graph_plotter._chunks_json_data
367
 
368
+ #^############################## Frame Processing & Composition ##############################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  def _process_frame(self, frame):
370
  #* Warnings for real time feedback
371
  warnings = []
 
493
  return warnings, has_appended_midpoint
494
 
495
  def _compose_frame(self, frame, is_part_of_a_posture_warnings_region):
 
496
  if frame is not None:
497
  frame = self.chest_initializer.draw_expected_chest_region(frame)
498
  cpr_logger.info(f"[VISUALIZATION] Drawn chest region")
 
504
 
505
  return frame
506
 
507
+ #^############################## Start/End of Chunk/Posture Warnings Region Handling ##############################
508
+ def _handle_chunk_end(self):
509
+ """Helper to handle chunk termination logic"""
510
+
511
+ self._calculate_rate_and_depth_for_chunk()
512
+ cpr_logger.info(f"[RUN ANALYSIS] Calculated rate and depth for the chunk")
513
+
514
+ rate_and_depth_warnings = self._get_rate_and_depth_warnings()
515
+
516
+ # If the chunk is too short, we don't want to report any warnings it might contain.
517
+ if (self.chunk_end_frame_index - self.chunk_start_frame_index) < self.min_chunk_length_to_report_frames:
518
+ rate_and_depth_warnings = []
519
+
520
+ self.cached_rate_and_depth_warnings = rate_and_depth_warnings
521
+ self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames
522
+ cpr_logger.info(f"[RUN ANALYSIS] Retrieved rate and depth warnings for the chunk")
523
+
524
+ self.rate_and_depth_warnings.append({
525
+ 'start_frame': self.chunk_start_frame_index,
526
+ 'end_frame': self.chunk_end_frame_index,
527
+ 'rate_and_depth_warnings': rate_and_depth_warnings,
528
+ })
529
+ cpr_logger.info(f"[RUN ANALYSIS] Assigned rate and depth warnings region data")
530
+
531
+ self.shoulders_analyzer.reset_shoulder_distances()
532
+ self.wrists_midpoint_analyzer.reset_midpoint_history()
533
+ cpr_logger.info(f"[RUN ANALYSIS] Reset shoulder distances and midpoint history for the chunk")
534
+
535
+ def _handle_posture_warnings_region_end(self):
536
+ """Helper to handle posture warnings region termination"""
537
+
538
+ self.posture_warnings.append({
539
+ 'start_frame': self.posture_warnings_region_start_frame_index,
540
+ 'end_frame': self.posture_warnings_region_end_frame_index,
541
+ 'posture_warnings': self.cached_posture_warnings.copy(),
542
+ })
543
+ cpr_logger.info(f"[RUN ANALYSIS] Assigned posture warnings region data")
544
+
545
+ def _start_new_chunk(self, chunk_type="chunk"):
546
+ """Helper to initialize new chunk"""
547
+
548
+ self.chunk_start_frame_index = self.frame_counter
549
+ self.waiting_to_start_new_chunk = False
550
+ cpr_logger.info(f"[CHUNK] New {chunk_type} started at {self.frame_counter}")
551
+
552
+ def _start_new_posture_warnings_region(self):
553
+ """Helper to initialize new posture warnings region"""
554
+
555
+ self.posture_warnings_region_start_frame_index = self.frame_counter
556
+ cpr_logger.info(f"[POSTURE WARNINGS] New region started at {self.frame_counter}")
557
+
558
+ #^############################## Rate & Depth Calculations ##############################
559
  def _calculate_rate_and_depth_for_chunk(self):
560
  try:
561
  result = self.metrics_calculator.handle_chunk(np.array(self.wrists_midpoint_analyzer.midpoint_history), self.chunk_start_frame_index, self.chunk_end_frame_index, self.fps, np.array(self.shoulders_analyzer.shoulder_distance_history), self.sampling_interval_frames)
 
595
 
596
  return rate_and_depth_warnings
597
 
598
+ #^############################## Video Writer ##############################
599
+ def _initialize_video_writer(self, frame):
600
+ """Initialize writer with safe fallback options"""
 
 
 
 
 
 
 
 
 
601
 
602
+ height, width = frame.shape[:2]
603
+ effective_fps = self.fps / max(1, self.sampling_interval_frames)
604
+
605
+ # Try different codec/container combinations
606
+ for codec, ext, fmt in [('avc1', 'mp4', 'mp4v'), # H.264
607
+ ('MJPG', 'avi', 'avi'),
608
+ ('XVID', 'avi', 'avi')]:
609
+ fourcc = cv2.VideoWriter_fourcc(*codec)
610
+ writer = cv2.VideoWriter(self.video_output_path, fourcc, effective_fps, (width, height))
611
+
612
+ if writer.isOpened():
613
+ self.video_writer = writer
614
+ self._writer_initialized = True
615
+ cpr_logger.info(f"[VIDEO WRITER] Initialized with {codec} codec")
616
+ return
617
+ else:
618
+ writer.release()
619
+
620
+ cpr_logger.info("[ERROR] Failed to initialize any video writer!")
621
+ self._writer_initialized = False
622
+
623
+ #^############################## Frame Rotation ##############################
624
+ def _handle_frame_rotation(self, frame):
625
+ if frame.shape[1] > frame.shape[0]: # Width > Height
626
+ frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
627
+ return frame
CPR_Module/Educational_Mode/graph_plotter.py CHANGED
@@ -1,14 +1,12 @@
1
  import numpy as np
2
  import matplotlib.pyplot as plt
3
- import sys
4
- import cv2
5
- from CPR_Module.Common.logging_config import cpr_logger
6
  import os
7
 
8
- from matplotlib.ticker import MultipleLocator
9
 
10
  class GraphPlotter:
11
- """Class to plot graphs for various metrics"""
12
 
13
  def __init__(self):
14
  self.chunks_y_preprocessed = []
@@ -33,6 +31,7 @@ class GraphPlotter:
33
 
34
  def _assign_graph_data(self, chunks_y_preprocessed, chunks_peaks, chunks_depth, chunks_rate, chunks_start_and_end_indices, posture_warnings_regions, sampling_interval_in_frames, fps):
35
  """Assign data members for the class"""
 
36
  self.chunks_y_preprocessed = chunks_y_preprocessed
37
  self.chunks_peaks = chunks_peaks
38
  self.chunks_depth = chunks_depth
@@ -40,12 +39,14 @@ class GraphPlotter:
40
  self.chunks_start_and_end_indices = chunks_start_and_end_indices
41
  self.posture_warnings_regions = posture_warnings_regions
42
  self.sampling_interval_in_frames = sampling_interval_in_frames
43
- self.fps = fps # Store FPS
44
 
45
  cpr_logger.info(f"[Graph Plotter] Data members assigned with {len(self.chunks_start_and_end_indices)} chunks and {len(self.posture_warnings_regions)} error regions for a sampling interval of {self.sampling_interval_in_frames} frames and FPS {self.fps}")
46
 
47
  def _plot_single_chunk(self, ax, chunk, idx, prev_last_point, prev_chunk_end):
 
48
  (start_frame, end_frame), depth, rate = chunk
 
49
  # Convert frames to time
50
  chunk_frames = np.arange(start_frame, end_frame + 1, self.sampling_interval_in_frames)
51
  chunk_times = chunk_frames / self.fps # Convert to seconds
@@ -108,11 +109,12 @@ class GraphPlotter:
108
 
109
  def _plot_error_regions(self, ax, computed_error_regions):
110
  """Visualize error regions with adaptive symbol sizing"""
 
111
  cpr_logger.info("[Graph Plotter] Rendering error regions:")
112
 
113
  # Size parameters
114
- target_width_ratio = 0.7 # Max 80% of region width
115
- legend_size = 80 # Fixed legend symbol size (points²)
116
 
117
  legend_handles = []
118
  y_mid = np.mean(ax.get_ylim())
@@ -268,6 +270,7 @@ class GraphPlotter:
268
 
269
  def _print_analysis_details(self, sorted_chunks):
270
  """Combined helper for printing chunks and error regions in seconds"""
 
271
  cpr_logger.info(f"\n\n=== CPR Chunk Analysis ===")
272
  display_idx = 0 # Separate counter for displayed indices
273
 
@@ -288,14 +291,13 @@ class GraphPlotter:
288
  f"Time {start_sec:.2f}s - {end_sec:.2f}s ({duration_sec:.2f}s), "
289
  f"Depth: {depth:.1f}cm, Rate: {rate:.1f}cpm")
290
 
291
- #! Formatted json to mobile
292
-
293
  chunk_data = {
294
  "start": round(start_sec, 2),
295
  "end": round(end_sec, 2),
296
  "depth": round(depth, 1),
297
  "rate": round(rate, 1)
298
- }
299
  self._chunks_json_data.append(chunk_data)
300
 
301
  display_idx += 1
 
1
  import numpy as np
2
  import matplotlib.pyplot as plt
3
+ from matplotlib.ticker import MultipleLocator
 
 
4
  import os
5
 
6
+ from CPR_Module.Common.logging_config import cpr_logger
7
 
8
  class GraphPlotter:
9
+ """Class to handle plotting of CPR motion curves and error regions with detailed metrics."""
10
 
11
  def __init__(self):
12
  self.chunks_y_preprocessed = []
 
31
 
32
  def _assign_graph_data(self, chunks_y_preprocessed, chunks_peaks, chunks_depth, chunks_rate, chunks_start_and_end_indices, posture_warnings_regions, sampling_interval_in_frames, fps):
33
  """Assign data members for the class"""
34
+
35
  self.chunks_y_preprocessed = chunks_y_preprocessed
36
  self.chunks_peaks = chunks_peaks
37
  self.chunks_depth = chunks_depth
 
39
  self.chunks_start_and_end_indices = chunks_start_and_end_indices
40
  self.posture_warnings_regions = posture_warnings_regions
41
  self.sampling_interval_in_frames = sampling_interval_in_frames
42
+ self.fps = fps
43
 
44
  cpr_logger.info(f"[Graph Plotter] Data members assigned with {len(self.chunks_start_and_end_indices)} chunks and {len(self.posture_warnings_regions)} error regions for a sampling interval of {self.sampling_interval_in_frames} frames and FPS {self.fps}")
45
 
46
  def _plot_single_chunk(self, ax, chunk, idx, prev_last_point, prev_chunk_end):
47
+
48
  (start_frame, end_frame), depth, rate = chunk
49
+
50
  # Convert frames to time
51
  chunk_frames = np.arange(start_frame, end_frame + 1, self.sampling_interval_in_frames)
52
  chunk_times = chunk_frames / self.fps # Convert to seconds
 
109
 
110
  def _plot_error_regions(self, ax, computed_error_regions):
111
  """Visualize error regions with adaptive symbol sizing"""
112
+
113
  cpr_logger.info("[Graph Plotter] Rendering error regions:")
114
 
115
  # Size parameters
116
+ target_width_ratio = 0.7
117
+ legend_size = 80
118
 
119
  legend_handles = []
120
  y_mid = np.mean(ax.get_ylim())
 
270
 
271
  def _print_analysis_details(self, sorted_chunks):
272
  """Combined helper for printing chunks and error regions in seconds"""
273
+
274
  cpr_logger.info(f"\n\n=== CPR Chunk Analysis ===")
275
  display_idx = 0 # Separate counter for displayed indices
276
 
 
291
  f"Time {start_sec:.2f}s - {end_sec:.2f}s ({duration_sec:.2f}s), "
292
  f"Depth: {depth:.1f}cm, Rate: {rate:.1f}cpm")
293
 
294
+ #& Formatted json to mobile
 
295
  chunk_data = {
296
  "start": round(start_sec, 2),
297
  "end": round(end_sec, 2),
298
  "depth": round(depth, 1),
299
  "rate": round(rate, 1)
300
+ }
301
  self._chunks_json_data.append(chunk_data)
302
 
303
  display_idx += 1
CPR_Module/Educational_Mode/metrics_calculator.py CHANGED
@@ -1,10 +1,6 @@
1
- # metrics_calculator.py
2
  import numpy as np
3
  from scipy.signal import savgol_filter, find_peaks
4
- import matplotlib.pyplot as plt
5
  import sys
6
- import cv2
7
- import os
8
 
9
  from CPR_Module.Common.logging_config import cpr_logger
10
 
@@ -85,11 +81,13 @@ class MetricsCalculator:
85
  cpr_logger.info(f"\nERROR: Mismatch in expected and actual samples")
86
  cpr_logger.info(f"Expected: {expected_samples} samples (frames {start}-{end} @ every {interval} frames)")
87
  cpr_logger.info(f"Actual: {actual_y_exact_length} midoints points recieived")
88
- sys.exit(1)
 
 
89
 
90
  except Exception as e:
91
  cpr_logger.error(f"\nCRITICAL VALIDATION ERROR: {str(e)}")
92
- sys.exit(1)
93
 
94
  #^ ################# Preprocessing #######################
95
 
@@ -407,7 +405,7 @@ class MetricsCalculator:
407
  """
408
 
409
  # The program is terminated if the validation fails
410
- self.validate_midpoints_and_frames_count_in_chunk(midpoints, chunk_start_frame_index, chunk_end_frame_index, sampling_interval_in_frames)
411
 
412
  preprocessing_reult = self.preprocess_midpoints(midpoints)
413
  if not preprocessing_reult:
@@ -448,23 +446,3 @@ class MetricsCalculator:
448
  self.assign_chunk_data(chunk_start_frame_index, chunk_end_frame_index)
449
  cpr_logger.info(f"Chunk {chunk_start_frame_index}-{chunk_end_frame_index} processed successfully")
450
  return True
451
-
452
- #^ ################# Comments #######################
453
- # Between every two consecutive mini chunks, there wil be "sampling interval" frames unaccounted for.
454
- # This is because when we reach the "reporting interval" number of frames, we terminate the first mini chunk.
455
- # But we only start the next mini chunk when we detect the next successfully processed frame.
456
- # Which is "sampling interval" frames later at the earliest.
457
- # We can't just initialize the next mini chunk at the "reporting interval" frame, because we need to wait for the next successful frame.
458
- # Becuase maybe the next frame is a frame with posture errors.
459
- # For better visualization, we connect between the last point of the previous chunk and the first point of the next chunk if they are "sampling interval" frames apart.
460
- # But that is only for visualization, all calculations are done on the original frames.
461
-
462
- # Chunks that are too short can fail any stage of the "handle chunk" process.
463
- # If they do, we vizualize what we have and ignore the rest.
464
- # For example, a chunk with < 2 peaks will not be able to calculate the rate.
465
- # So we will set it to zero and display the midpoints and detected peaks.
466
- # If there are no peaks, we will set the rate to zero and display the midpoints.
467
-
468
- # Problems with chunks could be:
469
- # - Less than 3 seconds.
470
- # - Not enough peaks to calculate depth and rate
 
 
1
  import numpy as np
2
  from scipy.signal import savgol_filter, find_peaks
 
3
  import sys
 
 
4
 
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
 
81
  cpr_logger.info(f"\nERROR: Mismatch in expected and actual samples")
82
  cpr_logger.info(f"Expected: {expected_samples} samples (frames {start}-{end} @ every {interval} frames)")
83
  cpr_logger.info(f"Actual: {actual_y_exact_length} midoints points recieived")
84
+ return np.array([])
85
+
86
+ return y_exact
87
 
88
  except Exception as e:
89
  cpr_logger.error(f"\nCRITICAL VALIDATION ERROR: {str(e)}")
90
+ return np.array([])
91
 
92
  #^ ################# Preprocessing #######################
93
 
 
405
  """
406
 
407
  # The program is terminated if the validation fails
408
+ midpoints = self.validate_midpoints_and_frames_count_in_chunk(midpoints, chunk_start_frame_index, chunk_end_frame_index, sampling_interval_in_frames)
409
 
410
  preprocessing_reult = self.preprocess_midpoints(midpoints)
411
  if not preprocessing_reult:
 
446
  self.assign_chunk_data(chunk_start_frame_index, chunk_end_frame_index)
447
  cpr_logger.info(f"Chunk {chunk_start_frame_index}-{chunk_end_frame_index} processed successfully")
448
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CPR_Module/Educational_Mode/pose_estimation.py CHANGED
@@ -1,9 +1,6 @@
1
- # pose_estimation.py
2
- import cv2
3
  import numpy as np
4
  from ultralytics import YOLO
5
 
6
- from CPR_Module.Common.keypoints import CocoKeypoints
7
  from CPR_Module.Common.logging_config import cpr_logger
8
 
9
  class PoseEstimator:
 
 
 
1
  import numpy as np
2
  from ultralytics import YOLO
3
 
 
4
  from CPR_Module.Common.logging_config import cpr_logger
5
 
6
  class PoseEstimator:
CPR_Module/Emergency_Mode/graph_plotter.py CHANGED
@@ -1,7 +1,5 @@
1
  import numpy as np
2
  import matplotlib.pyplot as plt
3
- import sys
4
- import cv2
5
  from matplotlib.ticker import MultipleLocator
6
  import os
7
 
 
1
  import numpy as np
2
  import matplotlib.pyplot as plt
 
 
3
  from matplotlib.ticker import MultipleLocator
4
  import os
5
 
CPR_Module/Emergency_Mode/main.py CHANGED
@@ -1,9 +1,8 @@
1
- # main.py
2
  import cv2
3
  import time
4
  import math
5
  import numpy as np
6
- import os # Added for path handling
7
  import sys
8
 
9
  from CPR_Module.Emergency_Mode.pose_estimation import PoseEstimator
@@ -698,51 +697,3 @@ class CPRAnalyzer:
698
  cpr_logger.info(f"[VISUALIZATION] Rate and depth warnings data: {rate_and_depth_warnings}")
699
 
700
  return rate_and_depth_warnings
701
-
702
- if __name__ == "__main__":
703
- cpr_logger.info(f"[MAIN] CPR Analysis Started")
704
-
705
- # Configuration
706
- requested_fps = 30
707
- base_dir = r"C:\Users\Fatema Kotb\Documents\CUFE 25\Year 04\GP\Spring\El7a2ny-Graduation-Project"
708
-
709
- # Define input path
710
- input_video = os.path.join(base_dir, "CPR", "Dataset", "Batch 2", "14.mp4")
711
-
712
- # Validate input file exists
713
- if not os.path.exists(input_video):
714
- cpr_logger.error(f"[ERROR] Input video not found at: {input_video}")
715
- sys.exit(1)
716
-
717
- # Extract original filename without extension
718
- original_name = os.path.splitext(os.path.basename(input_video))[0]
719
- cpr_logger.info(f"[CONFIG] Original video name: {original_name}")
720
-
721
- # Create output directory if it doesn't exist
722
- output_dir = os.path.join(base_dir, "CPR", "End to End", "Code Refactor", "Output")
723
- os.makedirs(output_dir, exist_ok=True)
724
-
725
- # Set output paths using original name
726
- video_output_path = os.path.join(output_dir, f"{original_name}_output.mp4")
727
- plot_output_path = os.path.join(output_dir, f"{original_name}_output.png")
728
-
729
- # Log paths for verification
730
- cpr_logger.info(f"[CONFIG] Input video: {input_video}")
731
- cpr_logger.info(f"[CONFIG] Video output: {video_output_path}")
732
- cpr_logger.info(f"[CONFIG] Plot output: {plot_output_path}")
733
-
734
- # Initialize and run analyzer
735
- initialization_start_time = time.time()
736
- analyzer = CPRAnalyzer(input_video, video_output_path, plot_output_path, requested_fps)
737
-
738
- # Set plot output path in the analyzer
739
- analyzer.plot_output_path = plot_output_path
740
-
741
- initialization_end_time = time.time()
742
- initialization_elapsed_time = initialization_end_time - initialization_start_time
743
- cpr_logger.info(f"[TIMING] Initialization time: {initialization_elapsed_time:.2f}s")
744
-
745
- try:
746
- analyzer.run_analysis()
747
- finally:
748
- analyzer.socket_server.stop_server()
 
 
1
  import cv2
2
  import time
3
  import math
4
  import numpy as np
5
+ import os
6
  import sys
7
 
8
  from CPR_Module.Emergency_Mode.pose_estimation import PoseEstimator
 
697
  cpr_logger.info(f"[VISUALIZATION] Rate and depth warnings data: {rate_and_depth_warnings}")
698
 
699
  return rate_and_depth_warnings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CPR_Module/Emergency_Mode/metrics_calculator.py CHANGED
@@ -1,10 +1,6 @@
1
- # metrics_calculator.py
2
  import numpy as np
3
  from scipy.signal import savgol_filter, find_peaks
4
- import matplotlib.pyplot as plt
5
  import sys
6
- import cv2
7
- import os
8
 
9
  from CPR_Module.Common.logging_config import cpr_logger
10
 
@@ -85,11 +81,13 @@ class MetricsCalculator:
85
  cpr_logger.info(f"\nERROR: Mismatch in expected and actual samples")
86
  cpr_logger.info(f"Expected: {expected_samples} samples (frames {start}-{end} @ every {interval} frames)")
87
  cpr_logger.info(f"Actual: {actual_y_exact_length} midoints points recieived")
88
- sys.exit(1)
 
 
89
 
90
  except Exception as e:
91
  cpr_logger.error(f"\nCRITICAL VALIDATION ERROR: {str(e)}")
92
- sys.exit(1)
93
 
94
  #^ ################# Preprocessing #######################
95
 
@@ -454,23 +452,3 @@ class MetricsCalculator:
454
  self.assign_chunk_data(chunk_start_frame_index, chunk_end_frame_index)
455
  cpr_logger.info(f"Chunk {chunk_start_frame_index}-{chunk_end_frame_index} processed successfully")
456
  return True
457
-
458
- #^ ################# Comments #######################
459
- # Between every two consecutive mini chunks, there wil be "sampling interval" frames unaccounted for.
460
- # This is because when we reach the "reporting interval" number of frames, we terminate the first mini chunk.
461
- # But we only start the next mini chunk when we detect the next successfully processed frame.
462
- # Which is "sampling interval" frames later at the earliest.
463
- # We can't just initialize the next mini chunk at the "reporting interval" frame, because we need to wait for the next successful frame.
464
- # Becuase maybe the next frame is a frame with posture errors.
465
- # For better visualization, we connect between the last point of the previous chunk and the first point of the next chunk if they are "sampling interval" frames apart.
466
- # But that is only for visualization, all calculations are done on the original frames.
467
-
468
- # Chunks that are too short can fail any stage of the "handle chunk" process.
469
- # If they do, we vizualize what we have and ignore the rest.
470
- # For example, a chunk with < 2 peaks will not be able to calculate the rate.
471
- # So we will set it to zero and display the midpoints and detected peaks.
472
- # If there are no peaks, we will set the rate to zero and display the midpoints.
473
-
474
- # Problems with chunks could be:
475
- # - Less than 3 seconds.
476
- # - Not enough peaks to calculate depth and rate
 
 
1
  import numpy as np
2
  from scipy.signal import savgol_filter, find_peaks
 
3
  import sys
 
 
4
 
5
  from CPR_Module.Common.logging_config import cpr_logger
6
 
 
81
  cpr_logger.info(f"\nERROR: Mismatch in expected and actual samples")
82
  cpr_logger.info(f"Expected: {expected_samples} samples (frames {start}-{end} @ every {interval} frames)")
83
  cpr_logger.info(f"Actual: {actual_y_exact_length} midoints points recieived")
84
+ return np.array([]) # Return empty array to indicate failure
85
+
86
+ return y_exact # Return the exact y-values if validation passes
87
 
88
  except Exception as e:
89
  cpr_logger.error(f"\nCRITICAL VALIDATION ERROR: {str(e)}")
90
+ return np.array([]) # Return empty array to indicate failure
91
 
92
  #^ ################# Preprocessing #######################
93
 
 
452
  self.assign_chunk_data(chunk_start_frame_index, chunk_end_frame_index)
453
  cpr_logger.info(f"Chunk {chunk_start_frame_index}-{chunk_end_frame_index} processed successfully")
454
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CPR_Module/Emergency_Mode/pose_estimation.py CHANGED
@@ -1,9 +1,5 @@
1
- # pose_estimation.py
2
- import cv2
3
- import numpy as np
4
  from ultralytics import YOLO
5
 
6
- from CPR_Module.Common.keypoints import CocoKeypoints
7
  from CPR_Module.Common.logging_config import cpr_logger
8
 
9
  class PoseEstimator:
 
 
 
 
1
  from ultralytics import YOLO
2
 
 
3
  from CPR_Module.Common.logging_config import cpr_logger
4
 
5
  class PoseEstimator:
README.md CHANGED
@@ -1,5 +1,3 @@
1
- ![Alt text](ReamdMeAssets/El7a2ny.png)
2
-
3
  Ever wondered what you'd do if someone **collapsed** in front of you?<br>
4
  El7a2ny is a mobile app that trains you to handle medical emergencies and guides you step-by-step as they happen.<br>
5
  No medical degree needed — just follow the app, stay calm, and become the hero someone desperately needs.<br>
 
 
 
1
  Ever wondered what you'd do if someone **collapsed** in front of you?<br>
2
  El7a2ny is a mobile app that trains you to handle medical emergencies and guides you step-by-step as they happen.<br>
3
  No medical degree needed — just follow the app, stay calm, and become the hero someone desperately needs.<br>