diff --git a/.gitignore b/.gitignore index 5d381cc..16c6219 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +*.mp4 # PyInstaller # Usually these files are written by a python script from a template diff --git a/README.md b/README.md index f354122..fd9c09b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,91 @@ -# vision-ai-test +# SOP Compliance Analyzer -Belajar vision AI \ No newline at end of file +This program analyzes human activity in videos to check if workers are following Standard Operating Procedures (SOPs). It uses computer vision techniques to detect people, estimate their poses, classify activities, and evaluate compliance with predefined SOPs. + +## Features + +- Person detection using YOLO model +- Pose estimation for activity analysis +- Activity classification (SOP compliance checking) +- Real-time video analysis with visual feedback +- Compliance reporting and logging +- Person tracking across video frames + +## Requirements + +- Python 3.7+ +- OpenCV +- PyTorch +- ONNX Runtime +- Ultralytics YOLO +- NumPy +- PyTorchVideo (for MobileNet3D support) + +## Installation + +1. Install the required packages: + ``` + pip install -r requirements.txt + ``` + +## Usage + +1. Prepare your video file and models: + - Place your video file in the project directory + - Ensure you have the YOLO models (`yolo11m-2_uniform.onnx` and `yolo11s-pose.pt`) + +2. Run the analyzer: + ``` + python activity_analyzer.py + ``` + +3. View the results: + - Real-time video display with compliance indicators + - Compliance report saved to `compliance_report.json` + - Summary statistics printed to console + +## How It Works + +1. **Person Detection**: Uses YOLO to detect people in each video frame +2. **Person Tracking**: Tracks individuals across frames using IoU-based matching +3. **Pose Estimation**: Estimates body pose for each detected person +4. **Activity Classification**: Classifies activities based on pose patterns +5. **SOP Compliance Check**: Evaluates if activities comply with predefined SOPs +6. **Visualization**: Displays results with color-coded bounding boxes +7. **Reporting**: Generates detailed compliance reports + +## SOP Activities + +The program recognizes the following SOP-compliant activities: +- Speaking with customer +- Inputting order +- Giving item to customer +- Cleaning table + +Non-compliant activities detected: +- Using mobile phone +- Talking with colleagues +- Idle behavior + +## Customization + +You can customize the program by modifying: +- Activity definitions in the `sop_activities` and `non_compliant_activities` lists +- Confidence thresholds for detection and classification +- Tracking parameters +- Compliance rules in the `check_sop_compliance` method + +## Output + +The program generates: +1. Real-time video display with bounding boxes (green= compliant, red=non-compliant) +2. `compliance_report.json` with detailed activity logs +3. Console summary with compliance statistics + +## Troubleshooting + +If you encounter issues: +1. Ensure all required models are in the correct location +2. Check that your video file is accessible +3. Verify all dependencies are installed correctly +4. Adjust confidence thresholds if detection is inaccurate diff --git a/activity_analyzer.py b/activity_analyzer.py new file mode 100644 index 0000000..acc6d25 --- /dev/null +++ b/activity_analyzer.py @@ -0,0 +1,578 @@ +import cv2 +import onnxruntime as ort +import torch +import torch.nn as nn +import numpy as np +from torchvision import transforms +from ultralytics import YOLO +import time +from collections import deque +import json +import urllib +from torchvision.transforms import Compose, Lambda +from torchvision.transforms._transforms_video import ( + CenterCropVideo, + NormalizeVideo, +) + +# Try to import PyTorchVideo for MobileNet3D +try: + import torch.hub + PYTORCHVIDEO_AVAILABLE = True +except ImportError: + PYTORCHVIDEO_AVAILABLE = False + print("PyTorchVideo not available, using simplified model") + +class ActivityAnalyzer: + def __init__(self, yolo_model_path, pose_model_path, video_path): + # Load YOLO model for person detection + self.yolo_model = YOLO(yolo_model_path) + + # Load YOLO pose estimation model + self.pose_model = YOLO(pose_model_path) + + # Video path + self.video_path = video_path + + # Transform for preprocessing + self.transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ]) + + # Activity labels + self.sop_activities = [ + "speaking_with_customer", + "inputting_order", + "giving_item", + "cleaning_table" + ] + + self.non_compliant_activities = [ + "using_mobile_phone", + "talking_with_colleagues", + "idle" + ] + + # Initialize activity labels first, then MobileNet3D + self.activity_labels = [ + "speaking_with_customer", + "inputting_order", + "giving_item", + "cleaning_table", + "using_mobile_phone", + "talking_with_colleagues", + "idle", + "bringing_menu", + "bringing_food", + "opening_door", + ] + + # Initialize MobileNet3D for activity classification + self.activity_model = self._initialize_mobilenet3d() + + # Person tracking + self.person_tracks = {} + self.next_person_id = 0 + + # Compliance tracking + self.compliance_log = [] + + def _initialize_mobilenet3d(self): + """Initialize MobileNet3D model for activity classification""" + if not PYTORCHVIDEO_AVAILABLE: + print("PyTorchVideo not available, using simplified model") + # Create a simplified version for demonstration + try: + model = nn.Sequential( + nn.Conv3d(3, 32, kernel_size=(3, 3, 3), padding=(1, 1, 1)), + nn.ReLU(), + nn.AdaptiveAvgPool3d((1, 1, 1)), + nn.Flatten(), + nn.Linear(32, len(self.activity_labels)) + ) + return model + except Exception as e: + print(f"Warning: Could not initialize simplified MobileNet3D model: {e}") + print("Falling back to heuristic-based classification") + return None + + # Try to load a real MobileNet3D model from PyTorchVideo + try: + # Load a pretrained MobileNet3D model + # Note: This is a placeholder - you would need to select an appropriate model + # For example: torch.hub.load('facebookresearch/pytorchvideo', 'slow_r50', pretrained=True) + # model = torch.hub.load('facebookresearch/pytorchvideo', 'slow_r50', pretrained=True) + model_name='X3D_L' + model_dir='C:/Users/suherdy.yacob/.cache/torch/hub/checkpoints/' + #model = torch.hub.load(repo_or_dir=model_dir,model = model_name, pretrained=True) + #model = torch.hub.load(model_dir, model_name, source='local', pretrained=True) + model = torch.hub.load('facebookresearch/pytorchvideo', 'slow_r50', pretrained=True) + # Modify the final layer to match our number of activity classes + # This is a simplified approach - in practice you might need a more complex adaptation + + # json_url = "https://dl.fbaipublicfiles.com/pyslowfast/dataset/class_names/kinetics_classnames.json" + # json_filename = "kinetics_classnames.json" + # try: urllib.URLopener().retrieve(json_url, json_filename) + # except: urllib.request.urlretrieve(json_url, json_filename) + # with open(json_filename, "r") as f: + # kinetics_classnames = json.load(f) + + # # Create an id to label name mapping + # self.activity_labels = [] + # kinetics_id_to_classname = {} + # for k, v in kinetics_classnames.items(): + # kinetics_id_to_classname[v] = str(k).replace('"', "") + # self.activity_labels.append(str(k).replace('"',"")) + + model.blocks[-1].proj = nn.Linear(model.blocks[-1].proj.in_features, len(self.activity_labels)) + return model + except Exception as e: + print(f"Warning: Could not load pretrained MobileNet3D model: {e}") + print("Using simplified model instead") + try: + # Fallback to simplified model + model = nn.Sequential( + nn.Conv3d(3, 32, kernel_size=(3, 3, 3), padding=(1, 1, 1)), + nn.ReLU(), + nn.AdaptiveAvgPool3d((1, 1, 1)), + nn.Flatten(), + nn.Linear(32, len(self.activity_labels)) + ) + return model + except Exception as e: + print(f"Warning: Could not initialize simplified MobileNet3D model: {e}") + print("Falling back to heuristic-based classification") + return None + + def extract_person_bboxes(self, results): + """Extract person bounding boxes from YOLO results""" + persons = [] + if results.boxes is not None: + for box in results.boxes: + # Assuming class 0 is 'person' in your model + if box.cls == 0 and box.conf > 0.5: + persons.append(box.xyxy[0].cpu().numpy()) + return persons + + def calculate_iou(self, box1, box2): + """Calculate Intersection over Union between two bounding boxes""" + x1 = max(box1[0], box2[0]) + y1 = max(box1[1], box2[1]) + x2 = min(box1[2], box2[2]) + y2 = min(box1[3], box2[3]) + + if x2 <= x1 or y2 <= y1: + return 0.0 + + intersection = (x2 - x1) * (y2 - y1) + area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) + area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) + union = area1 + area2 - intersection + + return intersection / union if union > 0 else 0 + + def track_persons(self, current_persons, frame=None): + """Simple tracking based on IoU""" + if not self.person_tracks: + # Initialize tracks for first frame + for person in current_persons: + self.person_tracks[self.next_person_id] = { + 'id': self.next_person_id, + 'bbox': person, + 'activity_history': deque(maxlen=30), + 'frame_crops': deque(maxlen=16), # For MobileNet3D + 'last_seen': 0 + } + self.next_person_id += 1 + return list(self.person_tracks.values()) + + # Match current persons with existing tracks + tracked_persons = [] + used_tracks = set() + + for person in current_persons: + best_match = None + best_iou = 0 + + for track_id, track in self.person_tracks.items(): + if track_id in used_tracks: + continue + + iou = self.calculate_iou(person, track['bbox']) + if iou > best_iou and iou > 0.3: # Threshold for matching + best_iou = iou + best_match = track_id + + if best_match is not None: + # Update existing track + self.person_tracks[best_match]['bbox'] = person + self.person_tracks[best_match]['last_seen'] = 0 + + # Extract and store frame crop for MobileNet3D + if frame is not None: + x1, y1, x2, y2 = map(int, person) + # Add padding + pad = 20 + x1 = max(0, x1 - pad) + y1 = max(0, y1 - pad) + x2 = min(frame.shape[1], x2 + pad) + y2 = min(frame.shape[0], y2 + pad) + + if x2 > x1 and y2 > y1: + crop = frame[y1:y2, x1:x2] + self.person_tracks[best_match]['frame_crops'].append(crop) + + used_tracks.add(best_match) + tracked_persons.append(self.person_tracks[best_match]) + else: + # Create new track + new_track = { + 'id': self.next_person_id, + 'bbox': person, + 'activity_history': deque(maxlen=30), + 'frame_crops': deque(maxlen=16), # For MobileNet3D + 'last_seen': 0 + } + + # Extract and store frame crop for MobileNet3D + if frame is not None: + x1, y1, x2, y2 = map(int, person) + # Add padding + pad = 20 + x1 = max(0, x1 - pad) + y1 = max(0, y1 - pad) + x2 = min(frame.shape[1], x2 + pad) + y2 = min(frame.shape[0], y2 + pad) + + if x2 > x1 and y2 > y1: + crop = frame[y1:y2, x1:x2] + new_track['frame_crops'].append(crop) + + self.person_tracks[self.next_person_id] = new_track + used_tracks.add(self.next_person_id) + tracked_persons.append(new_track) + self.next_person_id += 1 + + # Increment last_seen for unused tracks + for track_id, track in self.person_tracks.items(): + if track_id not in used_tracks: + self.person_tracks[track_id]['last_seen'] += 1 + + # Remove old tracks (not seen for 10 frames) + self.person_tracks = {k: v for k, v in self.person_tracks.items() if v['last_seen'] < 10} + + return tracked_persons + + def estimate_pose(self, frame, bbox): + """Estimate pose for a person in bounding box""" + x1, y1, x2, y2 = map(int, bbox) + # Add some padding + pad = 20 + x1 = max(0, x1 - pad) + y1 = max(0, y1 - pad) + x2 = min(frame.shape[1], x2 + pad) + y2 = min(frame.shape[0], y2 + pad) + + roi = frame[y1:y2, x1:x2] + if roi.size == 0: + return None + + # Use YOLO pose model + results = self.pose_model(roi) + return results + + def _preprocess_for_mobilenet3d(self, frame_sequence): + """Preprocess frame sequence for MobileNet3D input""" + if not frame_sequence: + return None + + # Convert to tensor and normalize + processed_frames = [] + for frame in frame_sequence: + # Resize frame to model input size + resized = cv2.resize(frame, (224, 224)) + # Convert BGR to RGB + rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) + # Normalize + normalized = rgb.astype(np.float32) / 255.0 + processed_frames.append(normalized) + + # Stack frames into tensor (C, D, H, W) format for 3D conv + if processed_frames: + frames_tensor = np.stack(processed_frames, axis=0) # (D, H, W, C) + frames_tensor = np.transpose(frames_tensor, (3, 0, 1, 2)) # (C, D, H, W) + return torch.tensor(frames_tensor, dtype=torch.float32).unsqueeze(0) # Add batch dimension + + return None + + def classify_activity(self, pose_results, track): + """Classify activity using MobileNet3D""" + # Use MobileNet3D if available, otherwise fall back to heuristic + if self.activity_model is not None: + try: + # Extract person crop from track + if 'frame_crops' not in track: + track['frame_crops'] = deque(maxlen=16) # 16 frames for 3D processing + + # Add current frame crop to sequence if available + # In a real implementation, you would extract the person crop from the current frame + # For this example, we'll simulate with a placeholder + + # If we have enough frames for 3D processing + if len(track['frame_crops']) >= 8: + # Preprocess frames for MobileNet3D + input_tensor = self._preprocess_for_mobilenet3d(list(track['frame_crops'])) + + if input_tensor is not None: + # Run inference + with torch.no_grad(): + outputs = self.activity_model(input_tensor) + probabilities = torch.softmax(outputs, dim=1) + confidence, predicted = torch.max(probabilities, 1) + + # Get activity label + activity = self.activity_labels[predicted.item()] + return activity, confidence.item() + + # If not enough frames or model fails, fall back to heuristic + pass + except Exception as e: + print(f"MobileNet3D classification failed: {e}") + # Fall back to heuristic classification + pass + + # Heuristic-based classification (fallback) + if len(pose_results) == 0: + return "idle", 0.5 + + keypoints = pose_results[0].keypoints + if keypoints is None or len(keypoints) == 0: + return "idle", 0.5 + + # Check if keypoints has xy attribute and it's not empty + if not hasattr(keypoints, 'xy') or len(keypoints.xy) == 0: + return "idle", 0.5 + + # Extract keypoints + kpts = keypoints.xy[0].cpu().numpy() if len(keypoints.xy) > 0 else np.array([]) + confs = keypoints.conf[0].cpu().numpy() if hasattr(keypoints, 'conf') and keypoints.conf is not None and len(keypoints.conf) > 0 else np.ones(len(kpts)) if len(kpts) > 0 else np.array([]) + + # If no keypoints detected, return idle + if len(kpts) == 0: + return "idle", 0.5 + + # Simple heuristic-based activity classification + # Check if person is using mobile phone (head tilted down, hand near face) + if len(kpts) > 10: + # Check if nose and eyes are visible + nose_visible = confs[0] > 0.5 if len(confs) > 0 else False + left_eye_visible = confs[1] > 0.5 if len(confs) > 1 else False + right_eye_visible = confs[2] > 0.5 if len(confs) > 2 else False + + # Check if hands are near head (possible mobile phone use) + left_wrist_visible = confs[9] > 0.5 if len(confs) > 9 else False + right_wrist_visible = confs[10] > 0.5 if len(confs) > 10 else False + + if nose_visible and (left_eye_visible or right_eye_visible) and (left_wrist_visible or right_wrist_visible): + # Simple check for hand near head + if len(kpts) > 10: + head_y = kpts[0][1] # nose + left_hand_y = kpts[9][1] if left_wrist_visible else None + right_hand_y = kpts[10][1] if right_wrist_visible else None + + if left_hand_y is not None and abs(left_hand_y - head_y) < 100: + return "using_mobile_phone", 0.8 + if right_hand_y is not None and abs(right_hand_y - head_y) < 100: + return "using_mobile_phone", 0.8 + + # Add activity to history + track['activity_history'].append({ + 'timestamp': time.time(), + 'keypoints': kpts.tolist() if len(kpts) > 0 else [], + 'confidences': confs.tolist() if len(confs) > 0 else [] + }) + + # Default to idle if no specific activity detected + return "idle", 0.6 + + def check_sop_compliance(self, activity, confidence, track): + """Check if activity complies with SOP""" + # Non-compliant activities + if activity in self.non_compliant_activities: + return False, f"Non-compliant activity: {activity}" + + # Check for proper SOP sequence + if len(track['activity_history']) > 1: + recent_activities = [act for act in list(track['activity_history'])[-5:] if 'activity' in act] + if len(recent_activities) > 1: + # Simple sequence validation + pass # More complex logic would go here + + # Compliant if it's an SOP activity or idle + if activity in self.sop_activities or activity == "idle": + return True, "Following SOP" + + # Default to compliant with low confidence activities + if confidence < 0.7: + return True, "Uncertain activity" + + return True, "Following SOP" + + def log_compliance(self, track_id, activity, compliant, reason, timestamp): + """Log compliance information""" + log_entry = { + 'timestamp': timestamp, + 'person_id': track_id, + 'activity': activity, + 'compliant': compliant, + 'reason': reason + } + self.compliance_log.append(log_entry) + + def analyze_video(self): + """Main analysis function""" + cap = cv2.VideoCapture(self.video_path) + + if not cap.isOpened(): + print("Error opening video file") + return + + # Get video properties for output video + frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + fps = int(cap.get(cv2.CAP_PROP_FPS)) + + # Initialize video writer + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + out = cv2.VideoWriter('output_video.mp4', fourcc, fps, (frame_width, frame_height)) + + frame_count = 0 + start_time = time.time() + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + frame_count += 1 + + # Process every 3rd frame for performance + if frame_count % 3 != 0: + # Write unprocessed frame to output video + #out.write(frame) + continue + + # Detect persons in frame + results = self.yolo_model(frame) + + # Extract person bounding boxes + persons = [] + for result in results: + persons.extend(self.extract_person_bboxes(result)) + + # Track persons across frames + tracked_persons = self.track_persons(persons, frame) + + # Analyze each tracked person + for track in tracked_persons: + bbox = track['bbox'] + x1, y1, x2, y2 = map(int, bbox) + + # Estimate pose + pose_results = self.estimate_pose(frame, bbox) + + # Classify activity + activity, confidence = self.classify_activity(pose_results, track) + + # Check SOP compliance + compliant, reason = self.check_sop_compliance(activity, confidence, track) + + # Log compliance + self.log_compliance(track['id'], activity, compliant, reason, time.time()) + + # Draw bounding box + color = (0, 255, 0) if compliant else (0, 0, 255) + cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) + + # Add labels + label = f"ID:{track['id']} {activity}" + status = "Compliant" if compliant else "Non-compliant" + cv2.putText(frame, label, (x1, y1 - 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) + cv2.putText(frame, status, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) + + # Write processed frame to output video + out.write(frame) + + # Display frame + cv2.imshow('SOP Compliance Analyzer', frame) + + # Print progress + if frame_count % 30 == 0: + elapsed = time.time() - start_time + fps = frame_count / elapsed if elapsed > 0 else 0 + print(f"Frame: {frame_count}, FPS: {fps:.2f}, Persons tracked: {len(tracked_persons)}") + + if cv2.waitKey(1) & 0xFF == ord('q'): + break + + cap.release() + out.release() + cv2.destroyAllWindows() + + # Save compliance log + self.save_compliance_report() + + # Print summary + self.print_summary() + + print("Output video saved as 'output_video.mp4'") + + def save_compliance_report(self): + """Save compliance report to file""" + with open('compliance_report.json', 'w') as f: + json.dump(self.compliance_log, f, indent=2) + print("Compliance report saved to compliance_report.json") + + def print_summary(self): + """Print compliance summary""" + if not self.compliance_log: + print("No compliance data recorded") + return + + total_checks = len(self.compliance_log) + compliant_checks = sum(1 for log in self.compliance_log if log['compliant']) + non_compliant_checks = total_checks - compliant_checks + + print("\n=== SOP Compliance Summary ===") + print(f"Total activity checks: {total_checks}") + print(f"Compliant activities: {compliant_checks} ({compliant_checks/total_checks*100:.1f}%)") + print(f"Non-compliant activities: {non_compliant_checks} ({non_compliant_checks/total_checks*100:.1f}%)") + + # Count violations by type + violation_types = {} + for log in self.compliance_log: + if not log['compliant']: + activity = log['activity'] + violation_types[activity] = violation_types.get(activity, 0) + 1 + + if violation_types: + print("\nViolation types:") + for activity, count in sorted(violation_types.items(), key=lambda x: x[1], reverse=True): + print(f" {activity}: {count}") + +def main(): + # Initialize analyzer + analyzer = ActivityAnalyzer( + yolo_model_path='yolo11m-2_uniform.onnx', + pose_model_path='yolo11s-pose.pt', + video_path='Gayungsari_110825_2.mp4' + ) + + # Run analysis + print("Starting SOP compliance analysis...") + print("Press 'q' to quit the video display") + analyzer.analyze_video() + +if __name__ == "__main__": + main() diff --git a/compliance_report.json b/compliance_report.json new file mode 100644 index 0000000..02474f4 --- /dev/null +++ b/compliance_report.json @@ -0,0 +1,2543 @@ +[ + { + "timestamp": 1754898296.2718346, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898296.5895898, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898299.8185937, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898301.084203, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898304.4767087, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898304.7632413, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898305.887552, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898306.1487613, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898306.5167665, + "person_id": 2, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898309.0892913, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898309.3138995, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898310.2785845, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898310.5044377, + "person_id": 2, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898311.4984362, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898311.8714404, + "person_id": 2, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898312.0654407, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898313.0504444, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898313.255449, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898314.3110635, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898314.4630647, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898315.2988968, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898315.4908946, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898316.3389018, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898316.4619071, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898317.3417668, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898317.482771, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898318.274274, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898318.4683363, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898319.292696, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898319.489698, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898320.3705, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898320.5745034, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898321.4862802, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898321.7235963, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898322.4325376, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898322.6307938, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898323.3904588, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898323.5626512, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898324.2976558, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898324.4686556, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898325.2107277, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898325.3867292, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898326.1038518, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898326.2688525, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898326.978712, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898327.1427083, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898327.8537126, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898328.0237167, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898328.722851, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898328.8838542, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898329.610853, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898330.3782651, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898330.5392644, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898331.2597806, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898331.437159, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898332.232166, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898332.4121656, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898333.1057858, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898333.3239408, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898334.0058823, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898334.2125912, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898334.9125144, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898335.117427, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898335.8116212, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898336.0147572, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898336.756611, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898336.9469993, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898337.6207914, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898337.8087904, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898338.5227966, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898338.7107973, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898339.3858478, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898339.6178486, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898340.313946, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898340.9950032, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898341.183001, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898341.863545, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898342.0705457, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898342.7645516, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898343.4460297, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898343.6511548, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898344.32995, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898344.5291488, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898345.1996713, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898345.446145, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898346.1232848, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898346.3378787, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898347.0004416, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898347.1755984, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898347.8396857, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898348.5012949, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898348.697299, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898349.4229796, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898349.562348, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898350.222162, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898350.3606656, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898350.514684, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898351.168075, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898351.2986467, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898351.4926481, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898352.1637568, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898352.2877557, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898352.4507594, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898353.123765, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898353.2478306, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898353.423704, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898354.0650682, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898354.1933587, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898354.883247, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898355.0272565, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898355.2409143, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898355.8981464, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898356.0261729, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898356.2255135, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898356.873392, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898357.004387, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898357.1569924, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898357.8166566, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898357.9626582, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898358.1076593, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898358.7597132, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898358.911718, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898359.052767, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898359.677611, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898359.83761, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898359.974681, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898360.655611, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898360.7726455, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898360.9489198, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898361.6249356, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898361.754671, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898361.93871, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898362.5981236, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898362.7261314, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898362.9222457, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898363.6053681, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898363.7373679, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898364.0073671, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898364.1473682, + "person_id": 4, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898364.840373, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898364.9743757, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898365.2183766, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898365.3643777, + "person_id": 4, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898366.0213814, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898366.1602015, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898366.3282065, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898366.4392035, + "person_id": 5, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898367.1082876, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898367.2382853, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898367.4042907, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898368.0918555, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898368.2348561, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898368.4038596, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898369.0784152, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898369.2234175, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898369.390836, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898370.0455327, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898370.1652682, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898370.3257651, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898371.0041037, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898371.1130354, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898371.2839243, + "person_id": 1, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898371.9499156, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898372.0804656, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898372.7257333, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898372.8568263, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898373.5186749, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898373.647679, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898374.2950635, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898374.4090652, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898375.0917478, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898375.1937485, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898375.853183, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898375.9551818, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898376.6217704, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898376.7307713, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898377.3847082, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898377.4876065, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898378.1330407, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898378.2377965, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898378.9092774, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898379.0130637, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898379.6754808, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898379.7774858, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898380.429674, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898380.5346737, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898380.7146719, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898381.3843937, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898381.4923968, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898381.6653957, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898382.3594918, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898382.5234926, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898383.1930656, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898383.3503187, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898384.0073254, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898384.161324, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898384.2653286, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898384.9473321, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898385.097331, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898385.1953351, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898385.908605, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898386.061756, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898386.1905854, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898386.8663325, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898386.978062, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898387.1714604, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898387.8486347, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898387.9613004, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898388.1086392, + "person_id": 6, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898388.2358065, + "person_id": 7, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898388.3630326, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898389.1801176, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898389.3169928, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898389.439994, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898389.5479946, + "person_id": 7, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898390.3789997, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898390.5090024, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898390.6380033, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898391.3527246, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898391.5226688, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898391.6562643, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898392.3212516, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898392.449153, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898392.560139, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898393.2613368, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898393.405306, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898393.5403063, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898394.3163133, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898394.487314, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898394.626315, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898395.4105985, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898395.5655072, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898395.6831903, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898396.3621118, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898396.4874258, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898396.5948565, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898396.6860757, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898397.3631399, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898397.4681413, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898397.560143, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898397.70514, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898398.4107733, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898398.5187757, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898399.3045502, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898399.4317744, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898399.5465176, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898400.3668027, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898400.5044186, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898400.6407578, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898400.8485076, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898401.984856, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898402.110858, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898402.2538571, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898403.049611, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898403.1676111, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898403.266798, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898403.3858001, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898404.2500587, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898404.3880606, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898404.6180615, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898405.5667305, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898405.7247326, + "person_id": 0, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898405.886307, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898406.637681, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898406.75968, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898406.9032662, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898406.9872708, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898407.679776, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898407.8037744, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898407.9359717, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898408.070972, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898408.9583797, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898409.0823832, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898409.290384, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898409.4503846, + "person_id": 0, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898410.4561276, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898410.602127, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898410.7921307, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898411.7535815, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898411.9522138, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898412.0518017, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898413.3243704, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898413.5653753, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898413.8200305, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898415.4130297, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898415.527032, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898415.7160332, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898416.5490384, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898416.680038, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898416.81004, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898417.6344595, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898417.7619255, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898417.888611, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898419.3681676, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898419.6831691, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898421.300568, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898421.6385694, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898422.0062747, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898423.6405685, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898423.8848364, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898424.0898275, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898425.2857337, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898425.4745314, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898425.600107, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898426.4205105, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898426.5580494, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898426.6808035, + "person_id": 10, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898426.831371, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898427.576708, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898427.708845, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898428.5101626, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898428.6281748, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898428.7399077, + "person_id": 10, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898429.5450804, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898429.6780822, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898430.5320888, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898430.7120986, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898431.747954, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898431.937983, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898432.906106, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898433.038059, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898434.101582, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898434.306584, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898435.4353583, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898435.6523583, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898436.6801982, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898436.8435292, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898437.5554912, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898437.665898, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898437.7805781, + "person_id": 10, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898437.9145916, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898438.618162, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898438.7447982, + "person_id": 10, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898439.472504, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898439.6125033, + "person_id": 10, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898440.3735127, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898440.5135143, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898440.651514, + "person_id": 10, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898440.7815151, + "person_id": 9, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898441.4882765, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898441.7042766, + "person_id": 8, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898441.8342755, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898442.590283, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898442.703285, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898442.8292847, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898443.5632238, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898443.683225, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898443.7952266, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898444.5364027, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898444.6444063, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898444.7714038, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898445.5138094, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898445.63681, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898445.7568119, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898446.504912, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898446.627009, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898446.78267, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898447.5629444, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898447.685942, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898447.8139477, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898448.6130095, + "person_id": 3, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + }, + { + "timestamp": 1754898448.7350075, + "person_id": 8, + "activity": "using_mobile_phone", + "compliant": false, + "reason": "Non-compliant activity: using_mobile_phone" + }, + { + "timestamp": 1754898448.8820925, + "person_id": 9, + "activity": "idle", + "compliant": false, + "reason": "Non-compliant activity: idle" + } +] \ No newline at end of file diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..c91a433 --- /dev/null +++ b/demo.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Demo script to run the SOP Compliance Analyzer +""" + +import subprocess +import sys +import os + +def main(): + print("SOP Compliance Analyzer Demo") + print("============================") + print() + print("This demo will run the activity analyzer on the sample video.") + print("The analyzer will:") + print("1. Detect people in the video") + print("2. Track them across frames") + print("3. Estimate their poses") + print("4. Classify their activities") + print("5. Check for SOP compliance") + print("6. Generate a compliance report") + print("7. Save annotated video with labels to MP4 format") + print() + print("Press 'q' at any time to quit the video display.") + print() + input("Press Enter to start the analysis...") + + # Run the activity analyzer + try: + result = subprocess.run([sys.executable, "activity_analyzer.py"], + capture_output=True, text=True, timeout=300) + + print("\nAnalysis completed!") + print(f"Return code: {result.returncode}") + + if result.stdout: + print("\nOutput:") + print(result.stdout) + + if result.stderr: + print("\nErrors:") + print(result.stderr) + + except subprocess.TimeoutExpired: + print("Analysis timed out after 5 minutes") + except Exception as e: + print(f"Error running analysis: {e}") + + # Check if report was generated + if os.path.exists("compliance_report.json"): + print("\nCompliance report generated successfully!") + # Get file size + size = os.path.getsize("compliance_report.json") + print(f"Report size: {size} bytes") + else: + print("\nNo compliance report found.") + + print("\nDemo completed.") + +if __name__ == "__main__": + main() diff --git a/kinetics_classnames.json b/kinetics_classnames.json new file mode 100644 index 0000000..1d41181 --- /dev/null +++ b/kinetics_classnames.json @@ -0,0 +1 @@ +{"\"sharpening knives\"": 290, "\"eating ice cream\"": 115, "\"cutting nails\"": 81, "\"changing wheel\"": 53, "\"bench pressing\"": 19, "deadlifting": 88, "\"eating carrots\"": 111, "marching": 192, "\"throwing discus\"": 358, "\"playing flute\"": 231, "\"cooking on campfire\"": 72, "\"breading or breadcrumbing\"": 33, "\"playing badminton\"": 218, "\"ripping paper\"": 276, "\"playing saxophone\"": 244, "\"milking cow\"": 197, "\"juggling balls\"": 169, "\"flying kite\"": 130, "capoeira": 43, "\"making jewelry\"": 187, "drinking": 100, "\"playing cymbals\"": 228, "\"cleaning gutters\"": 61, "\"hurling (sport)\"": 161, "\"playing organ\"": 239, "\"tossing coin\"": 361, "wrestling": 395, "\"driving car\"": 103, "headbutting": 150, "\"gymnastics tumbling\"": 147, "\"making bed\"": 186, "abseiling": 0, "\"holding snake\"": 155, "\"rock climbing\"": 278, "\"cooking egg\"": 71, "\"long jump\"": 182, "\"bee keeping\"": 17, "\"trimming or shaving beard\"": 365, "\"cleaning shoes\"": 63, "\"dancing gangnam style\"": 86, "\"catching or throwing softball\"": 50, "\"ice skating\"": 164, "jogging": 168, "\"eating spaghetti\"": 116, "bobsledding": 28, "\"assembling computer\"": 8, "\"playing cricket\"": 227, "\"playing monopoly\"": 238, "\"golf putting\"": 143, "\"making pizza\"": 188, "\"javelin throw\"": 166, "\"peeling potatoes\"": 211, "clapping": 57, "\"brushing hair\"": 36, "\"flipping pancake\"": 129, "\"drinking beer\"": 101, "\"dribbling basketball\"": 99, "\"playing bagpipes\"": 219, "somersaulting": 325, "\"canoeing or kayaking\"": 42, "\"riding unicycle\"": 275, "texting": 355, "\"tasting beer\"": 352, "\"hockey stop\"": 154, "\"playing clarinet\"": 225, "\"waxing legs\"": 389, "\"curling hair\"": 80, "\"running on treadmill\"": 281, "\"tai chi\"": 346, "\"driving tractor\"": 104, "\"shaving legs\"": 293, "\"sharpening pencil\"": 291, "\"making sushi\"": 190, "\"spray painting\"": 327, "situp": 305, "\"playing kickball\"": 237, "\"sticking tongue out\"": 331, "headbanging": 149, "\"folding napkins\"": 132, "\"playing piano\"": 241, "skydiving": 312, "\"dancing charleston\"": 85, "\"ice fishing\"": 163, "tickling": 359, "bandaging": 13, "\"high jump\"": 151, "\"making a sandwich\"": 185, "\"riding mountain bike\"": 271, "\"cutting pineapple\"": 82, "\"feeding goats\"": 125, "\"dancing macarena\"": 87, "\"playing basketball\"": 220, "krumping": 179, "\"high kick\"": 152, "\"balloon blowing\"": 12, "\"playing accordion\"": 217, "\"playing chess\"": 224, "\"hula hooping\"": 159, "\"pushing wheelchair\"": 263, "\"riding camel\"": 268, "\"blowing out candles\"": 27, "\"extinguishing fire\"": 121, "\"using computer\"": 373, "\"jumpstyle dancing\"": 173, "yawning": 397, "writing": 396, "\"jumping into pool\"": 172, "\"doing laundry\"": 96, "\"egg hunting\"": 118, "\"sanding floor\"": 284, "\"moving furniture\"": 200, "\"exercising arm\"": 119, "\"sword fighting\"": 345, "\"sign language interpreting\"": 303, "\"counting money\"": 74, "bartending": 15, "\"cleaning windows\"": 65, "\"blasting sand\"": 23, "\"petting cat\"": 213, "sniffing": 320, "bowling": 31, "\"playing poker\"": 242, "\"taking a shower\"": 347, "\"washing hands\"": 382, "\"water sliding\"": 384, "\"presenting weather forecast\"": 254, "tobogganing": 360, "celebrating": 51, "\"getting a haircut\"": 138, "snorkeling": 321, "\"weaving basket\"": 390, "\"playing squash or racquetball\"": 245, "parasailing": 206, "\"news anchoring\"": 202, "\"belly dancing\"": 18, "windsurfing": 393, "\"braiding hair\"": 32, "\"crossing river\"": 78, "\"laying bricks\"": 181, "\"roller skating\"": 280, "hopscotch": 156, "\"playing trumpet\"": 248, "\"dying hair\"": 108, "\"trimming trees\"": 366, "\"pumping fist\"": 256, "\"playing keyboard\"": 236, "snowboarding": 322, "\"garbage collecting\"": 136, "\"playing controller\"": 226, "dodgeball": 94, "\"recording music\"": 266, "\"country line dancing\"": 75, "\"dancing ballet\"": 84, "gargling": 137, "ironing": 165, "\"push up\"": 260, "\"frying vegetables\"": 135, "\"ski jumping\"": 307, "\"mowing lawn\"": 201, "\"getting a tattoo\"": 139, "\"rock scissors paper\"": 279, "cheerleading": 55, "\"using remote controller (not gaming)\"": 374, "\"shaking head\"": 289, "sailing": 282, "\"training dog\"": 363, "hurdling": 160, "\"fixing hair\"": 128, "\"climbing ladder\"": 67, "\"filling eyebrows\"": 126, "\"springboard diving\"": 329, "\"eating watermelon\"": 117, "\"drumming fingers\"": 106, "\"waxing back\"": 386, "\"playing didgeridoo\"": 229, "\"swimming backstroke\"": 339, "\"biking through snow\"": 22, "\"washing feet\"": 380, "\"mopping floor\"": 198, "\"throwing ball\"": 357, "\"eating doughnuts\"": 113, "\"drinking shots\"": 102, "\"tying bow tie\"": 368, "dining": 91, "\"surfing water\"": 337, "\"sweeping floor\"": 338, "\"grooming dog\"": 145, "\"catching fish\"": 47, "\"pumping gas\"": 257, "\"riding or walking with horse\"": 273, "\"massaging person's head\"": 196, "archery": 5, "\"ice climbing\"": 162, "\"playing recorder\"": 243, "\"decorating the christmas tree\"": 89, "\"peeling apples\"": 210, "snowmobiling": 324, "\"playing ukulele\"": 249, "\"eating burger\"": 109, "\"building cabinet\"": 38, "\"stomping grapes\"": 332, "\"drop kicking\"": 105, "\"passing American football (not in game)\"": 209, "applauding": 3, "hugging": 158, "\"eating hotdog\"": 114, "\"pole vault\"": 253, "\"reading newspaper\"": 265, "\"snatch weight lifting\"": 318, "zumba": 399, "\"playing ice hockey\"": 235, "breakdancing": 34, "\"feeding fish\"": 124, "\"shredding paper\"": 300, "\"catching or throwing frisbee\"": 49, "\"exercising with an exercise ball\"": 120, "\"pushing cart\"": 262, "\"swimming butterfly stroke\"": 341, "\"riding scooter\"": 274, "spraying": 328, "\"folding paper\"": 133, "\"golf driving\"": 142, "\"robot dancing\"": 277, "\"bending back\"": 20, "testifying": 354, "\"waxing chest\"": 387, "\"carving pumpkin\"": 46, "\"hitting baseball\"": 153, "\"riding elephant\"": 269, "\"brushing teeth\"": 37, "\"pull ups\"": 255, "\"riding a bike\"": 267, "skateboarding": 306, "\"cleaning pool\"": 62, "\"playing paintball\"": 240, "\"massaging back\"": 193, "\"shoveling snow\"": 299, "\"surfing crowd\"": 336, "unboxing": 371, "faceplanting": 122, "trapezing": 364, "\"swinging legs\"": 343, "hoverboarding": 157, "\"playing violin\"": 250, "\"wrapping present\"": 394, "\"blowing nose\"": 26, "\"kicking field goal\"": 174, "\"picking fruit\"": 214, "\"swinging on something\"": 344, "\"giving or receiving award\"": 140, "\"planting trees\"": 215, "\"water skiing\"": 383, "\"washing dishes\"": 379, "\"punching bag\"": 258, "\"massaging legs\"": 195, "\"throwing axe\"": 356, "\"salsa dancing\"": 283, "bookbinding": 29, "\"tying tie\"": 370, "\"skiing crosscountry\"": 309, "\"shining shoes\"": 295, "\"making snowman\"": 189, "\"front raises\"": 134, "\"doing nails\"": 97, "\"massaging feet\"": 194, "\"playing drums\"": 230, "smoking": 316, "\"punching person (boxing)\"": 259, "cartwheeling": 45, "\"passing American football (in game)\"": 208, "\"shaking hands\"": 288, "plastering": 216, "\"watering plants\"": 385, "kissing": 176, "slapping": 314, "\"playing harmonica\"": 233, "welding": 391, "\"smoking hookah\"": 317, "\"scrambling eggs\"": 285, "\"cooking chicken\"": 70, "\"pushing car\"": 261, "\"opening bottle\"": 203, "\"cooking sausages\"": 73, "\"catching or throwing baseball\"": 48, "\"swimming breast stroke\"": 340, "digging": 90, "\"playing xylophone\"": 252, "\"doing aerobics\"": 95, "\"playing trombone\"": 247, "knitting": 178, "\"waiting in line\"": 377, "\"tossing salad\"": 362, "squat": 330, "vault": 376, "\"using segway\"": 375, "\"crawling baby\"": 77, "\"reading book\"": 264, "motorcycling": 199, "barbequing": 14, "\"cleaning floor\"": 60, "\"playing cello\"": 223, "drawing": 98, "auctioning": 9, "\"carrying baby\"": 44, "\"diving cliff\"": 93, "busking": 41, "\"cutting watermelon\"": 83, "\"scuba diving\"": 286, "\"riding mechanical bull\"": 270, "\"making tea\"": 191, "\"playing tennis\"": 246, "crying": 79, "\"dunking basketball\"": 107, "\"cracking neck\"": 76, "\"arranging flowers\"": 7, "\"building shed\"": 39, "\"golf chipping\"": 141, "\"tasting food\"": 353, "\"shaving head\"": 292, "\"answering questions\"": 2, "\"climbing tree\"": 68, "\"skipping rope\"": 311, "kitesurfing": 177, "\"juggling fire\"": 170, "laughing": 180, "paragliding": 205, "\"contact juggling\"": 69, "slacklining": 313, "\"arm wrestling\"": 6, "\"making a cake\"": 184, "\"finger snapping\"": 127, "\"grooming horse\"": 146, "\"opening present\"": 204, "\"tapping pen\"": 351, "singing": 304, "\"shot put\"": 298, "\"cleaning toilet\"": 64, "\"spinning poi\"": 326, "\"setting table\"": 287, "\"tying knot (not on a tie)\"": 369, "\"blowing glass\"": 24, "\"eating chips\"": 112, "\"tap dancing\"": 349, "\"climbing a rope\"": 66, "\"brush painting\"": 35, "\"chopping wood\"": 56, "\"stretching leg\"": 334, "\"petting animal (not cat)\"": 212, "\"baking cookies\"": 11, "\"stretching arm\"": 333, "beatboxing": 16, "jetskiing": 167, "\"bending metal\"": 21, "sneezing": 319, "\"folding clothes\"": 131, "\"sled dog racing\"": 315, "\"tapping guitar\"": 350, "\"bouncing on trampoline\"": 30, "\"waxing eyebrows\"": 388, "\"air drumming\"": 1, "\"kicking soccer ball\"": 175, "\"washing hair\"": 381, "\"riding mule\"": 272, "\"blowing leaves\"": 25, "\"strumming guitar\"": 335, "\"playing cards\"": 222, "snowkiting": 323, "\"playing bass guitar\"": 221, "\"applying cream\"": 4, "\"shooting basketball\"": 296, "\"walking the dog\"": 378, "\"triple jump\"": 367, "\"shearing sheep\"": 294, "\"clay pottery making\"": 58, "\"bungee jumping\"": 40, "\"unloading truck\"": 372, "\"shuffling cards\"": 301, "\"shooting goal (soccer)\"": 297, "\"tango dancing\"": 348, "\"side kick\"": 302, "\"grinding meat\"": 144, "yoga": 398, "\"hammer throw\"": 148, "\"changing oil\"": 52, "\"checking tires\"": 54, "parkour": 207, "\"eating cake\"": 110, "\"skiing slalom\"": 310, "\"juggling soccer ball\"": 171, "whistling": 392, "\"feeding birds\"": 123, "\"playing volleyball\"": 251, "\"swing dancing\"": 342, "\"skiing (not slalom or crosscountry)\"": 308, "lunge": 183, "\"disc golfing\"": 92, "\"clean and jerk\"": 59, "\"playing guitar\"": 232, "\"baby waking up\"": 10, "\"playing harp\"": 234} \ No newline at end of file diff --git a/main2.py b/main2.py index 904f8fc..d68fda6 100644 --- a/main2.py +++ b/main2.py @@ -22,7 +22,7 @@ while ret: if ret: results = model.predict(frame, conf=0.5) frame_ = results[0].plot() - pose_results = model_pose.predict(frame_, show_boxes=False, show_conf=False, show_labels=False) + pose_results = model_pose.predict(frame_, show_boxes=False, show_conf=True, show_labels=True) results[0].keypoints = pose_results[0].keypoints frame_ = results[0].plot() diff --git a/main3.py b/main3.py index 69f639b..d4c761f 100644 --- a/main3.py +++ b/main3.py @@ -1,50 +1,125 @@ -from ultralytics import YOLO import cv2 -import os +import onnxruntime as ort +import torch +import ultralytics +from ultralytics import YOLO import numpy as np +import ultralytics +from torchvision import transforms +import matplotlib.pyplot as plt +import matplotlib.image as mpimg -# Load the object detection model -det_model = YOLO("yolo11m-2_uniform.onnx") # General object detection model +yolo_model_path = 'yolo11m-2_uniform.onnx' +ort_session = ort.InferenceSession(yolo_model_path) -# Load the pose estimation model -pose_model = YOLO("yolo11s-pose.pt") # Pose estimation model +pose_model_path = 'yolo11s-pose.pt' +pose_model = YOLO(pose_model_path) -video_path = "PF-071124-2.mp4" +def extract_person_boxes(yolo_outputs): + """Extract person bounding boxes from YOLO ONNX output""" + boxes = yolo_outputs[0] + conf_threshold = 0.5 + + # Filter based on confidence score only + selected_indices = np.where( + boxes[:, 4] > conf_threshold + )[0] + + return boxes[selected_indices, :4].astype(int) + +def evaluate_sop(pose_output): + """Basic SOP compliance evaluation (example implementation): + - Checks if both arms are visible (keypoint confidence > 0.6) + - Checks torso vertical angle (placeholder logic)""" + if len(pose_output) == 0 or pose_output[0].keypoints.shape[0] == 0: + return False # No keypoints detected, assume non-compliant + + keypoints = pose_output[0].keypoints + + # Example conditions + left_shoulder_conf = keypoints[5, 2] if keypoints.shape[0] > 5 else 0 + right_shoulder_conf = keypoints[6, 2] if keypoints.shape[0] > 6 else 0 + left_elbow_conf = keypoints[7, 2] if keypoints.shape[0] > 7 else 0 + right_elbow_conf = keypoints[8, 2] if keypoints.shape[0] > 8 else 0 + + # Simple compliance criteria + arms_visible = ( + left_shoulder_conf > 0.6 and + right_shoulder_conf > 0.6 and + left_elbow_conf > 0.6 and + right_elbow_conf > 0.6 + ) + + # Add more conditions based on actual SOP requirements + return arms_visible # Temporary compliance criteria + +transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) + +video_path = 'PF-071124-2.mp4' cap = cv2.VideoCapture(video_path) -ret = True -frame_count = 0 # Initialize a frame counter -save_dir = "/results/" # Directory to save the crops -os.makedirs(save_dir, exist_ok=True) # Create the directory if it doesn't exist +if not cap.isOpened(): + print("Error opening video file") + exit() -while ret: +while cap.isOpened(): ret, frame = cap.read() - frame_count += 1 # Increment the frame counter - frame2_, frame_ = np.empty(2) - if ret: - # Run object detection - det_results = det_model.predict(frame, conf=0.5) + if not ret: + break - # Filter detections for persons (class 0) - person_detections = [det for det in det_results if det.names[0] == 'crew'] + input_frame = cv2.resize(frame, (640, 640)) + input_frame = np.transpose(input_frame, (2, 0, 1)).astype(np.float32) / 255.0 + input_tensor = np.expand_dims(input_frame, 0) - #for i, det in enumerate(person_detections[0].boxes.xyxy): - # Extract bounding box coordinates - # x1, y1, x2, y2 = map(int, det[:4]) - # crop = frame[y1:y2, x1:x2] - - # Run pose estimation on detected persons - for i, person in person_detections: - #how to return the tensor to posemodel??? - x1, y1, x2, y2 = map(int, person[i].boxes.xyxy[:4]) - person_image = frame[y1:y2, x1:x2] # Crop the person from the image - - pose_results = pose_model(person_image) - frame_ = pose_results[0].plot() - cv2.imshow("frame", frame_) - - if cv2.waitKey(25) & 0xFF == ord('q'): - break + yolo_outputs = ort_session.run(None, {'images': input_tensor}) + print("YOLO Outputs Shape:", [output.shape for output in yolo_outputs]) + print("YOLO Outputs:", yolo_outputs) + + persons = extract_person_boxes(yolo_outputs) + + for bbox in persons: + x1, y1, x2, y2 = map(lambda arr: arr[0], bbox) + #x1, y1, x2, y2 = map(int, bbox) + roi = frame[y1:y2, x1:x2] + + roi_resized = cv2.resize(roi, (256, 256)) + roi_tensor = transform(roi_resized).unsqueeze(0) + + with torch.no_grad(): + pose_output = pose_model(roi_tensor) + + print("Pose Output Type:", type(pose_output)) + print("Pose Output Keys:", pose_output.keys()) if hasattr(pose_output, 'keys') else print("Pose Output:", pose_output) + + if pose_output[0].keypoints.shape[0] > 0: + keypoints = pose_output[0].keypoints + if keypoints.shape[1] > 5: # Ensure there are at least 6 keypoints + compliant = evaluate_sop(pose_output) + else: + compliant = False # Not enough keypoints detected, assume non-compliant + else: + compliant = False # No keypoints detected, assume non-compliant + + color = (0, 255, 0) if compliant else (0, 0, 255) + cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) + label = 'Compliant' if compliant else 'Non-compliant' + cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2) + + # Display the frame using matplotlib + plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) + plt.axis('off') # Turn off axis labels + plt.show(block=False) + plt.pause(0.01) + plt.clf() # Clear the current figure + + if cv2.waitKey(25) & 0xFF == ord('q'): + break + #if cv2.waitKey(1) & 0xFF == ord('q'): + # break cap.release() -cv2.destroyAllWindows() \ No newline at end of file + +cv2.destroyAllWindows() diff --git a/main4.py b/main4.py new file mode 100644 index 0000000..004d458 --- /dev/null +++ b/main4.py @@ -0,0 +1,70 @@ +import cv2 +import onnxruntime as ort +import torch +import numpy as np +from torchvision import transforms + +# Load YOLO model for person detection +yolo_model_path = 'yolo11m-2_uniform.onnx' +ort_session = ort.InferenceSession(yolo_model_path) + +# Load YOLO pose estimation model +pose_model_path = 'yolo11s-pose.pt' +pose_model = torch.load(pose_model_path) +pose_model.eval() + +transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) + +video_path = 'PF-071124-2.mp4' +cap = cv2.VideoCapture(video_path) + +if not cap.isOpened(): + print("Error opening video file") + exit() + +def get_person_bboxes(outputs): + # Implement YOLO output parsing here + return [] + +def check_sop_compliance(pose_output): + # Implement your SOP compliance logic here + return True + +while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + input_frame = cv2.resize(frame, (640, 640)) + input_frame = np.transpose(input_frame, (2, 0, 1)).astype(np.float32) / 255.0 + input_tensor = np.expand_dims(input_frame, 0) + + yolo_outputs = ort_session.run(None, {'images': input_tensor}) + persons = get_person_bboxes(yolo_outputs) + + for bbox in persons: + x1, y1, x2, y2 = map(int, bbox) + roi = frame[y1:y2, x1:x2] + + roi_resized = cv2.resize(roi, (256, 256)) + roi_tensor = transform(roi_resized).unsqueeze(0) + + with torch.no_grad(): + pose_output = pose_model(roi_tensor) + + compliant = check_sop_compliance(pose_output) + + color = (0, 255, 0) if compliant else (0, 0, 255) + cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) + label = 'Compliant' if compliant else 'Non-compliant' + cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2) + + cv2.imshow('SOP Compliance', frame) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + +cap.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5277c08 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +opencv-python>=4.5.0 +onnxruntime>=1.10.0 +torch>=1.10.0 +torchvision>=0.11.0 +ultralytics>=8.0.0 +numpy>=1.21.0 +# For MobileNet3D support +pytorchvideo>=0.1.5 +fvcore>=0.1.5 diff --git a/sequence.txt b/sequence.txt new file mode 100644 index 0000000..d62f6a0 --- /dev/null +++ b/sequence.txt @@ -0,0 +1,4 @@ +1. speaking with customer for taking order +2. input the order +3. give the ordered item to the customer +4. table cleaning when the customer is done and leaving the table \ No newline at end of file diff --git a/yolo11m-2_uniform.onnx b/yolo11m-2_uniform.onnx new file mode 100644 index 0000000..ccbe30f Binary files /dev/null and b/yolo11m-2_uniform.onnx differ