#!/usr/bin/env python """ System Validation Script for Traffic Monitoring Application This script performs a comprehensive check of the system components, dependencies, and configuration to ensure the application is properly set up. It validates: 1. Required Python packages 2. Model files existence and format 3. Configuration file correctness 4. UI components 5. Controller functionality 6. Hardware compatibility (GPU/OpenVINO) 7. Camera accessibility Usage: python validate_system.py [--fix] [--verbose] Options: --fix Attempt to fix common issues (install packages, download models) --verbose Show detailed output for all checks """ import os import sys import json import platform import importlib import subprocess import argparse import traceback from pathlib import Path from typing import Dict, List, Tuple, Any, Optional # Add parent directory to path to ensure imports work sys.path.append(os.path.dirname(os.path.abspath(__file__))) # Colors for terminal output class Colors: HEADER = '\033[95m' BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' END = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' # Define required packages with minimum versions REQUIRED_PACKAGES = { 'PySide6': '6.0.0', 'opencv-python': '4.5.0', 'numpy': '1.20.0', 'openvino': '2021.4.0', 'PyYAML': '5.4.0', 'Pillow': '8.0.0', 'matplotlib': '3.3.0', 'pandas': '1.2.0', 'torch': '1.8.0', 'torchvision': '0.9.0', } # Define required model files REQUIRED_MODELS = [ 'mobilenetv2.bin', 'mobilenetv2.xml', 'yolo11n.bin', 'yolo11n.xml', 'yolo11x.bin', 'yolo11x.xml', ] def print_header(message: str) -> None: """Print a formatted header message""" print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*80}{Colors.END}") print(f"{Colors.HEADER}{Colors.BOLD} {message} {Colors.END}") print(f"{Colors.HEADER}{Colors.BOLD}{'='*80}{Colors.END}\n") def print_result(test_name: str, status: bool, message: str = "") -> None: """Print a test result with appropriate formatting""" status_text = f"{Colors.GREEN}✓ PASS{Colors.END}" if status else f"{Colors.RED}✗ FAIL{Colors.END}" print(f"{test_name:<40} {status_text:<15} {message}") def get_package_version(package_name: str) -> Optional[str]: """Get installed version of a package""" # Try using importlib.metadata first (Python 3.8+) try: # Try importlib.metadata (Python 3.8+) try: import importlib.metadata return importlib.metadata.version(package_name) except (ImportError, AttributeError): # Fallback for Python < 3.8 try: import pkg_resources return pkg_resources.get_distribution(package_name).version except ImportError: # pkg_resources not available pass except Exception as e: # Other pkg_resources error pass # Distribution not found or other pkg_resources error pass except Exception: # Continue with other methods if any exception occurs pass # Try to import the package and check __version__ try: pkg = importlib.import_module(package_name) if hasattr(pkg, "__version__"): return pkg.__version__ except (ImportError, AttributeError): pass # Try pip list as a fallback for getting version info try: result = subprocess.run( [sys.executable, "-m", "pip", "show", package_name], capture_output=True, text=True ) if result.returncode == 0: for line in result.stdout.split('\n'): if line.lower().startswith('version:'): return line.split(':', 1)[1].strip() except Exception: pass # If we got here, we couldn't determine the version return None def compare_versions(current: str, required: str) -> bool: """Compare two version strings""" if current is None: return False # Simple version comparison for now - can be enhanced with packaging.version current_parts = [int(x) for x in current.split('.')] required_parts = [int(x) for x in required.split('.')] # Pad with zeros to ensure equal length while len(current_parts) < len(required_parts): current_parts.append(0) while len(required_parts) < len(current_parts): required_parts.append(0) # Compare each part for c, r in zip(current_parts, required_parts): if c > r: return True if c < r: return False # Equal versions return True def check_packages(fix: bool = False, verbose: bool = False) -> Tuple[bool, List[str]]: """Check if all required packages are installed with correct versions""" print_header("Checking Required Packages") all_passed = True missing_packages = [] for package, min_version in REQUIRED_PACKAGES.items(): current_version = get_package_version(package) if current_version is None: print_result(package, False, "Not installed") all_passed = False missing_packages.append(package) elif not compare_versions(current_version, min_version): print_result(package, False, f"Version {current_version} < required {min_version}") all_passed = False missing_packages.append(f"{package}=={min_version}") else: print_result(package, True, f"Version {current_version}") # Try to fix missing packages if requested if fix and missing_packages: print(f"\n{Colors.YELLOW}Attempting to install missing packages...{Colors.END}") try: cmd = [sys.executable, "-m", "pip", "install"] + missing_packages if verbose: print(f"Running: {' '.join(cmd)}") subprocess.check_call(cmd) print(f"{Colors.GREEN}Package installation complete.{Colors.END}") # Re-check packages after installation return check_packages(fix=False, verbose=verbose) except subprocess.CalledProcessError: print(f"{Colors.RED}Failed to install packages!{Colors.END}") return all_passed, missing_packages def check_models(base_dir: Path, fix: bool = False, verbose: bool = False) -> Tuple[bool, List[str]]: """Check if all required model files exist""" print_header("Checking Model Files") all_passed = True missing_models = [] # Check for models in different possible locations search_dirs = [ base_dir, base_dir / "openvino_models", base_dir / "models", base_dir.parent / "openvino_models", base_dir.parent / "models" ] # Add specific model subdirectories that we know about additional_dirs = [] for directory in search_dirs: if directory.exists(): # Check for yolo11x_openvino_model subdirectory yolo11x_dir = directory / "yolo11x_openvino_model" if yolo11x_dir.exists(): additional_dirs.append(yolo11x_dir) # Check for yolo11n_openvino_model subdirectory yolo11n_dir = directory / "yolo11n_openvino_model" if yolo11n_dir.exists(): additional_dirs.append(yolo11n_dir) # Add all direct subdirectories of models directory if directory.name == "models" and directory.exists(): for subdir in directory.iterdir(): if subdir.is_dir(): additional_dirs.append(subdir) # Add the additional directories to our search paths search_dirs.extend(additional_dirs) for model_file in REQUIRED_MODELS: found = False found_path = None for directory in search_dirs: if not directory.exists(): continue model_path = directory / model_file if model_path.exists(): found = True found_path = model_path print_result(model_file, True, f"Found in {directory}") break if not found: print_result(model_file, False, "Not found") all_passed = False missing_models.append(model_file) elif verbose: print(f" Full path: {found_path}") # TODO: Implement model download functionality if fix=True if fix and missing_models: print(f"\n{Colors.YELLOW}Automatic model download not implemented yet.{Colors.END}") print(f"{Colors.YELLOW}Please download missing models manually.{Colors.END}") return all_passed, missing_models def check_config(base_dir: Path, fix: bool = False, verbose: bool = False) -> Tuple[bool, List[str]]: """Check if configuration files exist and are valid""" print_header("Checking Configuration Files") all_passed = True issues = [] # Check main config.json config_path = base_dir / "config.json" if not config_path.exists(): print_result("config.json", False, "Not found") all_passed = False issues.append("Missing config.json") # Create default config if fix is enabled if fix: print(f"{Colors.YELLOW}Creating default config.json...{Colors.END}") default_config = { "video_sources": { "default_camera_id": 0, "default_video": "" }, "detection_models": { "yolo_model_xml": "openvino_models/yolo11n.xml", "yolo_model_bin": "openvino_models/yolo11n.bin" }, "detection_settings": { "confidence_threshold": 0.5, "use_gpu": True }, "ui_settings": { "theme": "dark", "show_fps": True, "default_tab": 0 } } with open(config_path, 'w') as f: json.dump(default_config, f, indent=4) print(f"{Colors.GREEN}Created default config.json{Colors.END}") else: # Validate config.json try: with open(config_path, 'r') as f: config = json.load(f) # Check for required sections required_sections = ["video_sources", "detection_models", "detection_settings"] missing_sections = [s for s in required_sections if s not in config] if missing_sections: print_result("config.json structure", False, f"Missing sections: {', '.join(missing_sections)}") all_passed = False issues.append(f"Config missing sections: {', '.join(missing_sections)}") # Fix config if requested if fix: print(f"{Colors.YELLOW}Adding missing sections to config.json...{Colors.END}") for section in missing_sections: if section == "video_sources": config["video_sources"] = {"default_camera_id": 0, "default_video": ""} elif section == "detection_models": config["detection_models"] = { "yolo_model_xml": "openvino_models/yolo11n.xml", "yolo_model_bin": "openvino_models/yolo11n.bin" } elif section == "detection_settings": config["detection_settings"] = {"confidence_threshold": 0.5, "use_gpu": True} with open(config_path, 'w') as f: json.dump(config, f, indent=4) print(f"{Colors.GREEN}Updated config.json with missing sections{Colors.END}") else: print_result("config.json structure", True, "All required sections present") # Check model paths in config model_xml = config.get("detection_models", {}).get("yolo_model_xml", "") model_bin = config.get("detection_models", {}).get("yolo_model_bin", "") if not (base_dir / model_xml).exists() and model_xml: print_result("Model path in config", False, f"Model XML not found at {model_xml}") all_passed = False issues.append(f"Invalid model path: {model_xml}") else: print_result("Model XML path", True, f"{model_xml}") if not (base_dir / model_bin).exists() and model_bin: print_result("Model path in config", False, f"Model BIN not found at {model_bin}") all_passed = False issues.append(f"Invalid model path: {model_bin}") else: print_result("Model BIN path", True, f"{model_bin}") except json.JSONDecodeError: print_result("config.json", False, "Invalid JSON format") all_passed = False issues.append("Invalid JSON in config.json") if fix: print(f"{Colors.YELLOW}Backing up and creating new config.json...{Colors.END}") # Backup invalid config os.rename(config_path, config_path.with_suffix('.json.bak')) # Create new default config default_config = { "video_sources": {"default_camera_id": 0, "default_video": ""}, "detection_models": { "yolo_model_xml": "openvino_models/yolo11n.xml", "yolo_model_bin": "openvino_models/yolo11n.bin" }, "detection_settings": {"confidence_threshold": 0.5, "use_gpu": True}, "ui_settings": {"theme": "dark", "show_fps": True, "default_tab": 0} } with open(config_path, 'w') as f: json.dump(default_config, f, indent=4) print(f"{Colors.GREEN}Created new default config.json{Colors.END}") # Check for camera configuration files in violations directory camera_config_dir = base_dir / "violations" / "checkpoints" if camera_config_dir.exists(): has_camera_configs = any(f.endswith('.yaml') for f in os.listdir(camera_config_dir)) print_result("Camera configurations", has_camera_configs, "Found camera config files" if has_camera_configs else "No camera config files found (not critical)") else: print_result("Camera configurations", True, "Camera config directory not found (not critical)") return all_passed, issues def check_ui_components(base_dir: Path, verbose: bool = False) -> Tuple[bool, List[str]]: """Check if all UI components exist and can be imported""" print_header("Checking UI Components") all_passed = True issues = [] # List of critical UI files to check ui_files = [ "ui/main_window.py", "ui/fixed_live_tab.py", "ui/analytics_tab.py", "ui/violations_tab.py", "ui/export_tab.py", "ui/config_panel.py" ] # Check file existence for ui_file in ui_files: file_path = base_dir / ui_file if file_path.exists(): print_result(ui_file, True, "File exists") else: print_result(ui_file, False, "File not found") all_passed = False issues.append(f"Missing UI file: {ui_file}") # Try importing main window if verbose: print("\nAttempting to import main window class...") try: sys.path.insert(0, str(base_dir)) from ui.main_window import MainWindow print_result("MainWindow import", True, "Import successful") except ImportError as e: print_result("MainWindow import", False, f"Import failed: {str(e)}") all_passed = False issues.append(f"Failed to import MainWindow: {str(e)}") except Exception as e: print_result("MainWindow import", False, f"Error: {str(e)}") all_passed = False issues.append(f"Error importing MainWindow: {str(e)}") if verbose: traceback.print_exc() return all_passed, issues def check_controllers(base_dir: Path, verbose: bool = False) -> Tuple[bool, List[str]]: """Check if all controllers exist and can be imported""" print_header("Checking Controllers") all_passed = True issues = [] # List of controller files to check controller_files = [ "controllers/video_controller_new.py", "controllers/analytics_controller.py", "controllers/model_manager.py", "controllers/performance_overlay.py" ] # Check file existence for controller_file in controller_files: file_path = base_dir / controller_file if file_path.exists(): print_result(controller_file, True, "File exists") else: print_result(controller_file, False, "File not found") all_passed = False issues.append(f"Missing controller file: {controller_file}") # Try importing video controller if verbose: print("\nAttempting to import VideoController...") try: sys.path.insert(0, str(base_dir)) from controllers.video_controller_new import VideoController print_result("VideoController import", True, "Import successful") except ImportError as e: print_result("VideoController import", False, f"Import failed: {str(e)}") all_passed = False issues.append(f"Failed to import VideoController: {str(e)}") except Exception as e: print_result("VideoController import", False, f"Error: {str(e)}") all_passed = False issues.append(f"Error importing VideoController: {str(e)}") if verbose: traceback.print_exc() return all_passed, issues def check_hardware_compatibility(verbose: bool = False) -> Tuple[bool, Dict[str, Any]]: """Check hardware compatibility for OpenVINO and GPU acceleration""" print_header("Checking Hardware Compatibility") result = { "cpu_compatible": True, "gpu_available": False, "openvino_available": False, "system_info": { "os": platform.system(), "processor": platform.processor(), "python_version": platform.python_version(), } } # Print system information print(f"OS: {result['system_info']['os']}") print(f"Processor: {result['system_info']['processor']}") print(f"Python Version: {result['system_info']['python_version']}") # Check OpenVINO using subprocess to avoid import errors openvino_check_cmd = [sys.executable, "-c", "import openvino; print(openvino.__version__)"] try: openvino_output = subprocess.run(openvino_check_cmd, capture_output=True, text=True) if openvino_output.returncode == 0: openvino_version = openvino_output.stdout.strip() result["openvino_available"] = True result["openvino_version"] = openvino_version print_result("OpenVINO", True, f"Version {openvino_version}") # Try to get available devices device_check_cmd = [ sys.executable, "-c", "from openvino.runtime import Core; ie = Core(); print(','.join(ie.available_devices))" ] device_output = subprocess.run(device_check_cmd, capture_output=True, text=True) if device_output.returncode == 0: devices = device_output.stdout.strip().split(',') result["available_devices"] = devices print(f"Available devices: {', '.join(devices)}") # Check for GPU if any("GPU" in device for device in devices): result["gpu_available"] = True print_result("GPU acceleration", True, "GPU device available for inference") else: print_result("GPU acceleration", False, "No GPU device available for inference") else: print_result("Device query", False, "Could not query OpenVINO devices") if verbose and device_output.stderr: print(f"Error: {device_output.stderr}") else: print_result("OpenVINO", False, "OpenVINO not installed or not working") if verbose and openvino_output.stderr: print(f"Error: {openvino_output.stderr}") except Exception as e: print_result("OpenVINO", False, f"Error checking OpenVINO: {str(e)}") result["openvino_available"] = False if verbose: traceback.print_exc() # Check for CUDA if torch is available using subprocess torch_check_cmd = [ sys.executable, "-c", "import torch; print(f'{torch.__version__},{torch.cuda.is_available()}')" ] try: torch_output = subprocess.run(torch_check_cmd, capture_output=True, text=True) if torch_output.returncode == 0: torch_info = torch_output.stdout.strip().split(',') if len(torch_info) == 2: torch_version = torch_info[0] cuda_available = torch_info[1].lower() == 'true' result["torch_available"] = True result["torch_version"] = torch_version result["cuda_available"] = cuda_available if cuda_available: # Get CUDA details cuda_info_cmd = [ sys.executable, "-c", "import torch; print(f'{torch.version.cuda},{torch.cuda.device_count()},{torch.cuda.get_device_name(0) if torch.cuda.device_count() > 0 else \"Unknown\"}')" ] cuda_output = subprocess.run(cuda_info_cmd, capture_output=True, text=True) if cuda_output.returncode == 0: cuda_info = cuda_output.stdout.strip().split(',') if len(cuda_info) == 3: cuda_version = cuda_info[0] device_count = cuda_info[1] device_name = cuda_info[2] print_result("PyTorch CUDA", True, f"CUDA {cuda_version}, {device_count} device(s): {device_name}") result["cuda_version"] = cuda_version result["cuda_device_count"] = int(device_count) result["cuda_device_name"] = device_name # Update GPU availability result["gpu_available"] = True else: print_result("PyTorch CUDA details", False, "Could not get CUDA details") if verbose and cuda_output.stderr: print(f"Error: {cuda_output.stderr}") else: print_result("PyTorch CUDA", False, "CUDA not available") else: print_result("PyTorch", False, "PyTorch not installed") if verbose and torch_output.stderr: print(f"Error: {torch_output.stderr}") except Exception as e: print_result("PyTorch check", False, f"Error: {str(e)}") result["torch_available"] = False if verbose: traceback.print_exc() # Consider the system compatible if either OpenVINO is available or GPU acceleration is available return result["openvino_available"] or result["gpu_available"], result def check_camera_access(verbose: bool = False) -> Tuple[bool, List[int]]: """Check if cameras are accessible""" print_header("Checking Camera Access") available_cameras = [] camera_access = False # Use subprocess to run OpenCV check to avoid direct import errors cv2_check_script = """ import cv2 import json import sys def check_cameras(max_id=3): results = [] for camera_id in range(max_id+1): cap = cv2.VideoCapture(camera_id) if cap.isOpened(): ret, frame = cap.read() if ret: h, w = frame.shape[:2] results.append({ 'id': camera_id, 'accessible': True, 'width': w, 'height': h }) else: results.append({ 'id': camera_id, 'accessible': False }) cap.release() else: results.append({ 'id': camera_id, 'accessible': False }) return results # Only check additional cameras if verbose mode is enabled max_id = 3 if len(sys.argv) > 1 and sys.argv[1] == 'verbose' else 0 results = check_cameras(max_id) print(json.dumps(results)) """ try: # Execute the camera check script cmd = [sys.executable, "-c", cv2_check_script] if verbose: cmd.append("verbose") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: try: camera_results = json.loads(result.stdout) for camera in camera_results: camera_id = camera.get('id', 0) if camera.get('accessible', False): camera_access = True available_cameras.append(camera_id) resolution = f"{camera.get('width', 'unknown')}x{camera.get('height', 'unknown')}" print_result(f"Camera (ID: {camera_id})", True, f"Accessible, Resolution: {resolution}") else: print_result(f"Camera (ID: {camera_id})", False, "Not accessible") except json.JSONDecodeError: print_result("Camera check", False, "Error parsing camera results") if verbose: print(f"Output: {result.stdout}") else: print_result("Camera access", False, "OpenCV not installed or error accessing cameras") if verbose and result.stderr: print(f"Error: {result.stderr}") except Exception as e: print_result("Camera check", False, f"Error: {str(e)}") if verbose: traceback.print_exc() return camera_access, available_cameras def main(): """Main function to run system validation""" parser = argparse.ArgumentParser(description="Validate Traffic Monitoring System") parser.add_argument("--fix", action="store_true", help="Attempt to fix issues") parser.add_argument("--verbose", action="store_true", help="Show detailed output") args = parser.parse_args() # Get base directory base_dir = Path(os.path.dirname(os.path.abspath(__file__))) print(f"\n{Colors.BOLD}Traffic Monitoring System Validation{Colors.END}") print(f"Base directory: {base_dir}") print(f"Python executable: {sys.executable}") # Run all checks packages_ok, missing_packages = check_packages(fix=args.fix, verbose=args.verbose) models_ok, missing_models = check_models(base_dir, fix=args.fix, verbose=args.verbose) config_ok, config_issues = check_config(base_dir, fix=args.fix, verbose=args.verbose) ui_ok, ui_issues = check_ui_components(base_dir, verbose=args.verbose) controllers_ok, controller_issues = check_controllers(base_dir, verbose=args.verbose) hw_ok, hw_info = check_hardware_compatibility(verbose=args.verbose) camera_ok, available_cameras = check_camera_access(verbose=args.verbose) # Print summary print_header("Validation Summary") print_result("Python Packages", packages_ok, f"{len(missing_packages)} issues" if missing_packages else "All required packages found") print_result("Model Files", models_ok, f"{len(missing_models)} issues" if missing_models else "All model files found") print_result("Configuration", config_ok, f"{len(config_issues)} issues" if config_issues else "Configuration valid") print_result("UI Components", ui_ok, f"{len(ui_issues)} issues" if ui_issues else "All UI components found") print_result("Controllers", controllers_ok, f"{len(controller_issues)} issues" if controller_issues else "All controllers found") print_result("Hardware Compatibility", hw_ok, "GPU or OpenVINO available" if hw_ok else "No GPU or OpenVINO") print_result("Camera Access", camera_ok, f"{len(available_cameras)} cameras available" if camera_ok else "No cameras accessible") # Overall result all_ok = packages_ok and models_ok and config_ok and ui_ok and controllers_ok and hw_ok print(f"\n{Colors.BOLD}Overall Result: {'SUCCESS' if all_ok else 'ISSUES FOUND'}{Colors.END}") if all_ok: print(f"{Colors.GREEN}The system is correctly set up and should run without issues.{Colors.END}") else: print(f"{Colors.YELLOW}Please fix the issues reported above before running the application.{Colors.END}") if missing_packages: print(f"\n{Colors.BOLD}Missing Packages:{Colors.END}") print(f"Run: {sys.executable} -m pip install " + " ".join(missing_packages)) if missing_models: print(f"\n{Colors.BOLD}Missing Models:{Colors.END}") print("Download missing model files or check their paths in config.json") return 0 if all_ok else 1 if __name__ == "__main__": sys.exit(main())