Commit Β·
cfbaa51
1
Parent(s): d26e0b7
CPR Code Refactor
Browse files- .gitignore +1 -0
- CPR/client.py +0 -34
- CPR/posture_analyzer.py +0 -132
- CPR/shoulders_analyzer.py +0 -30
- CPR/threaded_camera.py +0 -103
- CPRRealTime/analysis_socket_server.py +0 -65
- CPRRealTime/chest_initializer.py +0 -154
- CPRRealTime/client.py +0 -34
- CPRRealTime/keypoints.py +0 -22
- CPRRealTime/logging_config.py +0 -20
- CPRRealTime/role_classifier.py +0 -178
- CPRRealTime/warnings_overlayer.py +0 -147
- CPRRealTime/wrists_midpoint_analyzer.py +0 -63
- CPRRealTime/yolo11n-pose.pt +0 -3
- CPR_Module/Common/__init__.py +0 -0
- {CPR β CPR_Module/Common}/analysis_socket_server.py +2 -1
- {CPR β CPR_Module/Common}/chest_initializer.py +3 -2
- {CPR β CPR_Module/Common}/keypoints.py +0 -0
- {CPR β CPR_Module/Common}/logging_config.py +0 -0
- {CPRRealTime β CPR_Module/Common}/posture_analyzer.py +3 -2
- {CPR β CPR_Module/Common}/role_classifier.py +2 -2
- {CPRRealTime β CPR_Module/Common}/shoulders_analyzer.py +3 -2
- {CPRRealTime β CPR_Module/Common}/threaded_camera.py +2 -1
- {CPR β CPR_Module/Common}/warnings_overlayer.py +1 -1
- {CPR β CPR_Module/Common}/wrists_midpoint_analyzer.py +3 -2
- {CPR β CPR_Module/Common}/yolo11n-pose.pt +0 -0
- {CPR β CPR_Module/Educational_Mode}/CPRAnalyzer.py +13 -12
- CPR_Module/Educational_Mode/__init__.py +0 -0
- {CPR β CPR_Module/Educational_Mode}/graph_plotter.py +3 -2
- {CPR β CPR_Module/Educational_Mode}/metrics_calculator.py +2 -1
- {CPR β CPR_Module/Educational_Mode}/pose_estimation.py +4 -3
- CPR_Module/Emergency_Mode/__init__.py +0 -0
- {CPRRealTime β CPR_Module/Emergency_Mode}/graph_plotter.py +2 -1
- {CPRRealTime β CPR_Module/Emergency_Mode}/main.py +13 -12
- {CPRRealTime β CPR_Module/Emergency_Mode}/metrics_calculator.py +2 -1
- {CPRRealTime β CPR_Module/Emergency_Mode}/pose_estimation.py +6 -6
- CPR_Module/__init__.py +0 -0
- Demo/__init__.py +0 -0
- Demo/demo.py +75 -0
- README.md +168 -40
- app.py +4 -4
.gitignore
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
__pycache__/*
|
| 2 |
venv/*
|
|
|
|
| 3 |
uploads/*
|
| 4 |
runs/*
|
| 5 |
screenshots/*
|
|
|
|
| 1 |
__pycache__/*
|
| 2 |
venv/*
|
| 3 |
+
backendEnv/*
|
| 4 |
uploads/*
|
| 5 |
runs/*
|
| 6 |
screenshots/*
|
CPR/client.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
import socket
|
| 2 |
-
import json
|
| 3 |
-
|
| 4 |
-
from CPR.logging_config import cpr_logger
|
| 5 |
-
|
| 6 |
-
HOST = 'localhost' # The server's hostname or IP address
|
| 7 |
-
PORT = 5000 # The port used by the server
|
| 8 |
-
|
| 9 |
-
#! Not an error
|
| 10 |
-
|
| 11 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 12 |
-
s.connect((HOST, PORT))
|
| 13 |
-
#^ Set as an error for cleaner logging purposes
|
| 14 |
-
cpr_logger.error(f"Connected to {HOST}:{PORT}")
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
while True:
|
| 18 |
-
data = s.recv(1024)
|
| 19 |
-
if not data:
|
| 20 |
-
break
|
| 21 |
-
|
| 22 |
-
# Split messages (in case multiple JSONs in buffer)
|
| 23 |
-
for line in data.decode('utf-8').split('\n'):
|
| 24 |
-
if line.strip():
|
| 25 |
-
try:
|
| 26 |
-
warnings = json.loads(line)
|
| 27 |
-
cpr_logger.error("\nReceived warnings:")
|
| 28 |
-
cpr_logger.error(f"Status: {warnings['status']}")
|
| 29 |
-
cpr_logger.error(f"Posture Warnings: {warnings['posture_warnings']}")
|
| 30 |
-
cpr_logger.error(f"Rate/Depth Warnings: {warnings['rate_and_depth_warnings']}")
|
| 31 |
-
except json.JSONDecodeError:
|
| 32 |
-
cpr_logger.error("Received invalid JSON")
|
| 33 |
-
except KeyboardInterrupt:
|
| 34 |
-
cpr_logger.error("Disconnecting...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPR/posture_analyzer.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
| 1 |
-
# posture_analyzer.py
|
| 2 |
-
import math
|
| 3 |
-
import cv2
|
| 4 |
-
import numpy as np
|
| 5 |
-
from CPR.keypoints import CocoKeypoints
|
| 6 |
-
from CPR.logging_config import cpr_logger
|
| 7 |
-
|
| 8 |
-
class PostureAnalyzer:
|
| 9 |
-
"""Posture analysis and visualization with comprehensive validation"""
|
| 10 |
-
|
| 11 |
-
def __init__(self, right_arm_angle_threshold, left_arm_angle_threshold, wrist_distance_threshold, history_length_to_average):
|
| 12 |
-
self.history_length_to_average = history_length_to_average
|
| 13 |
-
|
| 14 |
-
self.right_arm_angles = []
|
| 15 |
-
self.left_arm_angles = []
|
| 16 |
-
self.wrist_distances = []
|
| 17 |
-
|
| 18 |
-
self.right_arm_angle_threshold = right_arm_angle_threshold
|
| 19 |
-
self.left_arm_angle_threshold = left_arm_angle_threshold
|
| 20 |
-
self.wrist_distance_threshold = wrist_distance_threshold
|
| 21 |
-
|
| 22 |
-
def _calculate_angle(self, a, b, c):
|
| 23 |
-
"""Calculate angle between three points"""
|
| 24 |
-
try:
|
| 25 |
-
ang = math.degrees(math.atan2(c[1]-b[1], c[0]-b[0]) -
|
| 26 |
-
math.atan2(a[1]-b[1], a[0]-b[0]))
|
| 27 |
-
return ang + 360 if ang < 0 else ang
|
| 28 |
-
except Exception as e:
|
| 29 |
-
cpr_logger.error(f"Angle calculation error: {e}")
|
| 30 |
-
return 0
|
| 31 |
-
|
| 32 |
-
def _check_bended_right_arm(self, keypoints):
|
| 33 |
-
"""Check for right arm bending (returns warning)"""
|
| 34 |
-
warnings = []
|
| 35 |
-
try:
|
| 36 |
-
shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
|
| 37 |
-
elbow = keypoints[CocoKeypoints.RIGHT_ELBOW.value]
|
| 38 |
-
wrist = keypoints[CocoKeypoints.RIGHT_WRIST.value]
|
| 39 |
-
|
| 40 |
-
right_angle = self._calculate_angle(wrist, elbow, shoulder)
|
| 41 |
-
|
| 42 |
-
self.right_arm_angles.append(right_angle)
|
| 43 |
-
|
| 44 |
-
avg_right = np.mean(self.right_arm_angles[-self.history_length_to_average:] if self.right_arm_angles else 0)
|
| 45 |
-
|
| 46 |
-
if avg_right > self.right_arm_angle_threshold:
|
| 47 |
-
warnings.append("Right arm bent!")
|
| 48 |
-
|
| 49 |
-
return warnings
|
| 50 |
-
|
| 51 |
-
except Exception as e:
|
| 52 |
-
cpr_logger.error(f"Right arm check error: {e}")
|
| 53 |
-
|
| 54 |
-
return warnings
|
| 55 |
-
|
| 56 |
-
def _check_bended_left_arm(self, keypoints):
|
| 57 |
-
"""Check for left arm bending (returns warning)"""
|
| 58 |
-
warnings = []
|
| 59 |
-
try:
|
| 60 |
-
shoulder = keypoints[CocoKeypoints.LEFT_SHOULDER.value]
|
| 61 |
-
elbow = keypoints[CocoKeypoints.LEFT_ELBOW.value]
|
| 62 |
-
wrist = keypoints[CocoKeypoints.LEFT_WRIST.value]
|
| 63 |
-
|
| 64 |
-
left_angle = self._calculate_angle(wrist, elbow, shoulder)
|
| 65 |
-
|
| 66 |
-
self.left_arm_angles.append(left_angle)
|
| 67 |
-
|
| 68 |
-
avg_left = np.mean(self.left_arm_angles[-self.history_length_to_average:] if self.left_arm_angles else 0)
|
| 69 |
-
|
| 70 |
-
if avg_left < self.left_arm_angle_threshold:
|
| 71 |
-
warnings.append("Left arm bent!")
|
| 72 |
-
|
| 73 |
-
return warnings
|
| 74 |
-
|
| 75 |
-
except Exception as e:
|
| 76 |
-
cpr_logger.error(f"Left arm check error: {e}")
|
| 77 |
-
|
| 78 |
-
return warnings
|
| 79 |
-
|
| 80 |
-
def _check_hands_on_chest(self, keypoints, chest_params):
|
| 81 |
-
"""Check individual hand positions and return specific warnings"""
|
| 82 |
-
|
| 83 |
-
# Get the wrist keypoints
|
| 84 |
-
left_wrist = keypoints[CocoKeypoints.LEFT_WRIST.value]
|
| 85 |
-
right_wrist = keypoints[CocoKeypoints.RIGHT_WRIST.value]
|
| 86 |
-
|
| 87 |
-
warnings = []
|
| 88 |
-
try:
|
| 89 |
-
if chest_params is None:
|
| 90 |
-
return ["Both hands not on chest!"] # Fallback warning
|
| 91 |
-
|
| 92 |
-
cx, cy, cw, ch = chest_params
|
| 93 |
-
left_in = right_in = False
|
| 94 |
-
|
| 95 |
-
# Check left hand
|
| 96 |
-
if left_wrist is not None:
|
| 97 |
-
left_in = (cx - cw/2 < left_wrist[0] < cx + cw/2) and \
|
| 98 |
-
(cy - ch/2 < left_wrist[1] < cy + ch/2)
|
| 99 |
-
|
| 100 |
-
# Check right hand
|
| 101 |
-
if right_wrist is not None:
|
| 102 |
-
right_in = (cx - cw/2 < right_wrist[0] < cx + cw/2) and \
|
| 103 |
-
(cy - ch/2 < right_wrist[1] < cy + ch/2)
|
| 104 |
-
|
| 105 |
-
# Determine warnings
|
| 106 |
-
if not left_in and not right_in:
|
| 107 |
-
warnings.append("Both hands not on chest!")
|
| 108 |
-
else:
|
| 109 |
-
if not left_in:
|
| 110 |
-
warnings.append("Left hand not on chest!")
|
| 111 |
-
if not right_in:
|
| 112 |
-
warnings.append("Right hand not on chest!")
|
| 113 |
-
|
| 114 |
-
except Exception as e:
|
| 115 |
-
cpr_logger.error(f"Hands check error: {e}")
|
| 116 |
-
|
| 117 |
-
return warnings
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
def validate_posture(self, keypoints, chest_params):
|
| 121 |
-
"""Run all posture validations (returns aggregated warnings)"""
|
| 122 |
-
warnings = []
|
| 123 |
-
|
| 124 |
-
warnings += self._check_hands_on_chest(keypoints, chest_params)
|
| 125 |
-
|
| 126 |
-
if ("Right hand not on chest!" not in warnings) and ("Both hands not on chest!" not in warnings):
|
| 127 |
-
warnings += self._check_bended_right_arm(keypoints)
|
| 128 |
-
|
| 129 |
-
if ("Left hand not on chest!" not in warnings) and ("Both hands not on chest!" not in warnings):
|
| 130 |
-
warnings += self._check_bended_left_arm(keypoints)
|
| 131 |
-
|
| 132 |
-
return warnings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPR/shoulders_analyzer.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
-
from CPR.keypoints import CocoKeypoints
|
| 3 |
-
from CPR.logging_config import cpr_logger
|
| 4 |
-
|
| 5 |
-
class ShouldersAnalyzer:
|
| 6 |
-
"""Analyzes shoulder distances and posture"""
|
| 7 |
-
|
| 8 |
-
def __init__(self):
|
| 9 |
-
self.shoulder_distance = None
|
| 10 |
-
self.shoulder_distance_history = []
|
| 11 |
-
|
| 12 |
-
def calculate_shoulder_distance(self, rescuer_keypoints):
|
| 13 |
-
"""Calculate and store shoulder distance"""
|
| 14 |
-
if rescuer_keypoints is None:
|
| 15 |
-
return
|
| 16 |
-
|
| 17 |
-
try:
|
| 18 |
-
left = rescuer_keypoints[CocoKeypoints.LEFT_SHOULDER.value]
|
| 19 |
-
right = rescuer_keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
|
| 20 |
-
|
| 21 |
-
distance = np.linalg.norm(np.array(left) - np.array(right))
|
| 22 |
-
|
| 23 |
-
return distance
|
| 24 |
-
except Exception as e:
|
| 25 |
-
cpr_logger.error(f"Shoulder distance error: {e}")
|
| 26 |
-
return
|
| 27 |
-
|
| 28 |
-
def reset_shoulder_distances(self):
|
| 29 |
-
"""Reset shoulder distances"""
|
| 30 |
-
self.shoulder_distance_history = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPR/threaded_camera.py
DELETED
|
@@ -1,103 +0,0 @@
|
|
| 1 |
-
import threading
|
| 2 |
-
from queue import Queue
|
| 3 |
-
import queue
|
| 4 |
-
import cv2
|
| 5 |
-
from CPR.logging_config import cpr_logger
|
| 6 |
-
|
| 7 |
-
class ThreadedCamera:
|
| 8 |
-
def __init__(self, source, requested_fps = 30):
|
| 9 |
-
|
| 10 |
-
# The constructor of OpenCV's VideoCapture class automatically opens the camera
|
| 11 |
-
self.cap = cv2.VideoCapture(source)
|
| 12 |
-
if not self.cap.isOpened():
|
| 13 |
-
raise ValueError(f"[VIDEO CAPTURE] Unable to open camera source: {source}")
|
| 14 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Camera source opened: {source}")
|
| 15 |
-
|
| 16 |
-
# Attempt to configure the camera to the requested FPS
|
| 17 |
-
# Which is set to the value we have been working on with recorded videos
|
| 18 |
-
# .set() returns True if the camera acknowledged the request, not if it actually achieved the FPS.
|
| 19 |
-
set_success = self.cap.set(cv2.CAP_PROP_FPS, requested_fps)
|
| 20 |
-
|
| 21 |
-
# Get the actual FPS from the camera
|
| 22 |
-
# This is the FPS that the camera is actually using, which may differ from the requested FPS.
|
| 23 |
-
actual_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
| 24 |
-
self.fps = actual_fps
|
| 25 |
-
|
| 26 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Requested FPS: {requested_fps}, Set Success: {set_success}, Actual FPS: {actual_fps}")
|
| 27 |
-
|
| 28 |
-
# The buffer should be able to hold a lag of up to 2 seconds
|
| 29 |
-
number_of_seconds_to_buffer = 5
|
| 30 |
-
queue_size = int(actual_fps * number_of_seconds_to_buffer)
|
| 31 |
-
self.q = Queue(maxsize=queue_size)
|
| 32 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Queue size: {queue_size}")
|
| 33 |
-
|
| 34 |
-
# Set a flag to indicate that the camera is running
|
| 35 |
-
self.running = threading.Event()
|
| 36 |
-
self.running.set() # Initial state = running
|
| 37 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Camera running: {self.running.is_set()}")
|
| 38 |
-
|
| 39 |
-
self.number_of_total_frames = 0
|
| 40 |
-
self.number_of_dropped_frames = 0
|
| 41 |
-
|
| 42 |
-
self.thread = None
|
| 43 |
-
|
| 44 |
-
def start_capture(self):
|
| 45 |
-
# Clear any existing frames in queue
|
| 46 |
-
while not self.q.empty():
|
| 47 |
-
self.q.get()
|
| 48 |
-
|
| 49 |
-
# threading.Thread() initialize a new thread
|
| 50 |
-
# target=self._reader specify the method (_reader) the thread will execute
|
| 51 |
-
self.thread = threading.Thread(target=self._reader)
|
| 52 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Thread initialized: {self.thread}")
|
| 53 |
-
|
| 54 |
-
# Set the thread as a daemon thread:
|
| 55 |
-
# Daemon threads automatically exit when the main program exits
|
| 56 |
-
# They run in the background and don't block program termination
|
| 57 |
-
self.thread.daemon = True
|
| 58 |
-
cpr_logger.info(f"[VIDEO CAPTURE] Thread daemon: {self.thread.daemon}")
|
| 59 |
-
|
| 60 |
-
# Start the thread execution:
|
| 61 |
-
# Call the _reader method in parallel with the main program
|
| 62 |
-
self.thread.start()
|
| 63 |
-
|
| 64 |
-
def _reader(self):
|
| 65 |
-
while self.running.is_set():
|
| 66 |
-
ret, frame = self.cap.read()
|
| 67 |
-
if not ret:
|
| 68 |
-
cpr_logger.info("Camera disconnected")
|
| 69 |
-
self.q.put(None) # Sentinel for clean exit
|
| 70 |
-
break
|
| 71 |
-
|
| 72 |
-
try:
|
| 73 |
-
self.number_of_total_frames += 1
|
| 74 |
-
self.q.put(frame, timeout=0.1)
|
| 75 |
-
except queue.Full:
|
| 76 |
-
cpr_logger.info("Frame dropped")
|
| 77 |
-
self.number_of_dropped_frames += 1
|
| 78 |
-
|
| 79 |
-
def read(self):
|
| 80 |
-
return self.q.get()
|
| 81 |
-
|
| 82 |
-
def release(self):
|
| 83 |
-
#! Not an error
|
| 84 |
-
cpr_logger.error(f"[VIDEO CAPTURE] Total frames: {self.number_of_total_frames}, Dropped frames: {self.number_of_dropped_frames}")
|
| 85 |
-
|
| 86 |
-
self.running.clear()
|
| 87 |
-
|
| 88 |
-
# First release the capture to unblock pending reads
|
| 89 |
-
self.cap.release() # MOVED THIS LINE UP
|
| 90 |
-
|
| 91 |
-
# Then join the thread
|
| 92 |
-
self.thread.join(timeout=1.0)
|
| 93 |
-
|
| 94 |
-
if self.thread.is_alive():
|
| 95 |
-
cpr_logger.info("Warning: Thread didn't terminate cleanly")
|
| 96 |
-
# Removed redundant self.cap.release()
|
| 97 |
-
|
| 98 |
-
def isOpened(self):
|
| 99 |
-
return self.running.is_set() and self.cap.isOpened()
|
| 100 |
-
|
| 101 |
-
def __del__(self):
|
| 102 |
-
if self.running.is_set(): # Only release if not already done
|
| 103 |
-
self.release()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/analysis_socket_server.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
| 1 |
-
import socket
|
| 2 |
-
import json
|
| 3 |
-
from threading import Thread
|
| 4 |
-
from queue import Queue
|
| 5 |
-
import threading
|
| 6 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 7 |
-
import queue
|
| 8 |
-
|
| 9 |
-
class AnalysisSocketServer:
|
| 10 |
-
def __init__(self, host='localhost', port=5000):
|
| 11 |
-
self.host = host
|
| 12 |
-
self.port = port
|
| 13 |
-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 14 |
-
self.conn = None
|
| 15 |
-
self.running = False
|
| 16 |
-
self.warning_queue = Queue()
|
| 17 |
-
self.connection_event = threading.Event()
|
| 18 |
-
cpr_logger.info(f"[SOCKET] Server initialized on {host}:{port}")
|
| 19 |
-
|
| 20 |
-
def start_server(self):
|
| 21 |
-
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 22 |
-
self.sock.bind((self.host, self.port))
|
| 23 |
-
self.sock.listen()
|
| 24 |
-
self.running = True
|
| 25 |
-
Thread(target=self._accept_connections, daemon=True).start()
|
| 26 |
-
|
| 27 |
-
def _accept_connections(self):
|
| 28 |
-
while self.running:
|
| 29 |
-
try:
|
| 30 |
-
self.conn, addr = self.sock.accept()
|
| 31 |
-
cpr_logger.info(f"[SOCKET] Connected by {addr}")
|
| 32 |
-
self.connection_event.set() # Signal that connection was made
|
| 33 |
-
Thread(target=self._handle_client, args=(self.conn,), daemon=True).start()
|
| 34 |
-
except Exception as e:
|
| 35 |
-
#! Not an error
|
| 36 |
-
cpr_logger.error(f"[SOCKET] Connection error: {str(e)}")
|
| 37 |
-
|
| 38 |
-
def wait_for_connection(self, timeout=None):
|
| 39 |
-
"""Block until a client connects"""
|
| 40 |
-
#^ Set as an error for cleaner logging purposes
|
| 41 |
-
cpr_logger.error("[SOCKET] Waiting for client connection...")
|
| 42 |
-
self.connection_event.clear() # Reset the event
|
| 43 |
-
return self.connection_event.wait(timeout)
|
| 44 |
-
|
| 45 |
-
def _handle_client(self, conn):
|
| 46 |
-
while self.running:
|
| 47 |
-
try:
|
| 48 |
-
# Block until a warning is available (reduces CPU usage)
|
| 49 |
-
warnings = self.warning_queue.get(block=True, timeout=0.1)
|
| 50 |
-
serialized = json.dumps(warnings) + "\n"
|
| 51 |
-
conn.sendall(serialized.encode('utf-8'))
|
| 52 |
-
except queue.Empty:
|
| 53 |
-
continue # Timeout allows checking self.running periodically
|
| 54 |
-
except (BrokenPipeError, ConnectionResetError):
|
| 55 |
-
cpr_logger.error("[SOCKET] Client disconnected")
|
| 56 |
-
break
|
| 57 |
-
except Exception as e:
|
| 58 |
-
cpr_logger.error(f"[SOCKET] Error: {str(e)}")
|
| 59 |
-
break
|
| 60 |
-
conn.close()
|
| 61 |
-
|
| 62 |
-
def stop_server(self):
|
| 63 |
-
self.running = False
|
| 64 |
-
self.sock.close()
|
| 65 |
-
cpr_logger.info("[SOCKET] Server stopped")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/chest_initializer.py
DELETED
|
@@ -1,154 +0,0 @@
|
|
| 1 |
-
import cv2
|
| 2 |
-
import numpy as np
|
| 3 |
-
from CPRRealTime.keypoints import CocoKeypoints
|
| 4 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 5 |
-
|
| 6 |
-
class ChestInitializer:
|
| 7 |
-
"""Handles chest point detection with validations in estimation."""
|
| 8 |
-
|
| 9 |
-
def __init__(self):
|
| 10 |
-
self.chest_params = None
|
| 11 |
-
self.chest_params_history = []
|
| 12 |
-
self.expected_chest_params = None
|
| 13 |
-
|
| 14 |
-
def estimate_chest_region(self, keypoints, bounding_box, frame_width, frame_height):
|
| 15 |
-
"""Estimate and validate chest region. Returns (cx, cy, cw, ch) or None."""
|
| 16 |
-
try:
|
| 17 |
-
# Unpack bounding box and calculate shoulder dimensions
|
| 18 |
-
bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bounding_box
|
| 19 |
-
bbox_delta_y = abs(bbox_y2 - bbox_y1)
|
| 20 |
-
|
| 21 |
-
# Keypoints for shoulders
|
| 22 |
-
left_shoulder = keypoints[CocoKeypoints.LEFT_SHOULDER.value]
|
| 23 |
-
right_shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
|
| 24 |
-
|
| 25 |
-
# Midpoints calculation
|
| 26 |
-
shoulder_center = np.array([(left_shoulder[0] + right_shoulder[0]) / 2,
|
| 27 |
-
(left_shoulder[1] + right_shoulder[1]) / 2])
|
| 28 |
-
|
| 29 |
-
#& Handing different patient positions
|
| 30 |
-
# If the x-coordinate shoulder center is closer to that of the Bottom-Right bbox corner (2)
|
| 31 |
-
# then the orientation is "right"
|
| 32 |
-
# If the x-coordinate shoulder center is closer to that of the Top-Left bbox corner (1)
|
| 33 |
-
# then the orientation is "left"
|
| 34 |
-
|
| 35 |
-
if abs(shoulder_center[0] - bbox_x2) < abs(shoulder_center[0] - bbox_x1): # Orientation is "right"
|
| 36 |
-
chest_center_from_shoulder_x = shoulder_center[0] - 0.3 * bbox_delta_y
|
| 37 |
-
chest_center_from_shoulder_y = shoulder_center[1] - 0.1 * bbox_delta_y
|
| 38 |
-
chest_center_from_shoulder = np.array([chest_center_from_shoulder_x, chest_center_from_shoulder_y])
|
| 39 |
-
else: # Orientation is "left"
|
| 40 |
-
chest_center_from_shoulder_x = shoulder_center[0] + 1.0 * bbox_delta_y
|
| 41 |
-
chest_center_from_shoulder_y = shoulder_center[1] - 0.1 * bbox_delta_y
|
| 42 |
-
chest_center_from_shoulder = np.array([chest_center_from_shoulder_x, chest_center_from_shoulder_y])
|
| 43 |
-
|
| 44 |
-
# Chest dimensions (85% of shoulder width, 40% height)
|
| 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_from_shoulder[0] - chest_dx / 2
|
| 50 |
-
y1 = chest_center_from_shoulder[1] - chest_dy / 2
|
| 51 |
-
x2 = chest_center_from_shoulder[0] + chest_dx / 2
|
| 52 |
-
y2 = chest_center_from_shoulder[1] + chest_dy / 2
|
| 53 |
-
|
| 54 |
-
# Clamp to frame boundaries
|
| 55 |
-
x1 = max(0, min(x1, frame_width - 1))
|
| 56 |
-
y1 = max(0, min(y1, frame_height - 1))
|
| 57 |
-
x2 = max(0, min(x2, frame_width - 1))
|
| 58 |
-
y2 = max(0, min(y2, frame_height - 1))
|
| 59 |
-
|
| 60 |
-
# Check validity
|
| 61 |
-
if x2 <= x1 or y2 <= y1:
|
| 62 |
-
return None
|
| 63 |
-
|
| 64 |
-
# Adjusted parameters
|
| 65 |
-
cx = (x1 + x2) / 2
|
| 66 |
-
cy = (y1 + y2) / 2
|
| 67 |
-
cw = x2 - x1
|
| 68 |
-
ch = y2 - y1
|
| 69 |
-
|
| 70 |
-
return (cx, cy, cw, ch)
|
| 71 |
-
|
| 72 |
-
except (IndexError, TypeError, ValueError) as e:
|
| 73 |
-
cpr_logger.error(f"Chest estimation error: {e}")
|
| 74 |
-
return None
|
| 75 |
-
|
| 76 |
-
def estimate_chest_region_weighted_avg(self, frame_width, frame_height, window_size=60, min_samples=3):
|
| 77 |
-
"""
|
| 78 |
-
Calculate stabilized chest parameters using weighted averaging with boundary checks.
|
| 79 |
-
|
| 80 |
-
Args:
|
| 81 |
-
self.chest_params_history: List of recent chest parameters [(cx, cy, cw, ch), ...]
|
| 82 |
-
frame_width: Width of the video frame
|
| 83 |
-
frame_height: Height of the video frame
|
| 84 |
-
window_size: Number of recent frames to consider (default: 5)
|
| 85 |
-
min_samples: Minimum valid samples required (default: 3)
|
| 86 |
-
|
| 87 |
-
Returns:
|
| 88 |
-
Tuple of (cx, cy, cw, ch) as integers within frame boundaries,
|
| 89 |
-
or None if insufficient data or invalid rectangle
|
| 90 |
-
"""
|
| 91 |
-
if not self.chest_params_history:
|
| 92 |
-
return None
|
| 93 |
-
|
| 94 |
-
# Filter out None values and get recent frames
|
| 95 |
-
valid_history = [h for h in self.chest_params_history[-window_size:] if h is not None]
|
| 96 |
-
|
| 97 |
-
if len(valid_history) < min_samples:
|
| 98 |
-
return None
|
| 99 |
-
|
| 100 |
-
# Convert to numpy array (preserve floating-point precision)
|
| 101 |
-
history_array = np.array(valid_history, dtype=np.float32)
|
| 102 |
-
|
| 103 |
-
# Exponential weights (stronger emphasis on recent frames)
|
| 104 |
-
weights = np.exp(np.linspace(1, 3, len(history_array)))
|
| 105 |
-
weights /= weights.sum()
|
| 106 |
-
|
| 107 |
-
try:
|
| 108 |
-
# Calculate weighted average in float space
|
| 109 |
-
cx, cy, cw, ch = np.average(history_array, axis=0, weights=weights)
|
| 110 |
-
|
| 111 |
-
# Convert to rectangle coordinates (still floating point)
|
| 112 |
-
x1 = max(0.0, cx - cw/2)
|
| 113 |
-
y1 = max(0.0, cy - ch/2)
|
| 114 |
-
x2 = min(float(frame_width - 1), cx + cw/2)
|
| 115 |
-
y2 = min(float(frame_height - 1), cy + ch/2)
|
| 116 |
-
|
| 117 |
-
# Only round to integers after all calculations
|
| 118 |
-
x1, y1, x2, y2 = map(round, [x1, y1, x2, y2])
|
| 119 |
-
|
| 120 |
-
# Validate rectangle
|
| 121 |
-
if x2 <= x1 or y2 <= y1:
|
| 122 |
-
return None
|
| 123 |
-
|
| 124 |
-
return (
|
| 125 |
-
(x1 + x2) // 2, # cx
|
| 126 |
-
(y1 + y2) // 2, # cy
|
| 127 |
-
x2 - x1, # cw
|
| 128 |
-
y2 - y1 # ch
|
| 129 |
-
)
|
| 130 |
-
|
| 131 |
-
except Exception as e:
|
| 132 |
-
cpr_logger.error(f"Chest region estimation error: {e}")
|
| 133 |
-
return None
|
| 134 |
-
|
| 135 |
-
def draw_expected_chest_region(self, frame):
|
| 136 |
-
"""Draws the chest region without validation."""
|
| 137 |
-
if self.expected_chest_params is None:
|
| 138 |
-
return frame
|
| 139 |
-
|
| 140 |
-
cx, cy, cw, ch = self.expected_chest_params
|
| 141 |
-
x1 = int(cx - cw / 2)
|
| 142 |
-
y1 = int(cy - ch / 2)
|
| 143 |
-
x2 = int(cx + cw / 2)
|
| 144 |
-
y2 = int(cy + ch / 2)
|
| 145 |
-
|
| 146 |
-
# Draw rectangle and center
|
| 147 |
-
cv2.rectangle(frame, (x1, y1), (x2, y2), (128, 128, 0), 5)
|
| 148 |
-
|
| 149 |
-
cv2.circle(frame, (int(cx), int(cy)), 8, (128, 128, 0), -1)
|
| 150 |
-
|
| 151 |
-
cv2.putText(frame, "EXPECTED CHEST", (x1, max(10, y1 - 5)),
|
| 152 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (128, 128, 0), 2)
|
| 153 |
-
|
| 154 |
-
return frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/client.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
import socket
|
| 2 |
-
import json
|
| 3 |
-
|
| 4 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 5 |
-
|
| 6 |
-
HOST = 'localhost' # The server's hostname or IP address
|
| 7 |
-
PORT = 5000 # The port used by the server
|
| 8 |
-
|
| 9 |
-
#! Not an error
|
| 10 |
-
|
| 11 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 12 |
-
s.connect((HOST, PORT))
|
| 13 |
-
#^ Set as an error for cleaner logging purposes
|
| 14 |
-
cpr_logger.error(f"Connected to {HOST}:{PORT}")
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
while True:
|
| 18 |
-
data = s.recv(1024)
|
| 19 |
-
if not data:
|
| 20 |
-
break
|
| 21 |
-
|
| 22 |
-
# Split messages (in case multiple JSONs in buffer)
|
| 23 |
-
for line in data.decode('utf-8').split('\n'):
|
| 24 |
-
if line.strip():
|
| 25 |
-
try:
|
| 26 |
-
warnings = json.loads(line)
|
| 27 |
-
cpr_logger.error("\nReceived warnings:")
|
| 28 |
-
cpr_logger.error(f"Status: {warnings['status']}")
|
| 29 |
-
cpr_logger.error(f"Posture Warnings: {warnings['posture_warnings']}")
|
| 30 |
-
cpr_logger.error(f"Rate/Depth Warnings: {warnings['rate_and_depth_warnings']}")
|
| 31 |
-
except json.JSONDecodeError:
|
| 32 |
-
cpr_logger.error("Received invalid JSON")
|
| 33 |
-
except KeyboardInterrupt:
|
| 34 |
-
cpr_logger.error("Disconnecting...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/keypoints.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
# keypoints.py
|
| 2 |
-
from enum import Enum
|
| 3 |
-
|
| 4 |
-
class CocoKeypoints(Enum):
|
| 5 |
-
"""Enum for COCO keypoints (17 points)"""
|
| 6 |
-
NOSE = 0
|
| 7 |
-
LEFT_EYE = 1
|
| 8 |
-
RIGHT_EYE = 2
|
| 9 |
-
LEFT_EAR = 3
|
| 10 |
-
RIGHT_EAR = 4
|
| 11 |
-
LEFT_SHOULDER = 5
|
| 12 |
-
RIGHT_SHOULDER = 6
|
| 13 |
-
LEFT_ELBOW = 7
|
| 14 |
-
RIGHT_ELBOW = 8
|
| 15 |
-
LEFT_WRIST = 9
|
| 16 |
-
RIGHT_WRIST = 10
|
| 17 |
-
LEFT_HIP = 11
|
| 18 |
-
RIGHT_HIP = 12
|
| 19 |
-
LEFT_KNEE = 13
|
| 20 |
-
RIGHT_KNEE = 14
|
| 21 |
-
LEFT_ANKLE = 15
|
| 22 |
-
RIGHT_ANKLE = 16
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/logging_config.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
# logging_config.py
|
| 2 |
-
import logging
|
| 3 |
-
|
| 4 |
-
# 1. Set default log level here (change this value as needed)
|
| 5 |
-
DEFAULT_LOG_LEVEL = logging.INFO # Switch to logging.ERROR for errors-only by default
|
| 6 |
-
|
| 7 |
-
# 2. Configure logger with default level
|
| 8 |
-
cpr_logger = logging.getLogger("CPR-Analyzer")
|
| 9 |
-
cpr_logger.setLevel(DEFAULT_LOG_LEVEL)
|
| 10 |
-
|
| 11 |
-
# 3. Create console handler with formatter
|
| 12 |
-
console_handler = logging.StreamHandler()
|
| 13 |
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 14 |
-
console_handler.setFormatter(formatter)
|
| 15 |
-
|
| 16 |
-
# 4. Add handler to logger
|
| 17 |
-
cpr_logger.addHandler(console_handler)
|
| 18 |
-
|
| 19 |
-
# 5. Prevent propagation to root logger
|
| 20 |
-
cpr_logger.propagate = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/role_classifier.py
DELETED
|
@@ -1,178 +0,0 @@
|
|
| 1 |
-
# role_classifier.py
|
| 2 |
-
import cv2
|
| 3 |
-
import numpy as np
|
| 4 |
-
from ultralytics.utils.plotting import Annotator # Import YOLO's annotator
|
| 5 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 6 |
-
|
| 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
|
| 13 |
-
self.rescuer_id = None
|
| 14 |
-
self.rescuer_processed_results = None
|
| 15 |
-
self.patient_processed_results = None
|
| 16 |
-
|
| 17 |
-
def _calculate_verticality_score(self, bounding_box):
|
| 18 |
-
"""Calculate posture verticality score (0=horizontal, 1=vertical) using bounding box aspect ratio."""
|
| 19 |
-
try:
|
| 20 |
-
x1, y1, x2, y2 = bounding_box
|
| 21 |
-
width = abs(x2 - x1)
|
| 22 |
-
height = abs(y2 - y1)
|
| 23 |
-
|
| 24 |
-
# Handle edge cases with invalid dimensions
|
| 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 |
-
|
| 40 |
-
def _calculate_distance(self, point1, point2):
|
| 41 |
-
"""Calculate Euclidean distance between two points"""
|
| 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 |
-
|
| 71 |
-
# Calculate combined area threshold if previous boxes exist
|
| 72 |
-
threshold = None
|
| 73 |
-
if prev_rescuer_processed_results and prev_patient_processed_results:
|
| 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
|
| 86 |
-
bounding_box = [
|
| 87 |
-
x_center - width/2, # x1
|
| 88 |
-
y_center - height/2, # y1
|
| 89 |
-
x_center + width/2, # x2
|
| 90 |
-
y_center + height/2 # y2
|
| 91 |
-
]
|
| 92 |
-
|
| 93 |
-
# Skip if box exceeds area threshold (when threshold exists)
|
| 94 |
-
if threshold:
|
| 95 |
-
box_area = width * height
|
| 96 |
-
if box_area > threshold * 1.2: # 20% tolerance
|
| 97 |
-
cpr_logger.info(f"Filtered oversized box {i} (area: {box_area:.1f} > threshold: {threshold:.1f})")
|
| 98 |
-
continue
|
| 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 |
-
|
| 105 |
-
# Store valid results
|
| 106 |
-
processed_results.append({
|
| 107 |
-
'original_index': i,
|
| 108 |
-
'bounding_box': bounding_box,
|
| 109 |
-
'bounding_box_center': bounding_box_center,
|
| 110 |
-
'verticality_score': verticality_score,
|
| 111 |
-
'keypoints': keypoints,
|
| 112 |
-
})
|
| 113 |
-
|
| 114 |
-
except Exception as e:
|
| 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 |
-
]
|
| 139 |
-
|
| 140 |
-
if potential_rescuers:
|
| 141 |
-
# Select rescuer closest to patient
|
| 142 |
-
rescuer = min(potential_rescuers,
|
| 143 |
-
key=lambda x: self._calculate_distance(
|
| 144 |
-
x['bounding_box_center'],
|
| 145 |
-
patient['bounding_box_center']))
|
| 146 |
-
|
| 147 |
-
return rescuer, patient
|
| 148 |
-
|
| 149 |
-
def draw_rescuer_and_patient(self, frame):
|
| 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"]
|
| 161 |
-
annotator.kpts(keypoints, shape=frame.shape[:2])
|
| 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"]
|
| 173 |
-
annotator.kpts(keypoints, shape=frame.shape[:2])
|
| 174 |
-
except Exception as e:
|
| 175 |
-
cpr_logger.error(f"Error drawing patient: {str(e)}")
|
| 176 |
-
|
| 177 |
-
return annotator.result()
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/warnings_overlayer.py
DELETED
|
@@ -1,147 +0,0 @@
|
|
| 1 |
-
import cv2
|
| 2 |
-
import numpy as np
|
| 3 |
-
import os
|
| 4 |
-
import sys
|
| 5 |
-
|
| 6 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 7 |
-
|
| 8 |
-
class WarningsOverlayer:
|
| 9 |
-
def __init__(self):
|
| 10 |
-
self.WARNING_CONFIG = {
|
| 11 |
-
# Posture Warnings
|
| 12 |
-
"Right arm bent!": {
|
| 13 |
-
"color": (52, 110, 235),
|
| 14 |
-
"position": (50, 150)
|
| 15 |
-
},
|
| 16 |
-
"Left arm bent!": {
|
| 17 |
-
"color": (52, 110, 235),
|
| 18 |
-
"position": (50, 200)
|
| 19 |
-
},
|
| 20 |
-
"Left hand not on chest!": {
|
| 21 |
-
"color": (161, 127, 18),
|
| 22 |
-
"position": (50, 250)
|
| 23 |
-
},
|
| 24 |
-
"Right hand not on chest!": {
|
| 25 |
-
"color": (161, 127, 18),
|
| 26 |
-
"position": (50, 300)
|
| 27 |
-
},
|
| 28 |
-
"Both hands not on chest!": {
|
| 29 |
-
"color": (161, 127, 18),
|
| 30 |
-
"position": (50, 350)
|
| 31 |
-
},
|
| 32 |
-
|
| 33 |
-
# Rate/Depth Warnings
|
| 34 |
-
"Depth too low!": {
|
| 35 |
-
"color": (125, 52, 235),
|
| 36 |
-
"position": (50, 400)
|
| 37 |
-
},
|
| 38 |
-
"Depth too high!": {
|
| 39 |
-
"color": (125, 52, 235),
|
| 40 |
-
"position": (50, 450)
|
| 41 |
-
},
|
| 42 |
-
"Rate too slow!": {
|
| 43 |
-
"color": (235, 52, 214),
|
| 44 |
-
"position": (50, 500)
|
| 45 |
-
},
|
| 46 |
-
"Rate too fast!": {
|
| 47 |
-
"color": (235, 52, 214),
|
| 48 |
-
"position": (50, 550)
|
| 49 |
-
}
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
def add_warnings_to_processed_video(self, video_output_path, sampling_interval_frames, rate_and_depth_warnings, posture_warnings):
|
| 53 |
-
"""Process both warning types with identical handling"""
|
| 54 |
-
cpr_logger.info("\n[POST-PROCESS] Starting warning overlay")
|
| 55 |
-
|
| 56 |
-
# Read processed video with original parameters
|
| 57 |
-
cap = cv2.VideoCapture(video_output_path)
|
| 58 |
-
if not cap.isOpened():
|
| 59 |
-
cpr_logger.info("[ERROR] Failed to open processed video")
|
| 60 |
-
return
|
| 61 |
-
|
| 62 |
-
# Get original video properties
|
| 63 |
-
original_fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))
|
| 64 |
-
processed_fps = cap.get(cv2.CAP_PROP_FPS)
|
| 65 |
-
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 66 |
-
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 67 |
-
|
| 68 |
-
# Create final writer with ORIGINAL codec and parameters
|
| 69 |
-
base = os.path.splitext(video_output_path)[0]
|
| 70 |
-
final_path = os.path.abspath(f"{base}_final.mp4")
|
| 71 |
-
writer = cv2.VideoWriter(final_path, original_fourcc, processed_fps, (width, height))
|
| 72 |
-
|
| 73 |
-
# Combine all warnings into unified list
|
| 74 |
-
all_warnings = []
|
| 75 |
-
|
| 76 |
-
# Process posture warnings
|
| 77 |
-
for entry in posture_warnings:
|
| 78 |
-
if warnings := entry.get('posture_warnings'):
|
| 79 |
-
start = entry['start_frame'] // sampling_interval_frames
|
| 80 |
-
end = entry['end_frame'] // sampling_interval_frames
|
| 81 |
-
all_warnings.append((int(start), int(end), warnings))
|
| 82 |
-
|
| 83 |
-
# Process rate/depth warnings
|
| 84 |
-
for entry in rate_and_depth_warnings:
|
| 85 |
-
if warnings := entry.get('rate_and_depth_warnings'):
|
| 86 |
-
start = entry['start_frame'] // sampling_interval_frames
|
| 87 |
-
end = entry['end_frame'] // sampling_interval_frames
|
| 88 |
-
all_warnings.append((int(start), int(end), warnings))
|
| 89 |
-
|
| 90 |
-
# Video processing loop
|
| 91 |
-
frame_idx = 0
|
| 92 |
-
while True:
|
| 93 |
-
ret, frame = cap.read()
|
| 94 |
-
if not ret:
|
| 95 |
-
break
|
| 96 |
-
|
| 97 |
-
# Check active warnings for current frame
|
| 98 |
-
active_warnings = []
|
| 99 |
-
for start, end, warnings in all_warnings:
|
| 100 |
-
if start <= frame_idx <= end:
|
| 101 |
-
active_warnings.extend(warnings)
|
| 102 |
-
|
| 103 |
-
# Draw all warnings using unified config
|
| 104 |
-
self._draw_warnings(frame, active_warnings)
|
| 105 |
-
|
| 106 |
-
writer.write(frame)
|
| 107 |
-
frame_idx += 1
|
| 108 |
-
|
| 109 |
-
cap.release()
|
| 110 |
-
writer.release()
|
| 111 |
-
cpr_logger.info(f"\n[POST-PROCESS] Final output saved to: {final_path}")
|
| 112 |
-
|
| 113 |
-
def _draw_warnings(self, frame, active_warnings):
|
| 114 |
-
"""Draw warnings using unified configuration"""
|
| 115 |
-
drawn_positions = set() # Prevent overlapping
|
| 116 |
-
|
| 117 |
-
for warning_text in active_warnings:
|
| 118 |
-
if config := self.WARNING_CONFIG.get(warning_text):
|
| 119 |
-
x, y = config['position']
|
| 120 |
-
|
| 121 |
-
# Auto-stack if position occupied
|
| 122 |
-
while (x, y) in drawn_positions:
|
| 123 |
-
y += 50 # Move down by 50px
|
| 124 |
-
|
| 125 |
-
self._draw_warning_banner(
|
| 126 |
-
frame=frame,
|
| 127 |
-
text=warning_text,
|
| 128 |
-
color=config['color'],
|
| 129 |
-
position=(x, y)
|
| 130 |
-
)
|
| 131 |
-
drawn_positions.add((x, y))
|
| 132 |
-
|
| 133 |
-
def _draw_warning_banner(self, frame, text, color, position):
|
| 134 |
-
"""Base drawing function for warning banners"""
|
| 135 |
-
(text_width, text_height), _ = cv2.getTextSize(
|
| 136 |
-
text, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)
|
| 137 |
-
|
| 138 |
-
x, y = position
|
| 139 |
-
# Background rectangle
|
| 140 |
-
cv2.rectangle(frame,
|
| 141 |
-
(x - 10, y - text_height - 10),
|
| 142 |
-
(x + text_width + 10, y + 10),
|
| 143 |
-
color, -1)
|
| 144 |
-
# Text
|
| 145 |
-
cv2.putText(frame, text, (x, y),
|
| 146 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/wrists_midpoint_analyzer.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
import cv2
|
| 2 |
-
import numpy as np
|
| 3 |
-
from CPRRealTime.keypoints import CocoKeypoints
|
| 4 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 5 |
-
|
| 6 |
-
class WristsMidpointAnalyzer:
|
| 7 |
-
"""Analyzes and tracks wrist midpoints for rescuer"""
|
| 8 |
-
|
| 9 |
-
def __init__(self, allowed_distance_between_wrists=170):
|
| 10 |
-
self.allowed_distance_between_wrists = allowed_distance_between_wrists
|
| 11 |
-
self.midpoint = None
|
| 12 |
-
self.midpoint_history = []
|
| 13 |
-
|
| 14 |
-
def detect_wrists_midpoint(self, rescuer_keypoints):
|
| 15 |
-
"""Calculate midpoint between wrists in pixel coordinates"""
|
| 16 |
-
try:
|
| 17 |
-
if rescuer_keypoints is None:
|
| 18 |
-
return None
|
| 19 |
-
|
| 20 |
-
# Get wrist coordinates
|
| 21 |
-
lw = rescuer_keypoints[CocoKeypoints.LEFT_WRIST.value]
|
| 22 |
-
rw = rescuer_keypoints[CocoKeypoints.RIGHT_WRIST.value]
|
| 23 |
-
|
| 24 |
-
# If the distance between wrists is too large, return None
|
| 25 |
-
distance = np.linalg.norm(np.array(lw) - np.array(rw))
|
| 26 |
-
if distance > self.allowed_distance_between_wrists:
|
| 27 |
-
return None
|
| 28 |
-
|
| 29 |
-
# Calculate midpoint
|
| 30 |
-
midpoint = (
|
| 31 |
-
int((lw[0] + rw[0]) / 2),
|
| 32 |
-
int((lw[1] + rw[1]) / 2)
|
| 33 |
-
)
|
| 34 |
-
|
| 35 |
-
return midpoint
|
| 36 |
-
|
| 37 |
-
except Exception as e:
|
| 38 |
-
cpr_logger.error(f"Midpoint tracking error: {e}")
|
| 39 |
-
return None
|
| 40 |
-
|
| 41 |
-
def draw_midpoint(self, frame):
|
| 42 |
-
"""Visualize the midpoint on frame"""
|
| 43 |
-
|
| 44 |
-
if self.midpoint is None:
|
| 45 |
-
return frame
|
| 46 |
-
|
| 47 |
-
try:
|
| 48 |
-
# Draw visualization
|
| 49 |
-
cv2.circle(frame, self.midpoint, 8, (0, 255, 0), -1)
|
| 50 |
-
cv2.putText(
|
| 51 |
-
frame, "MIDPOINT",
|
| 52 |
-
(self.midpoint[0] + 5, self.midpoint[1] - 10),
|
| 53 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
return frame
|
| 57 |
-
except Exception as e:
|
| 58 |
-
cpr_logger.error(f"Midpoint drawing error: {e}")
|
| 59 |
-
return frame
|
| 60 |
-
|
| 61 |
-
def reset_midpoint_history(self):
|
| 62 |
-
"""Reset midpoint history"""
|
| 63 |
-
self.midpoint_history = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CPRRealTime/yolo11n-pose.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:869e83fcdffdc7371fa4e34cd8e51c838cc729571d1635e5141e3075e9319dc0
|
| 3 |
-
size 6255593
|
|
|
|
|
|
|
|
|
|
|
|
CPR_Module/Common/__init__.py
ADDED
|
File without changes
|
{CPR β CPR_Module/Common}/analysis_socket_server.py
RENAMED
|
@@ -3,9 +3,10 @@ import json
|
|
| 3 |
from threading import Thread
|
| 4 |
from queue import Queue
|
| 5 |
import threading
|
| 6 |
-
from CPR.logging_config import cpr_logger
|
| 7 |
import queue
|
| 8 |
|
|
|
|
|
|
|
| 9 |
class AnalysisSocketServer:
|
| 10 |
def __init__(self, host='localhost', port=5000):
|
| 11 |
self.host = host
|
|
|
|
| 3 |
from threading import Thread
|
| 4 |
from queue import Queue
|
| 5 |
import threading
|
|
|
|
| 6 |
import queue
|
| 7 |
|
| 8 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 9 |
+
|
| 10 |
class AnalysisSocketServer:
|
| 11 |
def __init__(self, host='localhost', port=5000):
|
| 12 |
self.host = host
|
{CPR β CPR_Module/Common}/chest_initializer.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import cv2
|
| 2 |
import numpy as np
|
| 3 |
-
|
| 4 |
-
from
|
|
|
|
| 5 |
|
| 6 |
class ChestInitializer:
|
| 7 |
"""Handles chest point detection with validations in estimation."""
|
|
|
|
| 1 |
import cv2
|
| 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 ChestInitializer:
|
| 8 |
"""Handles chest point detection with validations in estimation."""
|
{CPR β CPR_Module/Common}/keypoints.py
RENAMED
|
File without changes
|
{CPR β CPR_Module/Common}/logging_config.py
RENAMED
|
File without changes
|
{CPRRealTime β CPR_Module/Common}/posture_analyzer.py
RENAMED
|
@@ -2,8 +2,9 @@
|
|
| 2 |
import math
|
| 3 |
import cv2
|
| 4 |
import numpy as np
|
| 5 |
-
|
| 6 |
-
from
|
|
|
|
| 7 |
|
| 8 |
class PostureAnalyzer:
|
| 9 |
"""Posture analysis and visualization with comprehensive validation"""
|
|
|
|
| 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"""
|
{CPR β CPR_Module/Common}/role_classifier.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
# role_classifier.py
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
-
from ultralytics.utils.plotting import Annotator
|
| 5 |
-
from CPR.logging_config import cpr_logger
|
| 6 |
|
|
|
|
| 7 |
|
| 8 |
class RoleClassifier:
|
| 9 |
"""Role classification and tracking using image processing"""
|
|
|
|
| 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"""
|
{CPRRealTime β CPR_Module/Common}/shoulders_analyzer.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import numpy as np
|
| 2 |
-
|
| 3 |
-
from
|
|
|
|
| 4 |
|
| 5 |
class ShouldersAnalyzer:
|
| 6 |
"""Analyzes shoulder distances and posture"""
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
+
|
| 3 |
+
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"""
|
{CPRRealTime β CPR_Module/Common}/threaded_camera.py
RENAMED
|
@@ -2,7 +2,8 @@ import threading
|
|
| 2 |
from queue import Queue
|
| 3 |
import queue
|
| 4 |
import cv2
|
| 5 |
-
|
|
|
|
| 6 |
|
| 7 |
class ThreadedCamera:
|
| 8 |
def __init__(self, source, requested_fps = 30):
|
|
|
|
| 2 |
from queue import Queue
|
| 3 |
import queue
|
| 4 |
import cv2
|
| 5 |
+
|
| 6 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 7 |
|
| 8 |
class ThreadedCamera:
|
| 9 |
def __init__(self, source, requested_fps = 30):
|
{CPR β CPR_Module/Common}/warnings_overlayer.py
RENAMED
|
@@ -3,7 +3,7 @@ import numpy as np
|
|
| 3 |
import os
|
| 4 |
import sys
|
| 5 |
|
| 6 |
-
from
|
| 7 |
|
| 8 |
class WarningsOverlayer:
|
| 9 |
def __init__(self):
|
|
|
|
| 3 |
import os
|
| 4 |
import sys
|
| 5 |
|
| 6 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 7 |
|
| 8 |
class WarningsOverlayer:
|
| 9 |
def __init__(self):
|
{CPR β CPR_Module/Common}/wrists_midpoint_analyzer.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import cv2
|
| 2 |
import numpy as np
|
| 3 |
-
|
| 4 |
-
from
|
|
|
|
| 5 |
|
| 6 |
class WristsMidpointAnalyzer:
|
| 7 |
"""Analyzes and tracks wrist midpoints for rescuer"""
|
|
|
|
| 1 |
import cv2
|
| 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 WristsMidpointAnalyzer:
|
| 8 |
"""Analyzes and tracks wrist midpoints for rescuer"""
|
{CPR β CPR_Module/Common}/yolo11n-pose.pt
RENAMED
|
File without changes
|
{CPR β CPR_Module/Educational_Mode}/CPRAnalyzer.py
RENAMED
|
@@ -3,20 +3,21 @@ import cv2
|
|
| 3 |
import time
|
| 4 |
import math
|
| 5 |
import numpy as np
|
| 6 |
-
import os
|
| 7 |
import sys
|
| 8 |
|
| 9 |
-
from
|
| 10 |
-
from
|
| 11 |
-
from
|
| 12 |
-
|
| 13 |
-
from
|
| 14 |
-
from
|
| 15 |
-
from
|
| 16 |
-
from
|
| 17 |
-
from
|
| 18 |
-
|
| 19 |
-
|
|
|
|
| 20 |
|
| 21 |
class CPRAnalyzer:
|
| 22 |
"""Main CPR analysis pipeline with execution tracing"""
|
|
|
|
| 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
|
| 11 |
+
from CPR_Module.Educational_Mode.graph_plotter import GraphPlotter
|
| 12 |
+
|
| 13 |
+
from CPR_Module.Common.role_classifier import RoleClassifier
|
| 14 |
+
from CPR_Module.Common.chest_initializer import ChestInitializer
|
| 15 |
+
from CPR_Module.Common.posture_analyzer import PostureAnalyzer
|
| 16 |
+
from CPR_Module.Common.wrists_midpoint_analyzer import WristsMidpointAnalyzer
|
| 17 |
+
from CPR_Module.Common.shoulders_analyzer import ShouldersAnalyzer
|
| 18 |
+
from CPR_Module.Common.warnings_overlayer import WarningsOverlayer
|
| 19 |
+
|
| 20 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 21 |
|
| 22 |
class CPRAnalyzer:
|
| 23 |
"""Main CPR analysis pipeline with execution tracing"""
|
CPR_Module/Educational_Mode/__init__.py
ADDED
|
File without changes
|
{CPR β CPR_Module/Educational_Mode}/graph_plotter.py
RENAMED
|
@@ -2,10 +2,11 @@ import numpy as np
|
|
| 2 |
import matplotlib.pyplot as plt
|
| 3 |
import sys
|
| 4 |
import cv2
|
| 5 |
-
from
|
| 6 |
-
from matplotlib.ticker import MultipleLocator
|
| 7 |
import os
|
| 8 |
|
|
|
|
|
|
|
| 9 |
class GraphPlotter:
|
| 10 |
"""Class to plot graphs for various metrics"""
|
| 11 |
|
|
|
|
| 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 |
|
{CPR β CPR_Module/Educational_Mode}/metrics_calculator.py
RENAMED
|
@@ -5,7 +5,8 @@ import matplotlib.pyplot as plt
|
|
| 5 |
import sys
|
| 6 |
import cv2
|
| 7 |
import os
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
class MetricsCalculator:
|
| 11 |
"""Rate and depth calculation from motion data with improved peak detection"""
|
|
|
|
| 5 |
import sys
|
| 6 |
import cv2
|
| 7 |
import os
|
| 8 |
+
|
| 9 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 10 |
|
| 11 |
class MetricsCalculator:
|
| 12 |
"""Rate and depth calculation from motion data with improved peak detection"""
|
{CPR β CPR_Module/Educational_Mode}/pose_estimation.py
RENAMED
|
@@ -2,13 +2,14 @@
|
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
from ultralytics import YOLO
|
| 5 |
-
|
| 6 |
-
from
|
|
|
|
| 7 |
|
| 8 |
class PoseEstimator:
|
| 9 |
"""Human pose estimation using YOLO"""
|
| 10 |
|
| 11 |
-
def __init__(self, model_path="yolo11n-pose.pt", min_confidence=0.2):
|
| 12 |
self.model = YOLO(model_path)
|
| 13 |
self.min_confidence = min_confidence
|
| 14 |
|
|
|
|
| 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:
|
| 10 |
"""Human pose estimation using YOLO"""
|
| 11 |
|
| 12 |
+
def __init__(self, model_path="CPR_Module\Common\yolo11n-pose.pt", min_confidence=0.2):
|
| 13 |
self.model = YOLO(model_path)
|
| 14 |
self.min_confidence = min_confidence
|
| 15 |
|
CPR_Module/Emergency_Mode/__init__.py
ADDED
|
File without changes
|
{CPRRealTime β CPR_Module/Emergency_Mode}/graph_plotter.py
RENAMED
|
@@ -2,10 +2,11 @@ import numpy as np
|
|
| 2 |
import matplotlib.pyplot as plt
|
| 3 |
import sys
|
| 4 |
import cv2
|
| 5 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 6 |
from matplotlib.ticker import MultipleLocator
|
| 7 |
import os
|
| 8 |
|
|
|
|
|
|
|
| 9 |
class GraphPlotter:
|
| 10 |
"""Class to plot graphs for various metrics"""
|
| 11 |
|
|
|
|
| 2 |
import matplotlib.pyplot as plt
|
| 3 |
import sys
|
| 4 |
import cv2
|
|
|
|
| 5 |
from matplotlib.ticker import MultipleLocator
|
| 6 |
import os
|
| 7 |
|
| 8 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 9 |
+
|
| 10 |
class GraphPlotter:
|
| 11 |
"""Class to plot graphs for various metrics"""
|
| 12 |
|
{CPRRealTime β CPR_Module/Emergency_Mode}/main.py
RENAMED
|
@@ -6,18 +6,19 @@ import numpy as np
|
|
| 6 |
import os # Added for path handling
|
| 7 |
import sys
|
| 8 |
|
| 9 |
-
from
|
| 10 |
-
from
|
| 11 |
-
from
|
| 12 |
-
|
| 13 |
-
from
|
| 14 |
-
from
|
| 15 |
-
from
|
| 16 |
-
from
|
| 17 |
-
from
|
| 18 |
-
from
|
| 19 |
-
from
|
| 20 |
-
from
|
|
|
|
| 21 |
|
| 22 |
class CPRAnalyzer:
|
| 23 |
"""Main CPR analysis pipeline with execution tracing"""
|
|
|
|
| 6 |
import os # Added for path handling
|
| 7 |
import sys
|
| 8 |
|
| 9 |
+
from CPR_Module.Emergency_Mode.pose_estimation import PoseEstimator
|
| 10 |
+
from CPR_Module.Emergency_Mode.metrics_calculator import MetricsCalculator
|
| 11 |
+
from CPR_Module.Emergency_Mode.graph_plotter import GraphPlotter
|
| 12 |
+
|
| 13 |
+
from CPR_Module.Common.role_classifier import RoleClassifier
|
| 14 |
+
from CPR_Module.Common.chest_initializer import ChestInitializer
|
| 15 |
+
from CPR_Module.Common.posture_analyzer import PostureAnalyzer
|
| 16 |
+
from CPR_Module.Common.wrists_midpoint_analyzer import WristsMidpointAnalyzer
|
| 17 |
+
from CPR_Module.Common.shoulders_analyzer import ShouldersAnalyzer
|
| 18 |
+
from CPR_Module.Common.warnings_overlayer import WarningsOverlayer
|
| 19 |
+
from CPR_Module.Common.threaded_camera import ThreadedCamera
|
| 20 |
+
from CPR_Module.Common.analysis_socket_server import AnalysisSocketServer
|
| 21 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 22 |
|
| 23 |
class CPRAnalyzer:
|
| 24 |
"""Main CPR analysis pipeline with execution tracing"""
|
{CPRRealTime β CPR_Module/Emergency_Mode}/metrics_calculator.py
RENAMED
|
@@ -5,7 +5,8 @@ import matplotlib.pyplot as plt
|
|
| 5 |
import sys
|
| 6 |
import cv2
|
| 7 |
import os
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
class MetricsCalculator:
|
| 11 |
"""Rate and depth calculation from motion data with improved peak detection"""
|
|
|
|
| 5 |
import sys
|
| 6 |
import cv2
|
| 7 |
import os
|
| 8 |
+
|
| 9 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 10 |
|
| 11 |
class MetricsCalculator:
|
| 12 |
"""Rate and depth calculation from motion data with improved peak detection"""
|
{CPRRealTime β CPR_Module/Emergency_Mode}/pose_estimation.py
RENAMED
|
@@ -2,20 +2,20 @@
|
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
from ultralytics import YOLO
|
| 5 |
-
|
| 6 |
-
from
|
|
|
|
| 7 |
|
| 8 |
class PoseEstimator:
|
| 9 |
"""Human pose estimation using YOLO"""
|
| 10 |
|
| 11 |
-
def __init__(self, min_confidence, model_path="yolo11n-pose.pt"):
|
| 12 |
-
#self.model = YOLO(model_path).to("")
|
| 13 |
self.model = YOLO(model_path).to("cuda")
|
| 14 |
|
| 15 |
if next(self.model.model.parameters()).is_cuda:
|
| 16 |
-
|
| 17 |
else:
|
| 18 |
-
|
| 19 |
|
| 20 |
self.min_confidence = min_confidence
|
| 21 |
|
|
|
|
| 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:
|
| 10 |
"""Human pose estimation using YOLO"""
|
| 11 |
|
| 12 |
+
def __init__(self, min_confidence, model_path="CPR_Module\Common\yolo11n-pose.pt"):
|
|
|
|
| 13 |
self.model = YOLO(model_path).to("cuda")
|
| 14 |
|
| 15 |
if next(self.model.model.parameters()).is_cuda:
|
| 16 |
+
cpr_logger.info("YOLO model loaded on CUDA (GPU).")
|
| 17 |
else:
|
| 18 |
+
cpr_logger.warning("YOLO model is not on CUDA. Check your setup.")
|
| 19 |
|
| 20 |
self.min_confidence = min_confidence
|
| 21 |
|
CPR_Module/__init__.py
ADDED
|
File without changes
|
Demo/__init__.py
ADDED
|
File without changes
|
Demo/demo.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import time
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 7 |
+
from CPR_Module.Educational_Mode.CPRAnalyzer import CPRAnalyzer
|
| 8 |
+
|
| 9 |
+
cpr_logger.info("TEST LOG: Script started successfully")
|
| 10 |
+
|
| 11 |
+
def main(input_video, output_dir=None):
|
| 12 |
+
# Configuration
|
| 13 |
+
requested_fps = 30
|
| 14 |
+
base_dir = os.getcwd()
|
| 15 |
+
cpr_logger.info(f"[CONFIG] Base directory: {base_dir}")
|
| 16 |
+
|
| 17 |
+
# Validate input file exists
|
| 18 |
+
if not os.path.exists(input_video):
|
| 19 |
+
cpr_logger.error(f"[ERROR] Input video not found at: {input_video}")
|
| 20 |
+
sys.exit(1)
|
| 21 |
+
|
| 22 |
+
# Extract original filename without extension
|
| 23 |
+
original_name = os.path.splitext(os.path.basename(input_video))[0]
|
| 24 |
+
cpr_logger.info(f"[CONFIG] Original video name: {original_name}")
|
| 25 |
+
|
| 26 |
+
# Create output directory if it doesn't exist
|
| 27 |
+
if output_dir:
|
| 28 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 29 |
+
cpr_logger.info(f"[CONFIG] Output will be saved to: {output_dir}")
|
| 30 |
+
|
| 31 |
+
# Set output paths using original name
|
| 32 |
+
video_output_path = os.path.join(output_dir, f"{original_name}_output.mp4")
|
| 33 |
+
plot_output_path = os.path.join(output_dir, f"{original_name}_output.png")
|
| 34 |
+
|
| 35 |
+
# Log paths for verification
|
| 36 |
+
cpr_logger.info(f"[CONFIG] Video input: {input_video}")
|
| 37 |
+
cpr_logger.info(f"[CONFIG] Video output: {video_output_path}")
|
| 38 |
+
cpr_logger.info(f"[CONFIG] Plot output: {plot_output_path}")
|
| 39 |
+
|
| 40 |
+
# Initialize and run analyzer
|
| 41 |
+
initialization_start_time = time.time()
|
| 42 |
+
analyzer = CPRAnalyzer(input_video, video_output_path, plot_output_path, requested_fps)
|
| 43 |
+
|
| 44 |
+
initialization_end_time = time.time()
|
| 45 |
+
initialization_elapsed_time = initialization_end_time - initialization_start_time
|
| 46 |
+
cpr_logger.info(f"[TIMING] Initialization time: {initialization_elapsed_time:.2f}s")
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
analyzer.run_analysis_video()
|
| 50 |
+
finally:
|
| 51 |
+
cpr_logger.info(f"[MAIN] CPR Analysis Terminated")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
cpr_logger.info("Starting CPR Analysis Tool")
|
| 56 |
+
parser = argparse.ArgumentParser(description="CPR Analysis Tool")
|
| 57 |
+
|
| 58 |
+
cpr_logger.info("Parsing command line arguments")
|
| 59 |
+
parser.add_argument(
|
| 60 |
+
"-i", "--input",
|
| 61 |
+
required=True,
|
| 62 |
+
help="Path to input video file"
|
| 63 |
+
)
|
| 64 |
+
parser.add_argument(
|
| 65 |
+
"-o", "--output",
|
| 66 |
+
default=None,
|
| 67 |
+
help="Optional output directory path"
|
| 68 |
+
)
|
| 69 |
+
args = parser.parse_args()
|
| 70 |
+
|
| 71 |
+
cpr_logger.info(f"Input video: {args.input}")
|
| 72 |
+
cpr_logger.info(f"Output directory: {args.output if args.output else 'Not specified, using current directory'}")
|
| 73 |
+
|
| 74 |
+
cpr_logger.info("Starting main function")
|
| 75 |
+
main(args.input, args.output)
|
README.md
CHANGED
|
@@ -1,40 +1,168 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
#
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
```bash
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+

|
| 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>
|
| 6 |
+
|
| 7 |
+
# π Main Features
|
| 8 |
+
|
| 9 |
+
## π« ECG Analysis
|
| 10 |
+
Export your Apple Watch ECG PDF analysis and import it directly into our mobile application. Our advanced diagnostic system performs comprehensive checkups to determine if your ECG readings are normal or show abnormalities. Based on the analysis results, you'll receive personalized guidance and recommendations for appropriate next steps.
|
| 11 |
+
|
| 12 |
+
## π₯ Burn Assessment
|
| 13 |
+
Accidentally burned yourself on a stove or with boiling water? Simply take a photo and upload it to the app. Our intelligent image processing will:
|
| 14 |
+
- Automatically segment and identify the burn area
|
| 15 |
+
- Classify the burn degree (1st, 2nd, or 3rd degree)
|
| 16 |
+
- Provide immediate care guidelines and treatment recommendations
|
| 17 |
+
|
| 18 |
+
## π CPR Training & Analysis
|
| 19 |
+
|
| 20 |
+
### Educational Mode
|
| 21 |
+
Perfect your CPR technique at home or in training centers:
|
| 22 |
+
- Record yourself performing CPR (Cardiopulmonary Resuscitation)
|
| 23 |
+
- Upload the video for comprehensive analysis
|
| 24 |
+
- Receive detailed feedback on compression rate, depth, and technique
|
| 25 |
+
- Get posture corrections and improvement suggestions
|
| 26 |
+
|
| 27 |
+
### Real-Time Emergency Mode
|
| 28 |
+
For actual emergency situations:
|
| 29 |
+
- Mount your phone in portrait mode
|
| 30 |
+
- Start live CPR guidance with real-time feedback
|
| 31 |
+
- Get instant coaching during critical moments
|
| 32 |
+
- Ensure proper technique when every second counts
|
| 33 |
+
|
| 34 |
+
# π Installation
|
| 35 |
+
1. Clone the repository:
|
| 36 |
+
```bash
|
| 37 |
+
git clone https://github.com/El7a2ny-Graduation-Project/Backend.git
|
| 38 |
+
```
|
| 39 |
+
2. Navigate to the project directory:
|
| 40 |
+
```bash
|
| 41 |
+
cd Backend
|
| 42 |
+
```
|
| 43 |
+
3. Create a virtual environment (optional but recommended):
|
| 44 |
+
```bash
|
| 45 |
+
python 3.10 -m venv venv
|
| 46 |
+
```
|
| 47 |
+
4. Activate the virtual environment:
|
| 48 |
+
- On Windows:
|
| 49 |
+
```bash
|
| 50 |
+
venv\Scripts\activate
|
| 51 |
+
```
|
| 52 |
+
- On macOS/Linux:
|
| 53 |
+
```bash
|
| 54 |
+
source venv/bin/activate
|
| 55 |
+
```
|
| 56 |
+
5. Install the required packages:
|
| 57 |
+
```bash
|
| 58 |
+
pip install -r requirements.txt
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
# Technical Setup
|
| 62 |
+
|
| 63 |
+
## Framework
|
| 64 |
+
This backend is built using the **FastAPI** Python framework and can be run both locally and accessed through our deployed instance on Hugging Face.
|
| 65 |
+
|
| 66 |
+
## Deployment Options
|
| 67 |
+
|
| 68 |
+
### π Production Deployment (Recommended)
|
| 69 |
+
For most application features, we recommend using our deployed instance on Hugging Face:
|
| 70 |
+
|
| 71 |
+
**Base URL:** `https://husseinhadidy-deploy-el7a2ny-application.hf.space`
|
| 72 |
+
|
| 73 |
+
**Supported Features:**
|
| 74 |
+
- Skin Burns Analysis
|
| 75 |
+
- ECG Analysis
|
| 76 |
+
- CPR Education Mode
|
| 77 |
+
|
| 78 |
+
### π Local Deployment
|
| 79 |
+
For the **CPR Real-Time Mode**, local deployment is recommended due to latency requirements. The Hugging Face free container has limited specifications that may not provide optimal performance for real-time socket connections.
|
| 80 |
+
|
| 81 |
+
3. **Run the Application**
|
| 82 |
+
```bash
|
| 83 |
+
uvicorn app:app --host 0.0.0.0 --port 8000
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
2. **Access the API**
|
| 87 |
+
- Local URL: `http://localhost:8000`
|
| 88 |
+
- API Documentation: `http://localhost:8000/docs`
|
| 89 |
+
|
| 90 |
+
# π Bugs, Suggestions or Questions
|
| 91 |
+
Please be welcome to submit an issue.
|
| 92 |
+
|
| 93 |
+
# π₯ Run Demo
|
| 94 |
+
## CPR Module
|
| 95 |
+
1. Navigate to the project directory:
|
| 96 |
+
```bash
|
| 97 |
+
cd Backend
|
| 98 |
+
```
|
| 99 |
+
2. Run the test demo for the CPR's educational mode:
|
| 100 |
+
```bash
|
| 101 |
+
python -m Demo.demo -i "path_to_your_video.mp4" -o "path_to_output_directory"
|
| 102 |
+
```
|
| 103 |
+
For example:
|
| 104 |
+
```bash
|
| 105 |
+
python -m Demo.demo -i "Demo/Dataset/02.mp4" -o "Demo/Output"
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
# π₯ Collaborators
|
| 109 |
+
<table>
|
| 110 |
+
<tr>
|
| 111 |
+
<td align="center">
|
| 112 |
+
<a href="https://github.com/FatemaKotb">
|
| 113 |
+
<img src="https://avatars.githubusercontent.com/u/101884853?v=4" width="100;" alt="Fatema-Kotb"/>
|
| 114 |
+
</a>
|
| 115 |
+
</td>
|
| 116 |
+
<td align="center">
|
| 117 |
+
<a href="https://github.com/Hussein-Hadidy">
|
| 118 |
+
<img src="https://avatars.githubusercontent.com/u/111172851?v=4" width="100;" alt="Hussein-Hadidy"/>
|
| 119 |
+
</a>
|
| 120 |
+
</td>
|
| 121 |
+
<td align="center">
|
| 122 |
+
<a href="https://github.com/rana-abdalla1">
|
| 123 |
+
<img src="https://avatars.githubusercontent.com/u/111182872?v=4" width="100;" alt="Rana-Abdalla"/>
|
| 124 |
+
</a>
|
| 125 |
+
</td>
|
| 126 |
+
<td align="center">
|
| 127 |
+
<a href="https://github.com/Sherif-Hatem1">
|
| 128 |
+
<img src="https://avatars.githubusercontent.com/u/125387417?v=4" width="100;" alt="Sherif-Hatem"/>
|
| 129 |
+
</a>
|
| 130 |
+
</td>
|
| 131 |
+
</tr>
|
| 132 |
+
<tr>
|
| 133 |
+
<td align="center">
|
| 134 |
+
<a href="https://github.com/FatemaKotb">
|
| 135 |
+
Fatema Kotb
|
| 136 |
+
</a>
|
| 137 |
+
</td>
|
| 138 |
+
<td align="center">
|
| 139 |
+
<a href="https://github.com/Hussein-Hadidy">
|
| 140 |
+
Hussein Hadidy
|
| 141 |
+
</a>
|
| 142 |
+
</td>
|
| 143 |
+
<td align="center">
|
| 144 |
+
<a href="https://github.com/rana-abdalla1">
|
| 145 |
+
Rana Abdalla
|
| 146 |
+
</a>
|
| 147 |
+
</td>
|
| 148 |
+
<td align="center">
|
| 149 |
+
<a href="https://github.com/Sherif-Hatem1">
|
| 150 |
+
Sherif Hatem
|
| 151 |
+
</a>
|
| 152 |
+
</td>
|
| 153 |
+
</tr>
|
| 154 |
+
<tr>
|
| 155 |
+
<td align="center">
|
| 156 |
+
CPR Module Implementation
|
| 157 |
+
</td>
|
| 158 |
+
<td align="center">
|
| 159 |
+
CPR Module Testing & Application Development
|
| 160 |
+
</td>
|
| 161 |
+
<td align="center">
|
| 162 |
+
ECG Abalysis Module Implementation
|
| 163 |
+
</td>
|
| 164 |
+
<td align="center">
|
| 165 |
+
Skin Burns Module Implementation & Application Development
|
| 166 |
+
</td>
|
| 167 |
+
</tr>
|
| 168 |
+
</table>
|
app.py
CHANGED
|
@@ -23,24 +23,24 @@ from fastapi import WebSocket, WebSocketDisconnect
|
|
| 23 |
import base64
|
| 24 |
import cv2
|
| 25 |
import time
|
| 26 |
-
from CPR.CPRAnalyzer import CPRAnalyzer as OfflineAnalyzer
|
| 27 |
import tempfile
|
| 28 |
import matplotlib.pyplot as plt
|
| 29 |
import json
|
| 30 |
import asyncio
|
| 31 |
import concurrent.futures
|
| 32 |
-
from CPRRealTime.main import CPRAnalyzer as RealtimeAnalyzer
|
| 33 |
from threading import Thread
|
| 34 |
from starlette.responses import StreamingResponse
|
| 35 |
import threading
|
| 36 |
import queue
|
| 37 |
-
from CPRRealTime.analysis_socket_server import AnalysisSocketServer # adjust if needed
|
| 38 |
-
from CPRRealTime.logging_config import cpr_logger
|
| 39 |
import logging
|
| 40 |
import sys
|
| 41 |
import re
|
| 42 |
import signal
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
app = FastAPI()
|
| 46 |
|
|
|
|
| 23 |
import base64
|
| 24 |
import cv2
|
| 25 |
import time
|
|
|
|
| 26 |
import tempfile
|
| 27 |
import matplotlib.pyplot as plt
|
| 28 |
import json
|
| 29 |
import asyncio
|
| 30 |
import concurrent.futures
|
|
|
|
| 31 |
from threading import Thread
|
| 32 |
from starlette.responses import StreamingResponse
|
| 33 |
import threading
|
| 34 |
import queue
|
|
|
|
|
|
|
| 35 |
import logging
|
| 36 |
import sys
|
| 37 |
import re
|
| 38 |
import signal
|
| 39 |
|
| 40 |
+
from CPR_Module.Educational_Mode.CPRAnalyzer import CPRAnalyzer as OfflineAnalyzer
|
| 41 |
+
from CPR_Module.Emergency_Mode.main import CPRAnalyzer as RealtimeAnalyzer
|
| 42 |
+
from CPR_Module.Common.analysis_socket_server import AnalysisSocketServer # adjust if needed
|
| 43 |
+
from CPR_Module.Common.logging_config import cpr_logger
|
| 44 |
|
| 45 |
app = FastAPI()
|
| 46 |
|