Files
Traffic-Intersection-Monito…/qt_app_pyside1/validate_system.py

730 lines
30 KiB
Python

#!/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())