| import cv2 |
| import time |
| import math |
| import numpy as np |
| import os |
| import sys |
|
|
| from CPR_Module.Emergency_Mode.pose_estimation import PoseEstimator |
| from CPR_Module.Emergency_Mode.metrics_calculator import MetricsCalculator |
| from CPR_Module.Emergency_Mode.graph_plotter import GraphPlotter |
|
|
| from CPR_Module.Common.role_classifier import RoleClassifier |
| from CPR_Module.Common.chest_initializer import ChestInitializer |
| from CPR_Module.Common.posture_analyzer import PostureAnalyzer |
| from CPR_Module.Common.wrists_midpoint_analyzer import WristsMidpointAnalyzer |
| from CPR_Module.Common.shoulders_analyzer import ShouldersAnalyzer |
| from CPR_Module.Common.warnings_overlayer import WarningsOverlayer |
| from CPR_Module.Common.threaded_camera import ThreadedCamera |
| from CPR_Module.Common.analysis_socket_server import AnalysisSocketServer |
| from CPR_Module.Common.logging_config import cpr_logger |
|
|
| class CPRAnalyzer: |
| """Main CPR analysis pipeline with execution tracing""" |
| |
| def __init__(self, input_video, video_output_path, plot_output_path, requested_fps): |
| |
| cpr_logger.info(f"[INIT] Initializing CPR Analyzer") |
|
|
| |
| self.frame_counter = -1 |
| cpr_logger.info(f"[INIT] Frame counter initialized") |
|
|
| self.processed_frame_counter = 0 |
| cpr_logger.info(f"[INIT] Processed frame counter initialized") |
|
|
| |
| |
| self.socket_server = AnalysisSocketServer() |
| self.socket_server.start_server() |
| cpr_logger.info(f"[INIT] Socket server started") |
| |
| |
| self.cap = ThreadedCamera(input_video, requested_fps) |
| self.fps = self.cap.fps |
| cpr_logger.info(f"[INIT] Camera FPS: {self.fps}") |
|
|
| |
| self.video_output_path = video_output_path |
| self.video_writer = None |
| self._writer_initialized = False |
| cpr_logger.info(f"[INIT] Output path: {self.video_output_path}") |
|
|
| |
| self.plot_output_path = plot_output_path |
| |
| |
| self.pose_estimator = PoseEstimator(min_confidence=0.2) |
| self.role_classifier = RoleClassifier() |
| self.chest_initializer = ChestInitializer() |
| self.metrics_calculator = MetricsCalculator(shoulder_width_cm=45*0.65) |
| |
| |
| |
| |
|
|
| self.posture_analyzer = PostureAnalyzer(right_arm_angle_threshold=220, left_arm_angle_threshold=160, wrist_distance_threshold=170, history_length_to_average=10) |
| self.wrists_midpoint_analyzer = WristsMidpointAnalyzer() |
| self.shoulders_analyzer = ShouldersAnalyzer() |
| self.graph_plotter = GraphPlotter() |
| self.warnings_overlayer = WarningsOverlayer() |
| cpr_logger.info("[INIT] System components initialized") |
|
|
| |
| dummy_frame = np.zeros((480, 640, 3), dtype=np.uint8) |
| self.pose_estimator.detect_poses(dummy_frame) |
| cpr_logger.info("[INIT] Pose estimator warmed up with dummy data") |
|
|
| |
| self.prev_rescuer_processed_results = None |
| self.prev_patient_processed_results = None |
| self.prev_chest_params = None |
| self.prev_midpoint = None |
| self.prev_pose_results = None |
| cpr_logger.info("[INIT] Previous results initialized") |
|
|
| |
| self.frames_without_saying_existing_warnings = 0 |
|
|
| |
| self.MIN_ERROR_DURATION = 1.0 |
| self.REPORTING_INTERVAL = 5.0 |
| self.SAMPLING_INTERVAL = 0.1 |
| self.KEEP_RATE_AND_DEPTH_WARNINGS_INTERVAL = 3.0 |
| self.MIN_CHUNK_LENGTH_TO_REPORT = 3.0 |
| self.MIN_INTERVAL_BEFORE_READING_A_POSTURE_WARNING = 0.5 |
|
|
| |
| self.sampling_interval_frames = int(round(self.fps * self.SAMPLING_INTERVAL)) |
| self.error_threshold_frames = int(self.MIN_ERROR_DURATION / self.SAMPLING_INTERVAL) |
| self.reporting_interval_frames = int(self.REPORTING_INTERVAL / self.SAMPLING_INTERVAL) |
| self.return_rate_and_depth_warnings_interval_frames = int(self.KEEP_RATE_AND_DEPTH_WARNINGS_INTERVAL / self.SAMPLING_INTERVAL) |
| self.min_chunk_length_to_report_frames = int(self.MIN_CHUNK_LENGTH_TO_REPORT / self.SAMPLING_INTERVAL) |
| self.min_interval_before_reading_a_posture_warning_frames = int(self.MIN_INTERVAL_BEFORE_READING_A_POSTURE_WARNING / self.SAMPLING_INTERVAL) |
|
|
| |
| ratio = self.REPORTING_INTERVAL / self.SAMPLING_INTERVAL |
| assert math.isclose(ratio, round(ratio)), \ |
| f"Reporting interval ({self.REPORTING_INTERVAL}) must be an exact multiple of "\ |
| f"sampling interval ({self.SAMPLING_INTERVAL}). Actual ratio: {ratio:.2f}" |
|
|
| |
| assert self.MIN_ERROR_DURATION >= self.SAMPLING_INTERVAL, \ |
| f"Error detection window ({self.MIN_ERROR_DURATION}s) must be ≥ sampling interval ({self.SAMPLING_INTERVAL}s)" |
|
|
| cpr_logger.info(f"[INIT] Temporal alignment:") |
| cpr_logger.info(f" - {self.SAMPLING_INTERVAL}s sampling → {self.sampling_interval_frames} frames") |
| cpr_logger.info(f" - {self.MIN_ERROR_DURATION}s error detection → {self.error_threshold_frames} samples") |
| cpr_logger.info(f" - {self.REPORTING_INTERVAL}s reporting → {self.reporting_interval_frames} samples") |
|
|
| |
| |
| |
| self.possible_warnings = [ |
| "Right arm bent!", |
| "Left arm bent!", |
| "Left hand not on chest!", |
| "Right hand not on chest!", |
| "Both hands not on chest!", |
| ] |
| self.consecutive_frames_with_posture_errors_counters = {warning: 0 for warning in self.possible_warnings} |
|
|
| |
|
|
| self.rate_and_depth_warnings_from_the_last_report = [] |
| cpr_logger.info("[INIT] Rate and depth warnings from the last report initialized") |
|
|
| |
| self.has_not_processed_a_frame_successfully_before = True |
| self.waiting_to_start_new_chunk = False |
|
|
| self.chunk_start_frame_index = None |
| self.chunk_end_frame_index = None |
|
|
| |
| self.prev_is_part_of_a_posture_warnings_region = False |
| self.posture_warnings_region_start_frame_index = None |
| self.posture_warnings_region_end_frame_index = None |
|
|
| self.posture_warnings = [] |
| self.rate_and_depth_warnings = [] |
|
|
| |
| self.cached_posture_warnings = [] |
| self.cached_rate_and_depth_warnings = [] |
| self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames |
| cpr_logger.info("[INIT] Formatted warnings initialized") |
|
|
| def _initialize_video_writer(self, frame): |
| """Initialize writer with safe fallback options""" |
| height, width = frame.shape[:2] |
| effective_fps = self.fps / max(1, self.sampling_interval_frames) |
| |
| |
| for codec, ext, fmt in [('avc1', 'mp4', 'mp4v'), |
| ('MJPG', 'avi', 'avi'), |
| ('XVID', 'avi', 'avi')]: |
| fourcc = cv2.VideoWriter_fourcc(*codec) |
| writer = cv2.VideoWriter(self.video_output_path, fourcc, effective_fps, (width, height)) |
| |
| if writer.isOpened(): |
| self.video_writer = writer |
| self._writer_initialized = True |
| cpr_logger.info(f"[VIDEO WRITER] Initialized with {codec} codec") |
| return |
| else: |
| writer.release() |
| |
| cpr_logger.info("[ERROR] Failed to initialize any video writer!") |
| self._writer_initialized = False |
| |
| def _handle_chunk_end(self): |
| """Helper to handle chunk termination logic""" |
| self._calculate_rate_and_depth_for_chunk() |
| cpr_logger.info(f"[RUN ANALYSIS] Calculated rate and depth for the chunk") |
|
|
| rate_and_depth_warnings = self._get_rate_and_depth_warnings() |
|
|
| |
| if (self.chunk_end_frame_index - self.chunk_start_frame_index) < self.min_chunk_length_to_report_frames: |
| rate_and_depth_warnings = [] |
|
|
| self.cached_rate_and_depth_warnings = rate_and_depth_warnings |
| self.return_rate_and_depth_warnings_interval_frames_counter = self.return_rate_and_depth_warnings_interval_frames |
| cpr_logger.info(f"[RUN ANALYSIS] Retrieved rate and depth warnings for the chunk") |
|
|
| self.rate_and_depth_warnings.append({ |
| 'start_frame': self.chunk_start_frame_index, |
| 'end_frame': self.chunk_end_frame_index, |
| 'rate_and_depth_warnings': rate_and_depth_warnings, |
| }) |
| cpr_logger.info(f"[RUN ANALYSIS] Assigned rate and depth warnings region data") |
|
|
| self.shoulders_analyzer.reset_shoulder_distances() |
| self.wrists_midpoint_analyzer.reset_midpoint_history() |
| cpr_logger.info(f"[RUN ANALYSIS] Reset shoulder distances and midpoint history for the chunk") |
| |
| def _handle_posture_warnings_region_end(self): |
| """Helper to handle posture warnings region termination""" |
| self.posture_warnings.append({ |
| 'start_frame': self.posture_warnings_region_start_frame_index, |
| 'end_frame': self.posture_warnings_region_end_frame_index, |
| 'posture_warnings': self.cached_posture_warnings.copy(), |
| }) |
| cpr_logger.info(f"[RUN ANALYSIS] Assigned posture warnings region data") |
|
|
| def _start_new_chunk(self, chunk_type="chunk"): |
| """Helper to initialize new chunk""" |
| self.chunk_start_frame_index = self.frame_counter |
| self.waiting_to_start_new_chunk = False |
| cpr_logger.info(f"[CHUNK] New {chunk_type} started at {self.frame_counter}") |
| |
| def _start_new_posture_warnings_region(self): |
| """Helper to initialize new posture warnings region""" |
| self.posture_warnings_region_start_frame_index = self.frame_counter |
| cpr_logger.info(f"[POSTURE WARNINGS] New region started at {self.frame_counter}") |
| |
| def run_analysis(self): |
| try: |
| cpr_logger.info("[RUN ANALYSIS] Starting analysis") |
| |
| |
| |
| self.cap.start_capture() |
| cpr_logger.info("[RUN ANALYSIS] Camera capture started") |
|
|
| |
| main_loop_start_time = time.time() |
| cpr_logger.info("[RUN ANALYSIS] Main loop started") |
| while True: |
| |
| |
| frame = self.cap.read() |
|
|
| |
| if frame is None: |
| cpr_logger.info("Camera stream ended") |
| |
| |
| if self.prev_is_part_of_a_posture_warnings_region: |
| |
| self.posture_warnings_region_end_frame_index = self.frame_counter |
| cpr_logger.info(f"[RUN ANALYSIS] End of posture warnings region detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region start frame: {self.posture_warnings_region_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region end frame: {self.posture_warnings_region_end_frame_index}") |
| self._handle_posture_warnings_region_end() |
| |
| elif self.chunk_start_frame_index is not None: |
| |
| self.chunk_end_frame_index = self.frame_counter |
| cpr_logger.info(f"[RUN ANALYSIS] End of chunk detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk start frame: {self.chunk_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk end frame: {self.chunk_end_frame_index}") |
| self._handle_chunk_end() |
| break |
| |
| |
| self.frame_counter += 1 |
|
|
| cpr_logger.info(f"\n[FRAME {int(self.frame_counter)}]") |
| |
| |
| if self.frame_counter % self.sampling_interval_frames != 0: |
| |
| |
| formatted_warnings = self._format_warnings() |
| cpr_logger.info(f"[RUN ANALYSIS] Formatted warnings: {formatted_warnings}") |
| |
| |
| self.socket_server.warning_queue.put(formatted_warnings) |
| cpr_logger.info(f"[RUN ANALYSIS] Sent warnings to socket server") |
| |
| cpr_logger.info(f"[SKIP FRAME] Skipping frame") |
| continue |
| |
| |
| frame = self._handle_frame_rotation(frame) |
| cpr_logger.info(f"[RUN ANALYSIS] Rotated frame") |
|
|
| |
| |
| |
| |
| posture_warnings, has_appended_midpoint = self._process_frame(frame) |
| cpr_logger.info(f"[RUN ANALYSIS] Processed frame") |
|
|
| |
| |
| |
| is_part_of_a_posture_warnings_region = len(posture_warnings) > 0 |
|
|
| |
| is_start_of_posture_warnings_region = (not self.prev_is_part_of_a_posture_warnings_region) and is_part_of_a_posture_warnings_region |
| is_end_of_posture_warnings_region = self.prev_is_part_of_a_posture_warnings_region and not is_part_of_a_posture_warnings_region |
| |
| |
| self.prev_is_part_of_a_posture_warnings_region = is_part_of_a_posture_warnings_region |
|
|
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region flags updated") |
|
|
| |
| |
| if is_start_of_posture_warnings_region: |
| cpr_logger.info(f"[RUN ANALYSIS] Case 1: posture warnings region after a chunk") |
|
|
| |
| self._start_new_posture_warnings_region() |
|
|
| |
| if self.chunk_start_frame_index is not None: |
| self.chunk_end_frame_index = self.frame_counter - 1 |
| cpr_logger.info(f"[RUN ANALYSIS] End of chunk detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk start frame: {self.chunk_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk end frame: {self.chunk_end_frame_index}") |
| self._handle_chunk_end() |
|
|
| |
| if (self.cached_posture_warnings != posture_warnings) and (is_part_of_a_posture_warnings_region) and (not is_start_of_posture_warnings_region) and (not is_end_of_posture_warnings_region): |
| cpr_logger.info(f"[RUN ANALYSIS] Case 2: posture warnings region after a posture warnings region") |
|
|
| |
| self.posture_warnings_region_end_frame_index = self.frame_counter - 1 |
| cpr_logger.info(f"[RUN ANALYSIS] End of posture warnings region detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region start frame: {self.posture_warnings_region_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region end frame: {self.posture_warnings_region_end_frame_index}") |
| self._handle_posture_warnings_region_end() |
|
|
| |
| self._start_new_posture_warnings_region() |
| |
| |
| if is_end_of_posture_warnings_region: |
| cpr_logger.info(f"[RUN ANALYSIS] Case 3: chunk after a posture warnings region") |
|
|
| |
| self.waiting_to_start_new_chunk = True |
| cpr_logger.info(f"[RUN ANALYSIS] Waiting to start a new chunk") |
| new_chunk_type = "chunk" |
|
|
| |
| self.posture_warnings_region_end_frame_index = self.frame_counter - 1 |
| cpr_logger.info(f"[RUN ANALYSIS] End of posture warnings region detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region start frame: {self.posture_warnings_region_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Posture warnings region end frame: {self.posture_warnings_region_end_frame_index}") |
| self._handle_posture_warnings_region_end() |
| |
| |
| if (not is_part_of_a_posture_warnings_region) and (not is_end_of_posture_warnings_region) and (self.processed_frame_counter % self.reporting_interval_frames == 0): |
| cpr_logger.info(f"[RUN ANALYSIS] Case 4: chunk after a chunk") |
|
|
| |
| if self.chunk_start_frame_index is not None and self.chunk_start_frame_index != self.frame_counter: |
| self.chunk_end_frame_index = self.frame_counter |
| cpr_logger.info(f"[RUN ANALYSIS] End of chunk detected") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk start frame: {self.chunk_start_frame_index}") |
| cpr_logger.info(f"[RUN ANALYSIS] Chunk end frame: {self.chunk_end_frame_index}") |
| self._handle_chunk_end() |
|
|
| |
| self.waiting_to_start_new_chunk = True |
| cpr_logger.info(f"[RUN ANALYSIS] Waiting to start a new chunk") |
|
|
| new_chunk_type = "mini chunk" |
| |
| |
| if (self.waiting_to_start_new_chunk) and (has_appended_midpoint): |
| cpr_logger.info(f"[RUN ANALYSIS] Follow up on cases 3 and 4") |
|
|
| if (new_chunk_type == "chunk") or (new_chunk_type == "mini chunk" and self.frame_counter != self.chunk_end_frame_index): |
| self._start_new_chunk() |
|
|
| |
| |
| |
| composed_frame = self._compose_frame(frame, is_part_of_a_posture_warnings_region) |
|
|
| if composed_frame is not None: |
| frame = composed_frame |
| cpr_logger.info(f"[RUN ANALYSIS] Frame composed successfully") |
| else: |
| cpr_logger.info(f"[RUN ANALYSIS] Frame composition failed") |
|
|
| |
| if frame is not None and not self._writer_initialized: |
| self._initialize_video_writer(frame) |
| cpr_logger.info(f"[VIDEO WRITER] Initialized video writer") |
|
|
| |
| if self._writer_initialized: |
| |
| if frame.dtype != np.uint8: |
| frame = frame.astype(np.uint8) |
| if len(frame.shape) == 2: |
| frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) |
| |
| try: |
| self.video_writer.write(frame) |
| except Exception as e: |
| cpr_logger.error(f"[WRITE ERROR] {str(e)}") |
| self._writer_initialized = False |
| |
| |
| |
| self.cached_posture_warnings = posture_warnings |
| |
| |
| formatted_warnings = self._format_warnings() |
| cpr_logger.info(f"[RUN ANALYSIS] Formatted warnings: {formatted_warnings}") |
|
|
| |
| self.socket_server.warning_queue.put(formatted_warnings) |
| cpr_logger.info(f"[RUN ANALYSIS] Sent warnings to socket server") |
|
|
| self.processed_frame_counter += 1 |
| cpr_logger.info(f"[RUN ANALYSIS] Processed frame counter: {self.processed_frame_counter}") |
| |
| |
| if cv2.waitKey(1) & 0xFF == ord('q'): |
| cpr_logger.info("[RUN ANALYSIS] 'q' pressed, exiting loop.") |
| break |
|
|
| main_loop_end_time = time.time() |
| elapsed_time = main_loop_end_time - main_loop_start_time |
| cpr_logger.info(f"[TIMING] Main loop elapsed time: {elapsed_time:.2f}s") |
|
|
| except Exception as e: |
| cpr_logger.error(f"[ERROR] An error occurred during main execution loop: {str(e)}") |
|
|
| finally: |
| report_and_plot_start_time = time.time() |
| |
| self.cap.release() |
| self.cap = None |
| |
| if self.video_writer is not None: |
| self.video_writer.release() |
| cpr_logger.info(f"[VIDEO WRITER] Released writer. File should be at: {os.path.abspath(self.video_output_path)}") |
| cv2.destroyAllWindows() |
| cpr_logger.info("[RUN ANALYSIS] Released video capture and destroyed all windows") |
|
|
| self._calculate_rate_and_depth_for_all_chunks() |
| cpr_logger.info("[RUN ANALYSIS] Calculated weighted averages of the metrics across all chunks") |
|
|
| self._plot_full_motion_curve_for_all_chunks() |
| cpr_logger.info("[RUN ANALYSIS] Plotted full motion curve") |
|
|
| self.warnings_overlayer.add_warnings_to_processed_video(self.video_output_path, self.sampling_interval_frames, self.rate_and_depth_warnings, self.posture_warnings) |
| cpr_logger.info("[RUN ANALYSIS] Added warnings to processed video") |
|
|
| try: |
| if os.path.exists(self.video_output_path): |
| os.remove(self.video_output_path) |
| cpr_logger.info(f"[CLEANUP] Successfully deleted video file: {self.video_output_path}") |
| else: |
| cpr_logger.warning(f"[CLEANUP] Video file not found at: {self.video_output_path}") |
| except Exception as e: |
| cpr_logger.error(f"[ERROR] Failed to delete video file: {str(e)}") |
|
|
| report_and_plot_end_time = time.time() |
| report_and_plot_elapsed_time = report_and_plot_end_time - report_and_plot_start_time |
| cpr_logger.info(f"[TIMING] Report and plot elapsed time: {report_and_plot_elapsed_time:.2f}s") |
|
|
| def _format_warnings(self): |
| """Combine warnings into a structured response with N-frame interval reporting.""" |
| posture_warnings = self.cached_posture_warnings |
| rate_and_depth_warnings = self.cached_rate_and_depth_warnings |
| has_warnings = posture_warnings or rate_and_depth_warnings |
|
|
| if has_warnings: |
| |
| if self.frames_without_saying_existing_warnings >= self.min_interval_before_reading_a_posture_warning_frames: |
| self.frames_without_saying_existing_warnings = 0 |
| |
| |
| if posture_warnings: |
| return { |
| "status": "warning", |
| "posture_warnings": posture_warnings, |
| "rate_and_depth_warnings": [] |
| } |
| |
| |
| if rate_and_depth_warnings and self.return_rate_and_depth_warnings_interval_frames_counter > 0: |
| self.return_rate_and_depth_warnings_interval_frames_counter -= 1 |
| return { |
| "status": "warning", |
| "posture_warnings": [], |
| "rate_and_depth_warnings": rate_and_depth_warnings |
| } |
| else: |
| |
| self.frames_without_saying_existing_warnings += 1 |
| return { |
| "status": "pause", |
| "posture_warnings": [], |
| "rate_and_depth_warnings": [] |
| } |
|
|
| |
| else: |
| |
| self.frames_without_saying_existing_warnings = self.min_interval_before_reading_a_posture_warning_frames |
| return { |
| "status": "ok", |
| "posture_warnings": [], |
| "rate_and_depth_warnings": [] |
| } |
|
|
| def _handle_frame_rotation(self, frame): |
| if frame.shape[1] > frame.shape[0]: |
| frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) |
| return frame |
|
|
| def _process_frame(self, frame): |
| |
| warnings = [] |
|
|
| |
| has_appended_midpoint = False |
| |
| |
| pose_results = self.pose_estimator.detect_poses(frame) |
|
|
| |
| if not pose_results: |
| pose_results = self.prev_pose_results |
| cpr_logger.info("[POSE ESTIMATION] No pose detected, using previous results (could be None)") |
| else: |
| self.prev_pose_results = pose_results |
| |
| if not pose_results: |
| cpr_logger.info("[POSE ESTIMATION] Insufficient data for processing") |
| return warnings, has_appended_midpoint |
| |
| |
| rescuer_processed_results, patient_processed_results = self.role_classifier.classify_roles(pose_results, self.prev_rescuer_processed_results, self.prev_patient_processed_results) |
|
|
| |
| if not rescuer_processed_results: |
| rescuer_processed_results = self.prev_rescuer_processed_results |
| cpr_logger.info("[ROLE CLASSIFICATION] No rescuer detected, using previous results (could be None)") |
| else: |
| self.prev_rescuer_processed_results = rescuer_processed_results |
| |
| if not patient_processed_results: |
| patient_processed_results = self.prev_patient_processed_results |
| cpr_logger.info("[ROLE CLASSIFICATION] No patient detected, using previous results (could be None)") |
| else: |
| self.prev_patient_processed_results = patient_processed_results |
| |
| if not rescuer_processed_results or not patient_processed_results: |
| cpr_logger.info("[ROLE CLASSIFICATION] Insufficient data for processing") |
| return warnings, has_appended_midpoint |
| |
| |
| self.role_classifier.rescuer_processed_results = rescuer_processed_results |
| self.role_classifier.patient_processed_results = patient_processed_results |
| cpr_logger.info(f"[ROLE CLASSIFICATION] Updated role classifier with new results") |
| |
| |
| chest_params = self.chest_initializer.estimate_chest_region(patient_processed_results["keypoints"], patient_processed_results["bounding_box"], frame_width=frame.shape[1], frame_height=frame.shape[0]) |
| |
| |
| if not chest_params: |
| chest_params = self.prev_chest_params |
| cpr_logger.info("[CHEST ESTIMATION] No chest region detected, using previous results (could be None)") |
| else: |
| self.prev_chest_params = chest_params |
| |
| if not chest_params: |
| cpr_logger.info("[CHEST ESTIMATION] Insufficient data for processing") |
| return warnings, has_appended_midpoint |
|
|
| |
| self.chest_initializer.chest_params = chest_params |
| self.chest_initializer.chest_params_history.append(self.chest_initializer.chest_params) |
|
|
| |
| |
| expected_chest_params = self.chest_initializer.estimate_chest_region_weighted_avg(frame_width=frame.shape[1], frame_height=frame.shape[0]) |
|
|
| |
| if not expected_chest_params: |
| self.chest_initializer.expected_chest_params = self.chest_initializer.chest_params |
| else: |
| self.chest_initializer.expected_chest_params = expected_chest_params |
| |
| |
| cpr_logger.info(f"[POSTURE ANALYSIS] Analyzing posture") |
| current_warnings = self.posture_analyzer.validate_posture(rescuer_processed_results["keypoints"], self.chest_initializer.expected_chest_params) |
| cpr_logger.info(f"[POSTURE ANALYSIS] Posture analysis completed") |
|
|
| |
| for warning in self.possible_warnings: |
| if warning in current_warnings: |
| self.consecutive_frames_with_posture_errors_counters[warning] += 1 |
| else: |
| self.consecutive_frames_with_posture_errors_counters[warning] = 0 |
|
|
| |
| warnings = [ |
| warning for warning in self.possible_warnings |
| if self.consecutive_frames_with_posture_errors_counters[warning] >= self.error_threshold_frames |
| ] |
|
|
| |
| self.posture_analyzer.warnings = warnings |
| cpr_logger.info(f"[POSTURE ANALYSIS] Updated posture analyzer with new results") |
|
|
| |
| midpoint = self.wrists_midpoint_analyzer.detect_wrists_midpoint(rescuer_processed_results["keypoints"]) |
|
|
| |
| if not midpoint: |
| midpoint = self.prev_midpoint |
| cpr_logger.info("[WRIST MIDPOINT DETECTION] No midpoint detected, using previous results (could be None)") |
| else: |
| self.prev_midpoint = midpoint |
|
|
| if not midpoint: |
| cpr_logger.info("[WRIST MIDPOINT DETECTION] Insufficient data for processing") |
| return warnings, has_appended_midpoint |
| cpr_logger.error(f"[WRIST MIDPOINT DETECTION] Midpoint detected: {midpoint}") |
| if len(warnings) == 0: |
| |
| has_appended_midpoint = True |
| self.wrists_midpoint_analyzer.midpoint = midpoint |
| self.wrists_midpoint_analyzer.midpoint_history.append(midpoint) |
| cpr_logger.info(f"[WRIST MIDPOINT DETECTION] Updated wrist midpoint analyzer with new results") |
|
|
| |
| shoulder_distance = self.shoulders_analyzer.calculate_shoulder_distance(rescuer_processed_results["keypoints"]) |
| if shoulder_distance is not None: |
| self.shoulders_analyzer.shoulder_distance = shoulder_distance |
| self.shoulders_analyzer.shoulder_distance_history.append(shoulder_distance) |
| cpr_logger.info(f"[SHOULDER DISTANCE] Updated shoulder distance analyzer with new results") |
|
|
| return warnings, has_appended_midpoint |
| |
| def _compose_frame(self, frame, is_part_of_a_posture_warnings_region): |
| |
| if frame is not None: |
| frame = self.chest_initializer.draw_expected_chest_region(frame) |
| cpr_logger.info(f"[VISUALIZATION] Drawn chest region") |
| |
| if frame is not None: |
| if not is_part_of_a_posture_warnings_region: |
| frame = self.wrists_midpoint_analyzer.draw_midpoint(frame) |
| cpr_logger.info(f"[VISUALIZATION] Drawn midpoint") |
| |
| return frame |
|
|
| def _calculate_rate_and_depth_for_chunk(self): |
| try: |
| 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) |
|
|
| if result == False: |
| cpr_logger.info("[ERROR] Failed to calculate metrics for the chunk") |
| return |
| |
| except Exception as e: |
| cpr_logger.error(f"[ERROR] Metric calculation failed: {str(e)}") |
|
|
| def _calculate_rate_and_depth_for_all_chunks(self): |
| try: |
| self.metrics_calculator.calculate_rate_and_depth_for_all_chunk() |
| cpr_logger.info(f"[METRICS] Weighted averages calculated") |
| except Exception as e: |
| cpr_logger.error(f"[ERROR] Failed to calculate weighted averages: {str(e)}") |
| |
| def _plot_full_motion_curve_for_all_chunks(self): |
| try: |
| self.graph_plotter.plot_motion_curve_for_all_chunks(self.metrics_calculator.chunks_y_preprocessed, |
| self.metrics_calculator.chunks_peaks, |
| self.metrics_calculator.chunks_depth, |
| self.metrics_calculator.chunks_rate, |
| self.metrics_calculator.chunks_start_and_end_indices, |
| self.posture_warnings, |
| self.sampling_interval_frames, |
| self.fps, |
| self.plot_output_path) |
| cpr_logger.info("[PLOT] Full motion curve plotted") |
| except Exception as e: |
| cpr_logger.error(f"[ERROR] Failed to plot full motion curve: {str(e)}") |
| |
| def _get_rate_and_depth_warnings(self): |
| rate_and_depth_warnings = self.metrics_calculator.get_rate_and_depth_warnings() |
| cpr_logger.info(f"[VISUALIZATION] Rate and depth warnings data: {rate_and_depth_warnings}") |
|
|
| return rate_and_depth_warnings |
|
|