Chore: rename api->camera-management-api and web->management-dashboard-web-app; update compose, ignore, README references
This commit is contained in:
13
camera-management-api/usda_vision_system/camera/__init__.py
Normal file
13
camera-management-api/usda_vision_system/camera/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Camera module for the USDA Vision Camera System.
|
||||
|
||||
This module handles GigE camera discovery, management, monitoring, and recording
|
||||
using the camera SDK library (mvsdk).
|
||||
"""
|
||||
|
||||
from .manager import CameraManager
|
||||
from .recorder import CameraRecorder
|
||||
from .monitor import CameraMonitor
|
||||
from .streamer import CameraStreamer
|
||||
|
||||
__all__ = ["CameraManager", "CameraRecorder", "CameraMonitor", "CameraStreamer"]
|
||||
546
camera-management-api/usda_vision_system/camera/manager.py
Normal file
546
camera-management-api/usda_vision_system/camera/manager.py
Normal file
@@ -0,0 +1,546 @@
|
||||
"""
|
||||
Camera Manager for the USDA Vision Camera System.
|
||||
|
||||
This module manages GigE camera discovery, initialization, and coordination
|
||||
with the recording system based on machine state changes.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
from datetime import datetime
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk"))
|
||||
import mvsdk
|
||||
|
||||
from ..core.config import Config, CameraConfig
|
||||
from ..core.state_manager import StateManager, CameraStatus
|
||||
from ..core.events import EventSystem, EventType, Event, publish_camera_status_changed
|
||||
from ..core.timezone_utils import format_filename_timestamp
|
||||
from .recorder import CameraRecorder
|
||||
from .monitor import CameraMonitor
|
||||
from .streamer import CameraStreamer
|
||||
from .sdk_config import initialize_sdk_with_suppression
|
||||
|
||||
|
||||
class CameraManager:
|
||||
"""Manages all cameras in the system"""
|
||||
|
||||
def __init__(self, config: Config, state_manager: StateManager, event_system: EventSystem):
|
||||
self.config = config
|
||||
self.state_manager = state_manager
|
||||
self.event_system = event_system
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize SDK early to suppress error messages
|
||||
initialize_sdk_with_suppression()
|
||||
|
||||
# Camera management
|
||||
self.available_cameras: List[Any] = [] # mvsdk camera device info
|
||||
self.camera_recorders: Dict[str, CameraRecorder] = {} # camera_name -> recorder
|
||||
self.camera_streamers: Dict[str, CameraStreamer] = {} # camera_name -> streamer
|
||||
self.camera_monitor: Optional[CameraMonitor] = None
|
||||
|
||||
# Threading
|
||||
self._lock = threading.RLock()
|
||||
self.running = False
|
||||
|
||||
# Subscribe to machine state changes
|
||||
self.event_system.subscribe(EventType.MACHINE_STATE_CHANGED, self._on_machine_state_changed)
|
||||
|
||||
# Initialize camera discovery
|
||||
self._discover_cameras()
|
||||
|
||||
# Create camera monitor
|
||||
self.camera_monitor = CameraMonitor(config=config, state_manager=state_manager, event_system=event_system, camera_manager=self)
|
||||
|
||||
def start(self) -> bool:
|
||||
"""Start the camera manager"""
|
||||
if self.running:
|
||||
self.logger.warning("Camera manager is already running")
|
||||
return True
|
||||
|
||||
self.logger.info("Starting camera manager...")
|
||||
self.running = True
|
||||
|
||||
# Start camera monitor
|
||||
if self.camera_monitor:
|
||||
self.camera_monitor.start()
|
||||
|
||||
# Initialize camera recorders
|
||||
self._initialize_recorders()
|
||||
|
||||
# Initialize camera streamers
|
||||
self._initialize_streamers()
|
||||
|
||||
self.logger.info("Camera manager started successfully")
|
||||
return True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the camera manager"""
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.logger.info("Stopping camera manager...")
|
||||
self.running = False
|
||||
|
||||
# Stop camera monitor
|
||||
if self.camera_monitor:
|
||||
self.camera_monitor.stop()
|
||||
|
||||
# Stop all active recordings
|
||||
with self._lock:
|
||||
for recorder in self.camera_recorders.values():
|
||||
if recorder.is_recording():
|
||||
recorder.stop_recording()
|
||||
recorder.cleanup()
|
||||
|
||||
# Stop all active streaming
|
||||
with self._lock:
|
||||
for streamer in self.camera_streamers.values():
|
||||
if streamer.is_streaming():
|
||||
streamer.stop_streaming()
|
||||
|
||||
self.logger.info("Camera manager stopped")
|
||||
|
||||
def _discover_cameras(self) -> None:
|
||||
"""Discover available GigE cameras"""
|
||||
try:
|
||||
self.logger.info("Discovering GigE cameras...")
|
||||
|
||||
# Enumerate cameras using mvsdk
|
||||
device_list = mvsdk.CameraEnumerateDevice()
|
||||
self.available_cameras = device_list
|
||||
|
||||
self.logger.info(f"Found {len(device_list)} camera(s)")
|
||||
|
||||
for i, dev_info in enumerate(device_list):
|
||||
try:
|
||||
name = dev_info.GetFriendlyName()
|
||||
port_type = dev_info.GetPortType()
|
||||
serial = getattr(dev_info, "acSn", "Unknown")
|
||||
|
||||
self.logger.info(f" Camera {i}: {name} ({port_type}) - Serial: {serial}")
|
||||
|
||||
# Update state manager with discovered camera
|
||||
camera_name = f"camera{i+1}" # Default naming
|
||||
self.state_manager.update_camera_status(name=camera_name, status="available", device_info={"friendly_name": name, "port_type": port_type, "serial_number": serial, "device_index": i})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing camera {i}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error discovering cameras: {e}")
|
||||
self.available_cameras = []
|
||||
|
||||
def _initialize_recorders(self) -> None:
|
||||
"""Initialize camera recorders for configured cameras"""
|
||||
with self._lock:
|
||||
for camera_config in self.config.cameras:
|
||||
if not camera_config.enabled:
|
||||
continue
|
||||
|
||||
try:
|
||||
# Find matching physical camera
|
||||
device_info = self._find_camera_device(camera_config.name)
|
||||
if device_info is None:
|
||||
self.logger.warning(f"No physical camera found for configured camera: {camera_config.name}")
|
||||
# Update state to indicate camera is not available
|
||||
self.state_manager.update_camera_status(name=camera_config.name, status="not_found", device_info=None)
|
||||
continue
|
||||
|
||||
# Create recorder (uses lazy initialization - camera will be initialized when recording starts)
|
||||
recorder = CameraRecorder(camera_config=camera_config, device_info=device_info, state_manager=self.state_manager, event_system=self.event_system)
|
||||
|
||||
# Add recorder to the list (camera will be initialized lazily when needed)
|
||||
self.camera_recorders[camera_config.name] = recorder
|
||||
self.logger.info(f"Successfully created recorder for camera: {camera_config.name} (lazy initialization)")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing recorder for {camera_config.name}: {e}")
|
||||
# Update state to indicate error
|
||||
self.state_manager.update_camera_status(name=camera_config.name, status="error", device_info={"error": str(e)})
|
||||
|
||||
def _find_camera_device(self, camera_name: str) -> Optional[Any]:
|
||||
"""Find physical camera device for a configured camera"""
|
||||
# For now, use simple mapping: camera1 -> device 0, camera2 -> device 1, etc.
|
||||
# This could be enhanced to use serial numbers or other identifiers
|
||||
|
||||
camera_index_map = {"camera1": 0, "camera2": 1, "camera3": 2, "camera4": 3}
|
||||
|
||||
device_index = camera_index_map.get(camera_name)
|
||||
if device_index is not None and device_index < len(self.available_cameras):
|
||||
return self.available_cameras[device_index]
|
||||
|
||||
return None
|
||||
|
||||
def _on_machine_state_changed(self, event: Event) -> None:
|
||||
"""Handle machine state change events"""
|
||||
try:
|
||||
machine_name = event.data.get("machine_name")
|
||||
new_state = event.data.get("state")
|
||||
|
||||
if not machine_name or not new_state:
|
||||
return
|
||||
|
||||
self.logger.info(f"Handling machine state change: {machine_name} -> {new_state}")
|
||||
|
||||
# Find camera associated with this machine
|
||||
camera_config = None
|
||||
for config in self.config.cameras:
|
||||
if config.machine_topic == machine_name:
|
||||
camera_config = config
|
||||
break
|
||||
|
||||
if not camera_config:
|
||||
self.logger.warning(f"No camera configured for machine: {machine_name}")
|
||||
return
|
||||
|
||||
# Get the recorder for this camera
|
||||
recorder = self.camera_recorders.get(camera_config.name)
|
||||
if not recorder:
|
||||
self.logger.warning(f"No recorder found for camera: {camera_config.name}")
|
||||
return
|
||||
|
||||
# Handle state change
|
||||
if new_state == "on":
|
||||
self._start_recording(camera_config.name, recorder)
|
||||
elif new_state in ["off", "error"]:
|
||||
self._stop_recording(camera_config.name, recorder)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error handling machine state change: {e}")
|
||||
|
||||
def _start_recording(self, camera_name: str, recorder: CameraRecorder) -> None:
|
||||
"""Start recording for a camera"""
|
||||
try:
|
||||
if recorder.is_recording():
|
||||
self.logger.info(f"Camera {camera_name} is already recording")
|
||||
return
|
||||
|
||||
# Generate filename with Atlanta timezone timestamp
|
||||
timestamp = format_filename_timestamp()
|
||||
camera_config = self.config.get_camera_by_name(camera_name)
|
||||
video_format = camera_config.video_format if camera_config else "mp4"
|
||||
filename = f"{camera_name}_recording_{timestamp}.{video_format}"
|
||||
|
||||
# Start recording
|
||||
success = recorder.start_recording(filename)
|
||||
if success:
|
||||
self.logger.info(f"Started recording for camera {camera_name}: {filename}")
|
||||
else:
|
||||
self.logger.error(f"Failed to start recording for camera {camera_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error starting recording for {camera_name}: {e}")
|
||||
|
||||
def _stop_recording(self, camera_name: str, recorder: CameraRecorder) -> None:
|
||||
"""Stop recording for a camera"""
|
||||
try:
|
||||
if not recorder.is_recording():
|
||||
self.logger.info(f"Camera {camera_name} is not recording")
|
||||
return
|
||||
|
||||
# Stop recording
|
||||
success = recorder.stop_recording()
|
||||
if success:
|
||||
self.logger.info(f"Stopped recording for camera {camera_name}")
|
||||
else:
|
||||
self.logger.error(f"Failed to stop recording for camera {camera_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error stopping recording for {camera_name}: {e}")
|
||||
|
||||
def get_camera_status(self, camera_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get status of a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
return None
|
||||
|
||||
return recorder.get_status()
|
||||
|
||||
def get_all_camera_status(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""Get status of all cameras"""
|
||||
status = {}
|
||||
with self._lock:
|
||||
for camera_name, recorder in self.camera_recorders.items():
|
||||
status[camera_name] = recorder.get_status()
|
||||
return status
|
||||
|
||||
def manual_start_recording(self, camera_name: str, filename: Optional[str] = None, exposure_ms: Optional[float] = None, gain: Optional[float] = None, fps: Optional[float] = None) -> bool:
|
||||
"""Manually start recording for a camera with optional camera settings"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
# Update camera settings if provided
|
||||
if exposure_ms is not None or gain is not None or fps is not None:
|
||||
settings_updated = recorder.update_camera_settings(exposure_ms=exposure_ms, gain=gain, target_fps=fps)
|
||||
if not settings_updated:
|
||||
self.logger.warning(f"Failed to update camera settings for {camera_name}")
|
||||
|
||||
# Generate filename with datetime prefix
|
||||
timestamp = format_filename_timestamp()
|
||||
camera_config = self.config.get_camera_by_name(camera_name)
|
||||
video_format = camera_config.video_format if camera_config else "mp4"
|
||||
|
||||
if filename:
|
||||
# Always prepend datetime to the provided filename
|
||||
filename = f"{timestamp}_{filename}"
|
||||
else:
|
||||
filename = f"{camera_name}_manual_{timestamp}.{video_format}"
|
||||
|
||||
return recorder.start_recording(filename)
|
||||
|
||||
def manual_stop_recording(self, camera_name: str) -> bool:
|
||||
"""Manually stop recording for a camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
return recorder.stop_recording()
|
||||
|
||||
def get_available_cameras(self) -> List[Dict[str, Any]]:
|
||||
"""Get list of available physical cameras"""
|
||||
cameras = []
|
||||
for i, dev_info in enumerate(self.available_cameras):
|
||||
try:
|
||||
cameras.append({"index": i, "name": dev_info.GetFriendlyName(), "port_type": dev_info.GetPortType(), "serial_number": getattr(dev_info, "acSn", "Unknown")})
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting info for camera {i}: {e}")
|
||||
|
||||
return cameras
|
||||
|
||||
def refresh_camera_discovery(self) -> int:
|
||||
"""Refresh camera discovery and return number of cameras found"""
|
||||
self._discover_cameras()
|
||||
return len(self.available_cameras)
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""Check if camera manager is running"""
|
||||
return self.running
|
||||
|
||||
def test_camera_connection(self, camera_name: str) -> bool:
|
||||
"""Test connection for a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
return recorder.test_connection()
|
||||
|
||||
def reconnect_camera(self, camera_name: str) -> bool:
|
||||
"""Attempt to reconnect a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
success = recorder.reconnect()
|
||||
|
||||
# Update camera status based on result
|
||||
if success:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="connected", error=None)
|
||||
else:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="connection_failed", error="Reconnection failed")
|
||||
|
||||
return success
|
||||
|
||||
def restart_camera_grab(self, camera_name: str) -> bool:
|
||||
"""Restart grab process for a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
success = recorder.restart_grab()
|
||||
|
||||
# Update camera status based on result
|
||||
if success:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="connected", error=None)
|
||||
else:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="grab_failed", error="Grab restart failed")
|
||||
|
||||
return success
|
||||
|
||||
def reset_camera_timestamp(self, camera_name: str) -> bool:
|
||||
"""Reset timestamp for a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
return recorder.reset_timestamp()
|
||||
|
||||
def full_reset_camera(self, camera_name: str) -> bool:
|
||||
"""Perform full reset for a specific camera"""
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera not found: {camera_name}")
|
||||
return False
|
||||
|
||||
success = recorder.full_reset()
|
||||
|
||||
# Update camera status based on result
|
||||
if success:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="connected", error=None)
|
||||
else:
|
||||
self.state_manager.update_camera_status(name=camera_name, status="reset_failed", error="Full reset failed")
|
||||
|
||||
return success
|
||||
|
||||
def reinitialize_failed_camera(self, camera_name: str) -> bool:
|
||||
"""Attempt to reinitialize a camera that failed to initialize"""
|
||||
with self._lock:
|
||||
# Find the camera config
|
||||
camera_config = None
|
||||
for config in self.config.cameras:
|
||||
if config.name == camera_name:
|
||||
camera_config = config
|
||||
break
|
||||
|
||||
if not camera_config:
|
||||
self.logger.error(f"No configuration found for camera: {camera_name}")
|
||||
return False
|
||||
|
||||
if not camera_config.enabled:
|
||||
self.logger.error(f"Camera {camera_name} is disabled in configuration")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Remove existing recorder if any
|
||||
if camera_name in self.camera_recorders:
|
||||
old_recorder = self.camera_recorders[camera_name]
|
||||
try:
|
||||
old_recorder._cleanup_camera()
|
||||
except:
|
||||
pass # Ignore cleanup errors
|
||||
del self.camera_recorders[camera_name]
|
||||
|
||||
# Find matching physical camera
|
||||
device_info = self._find_camera_device(camera_name)
|
||||
if device_info is None:
|
||||
self.logger.warning(f"No physical camera found for configured camera: {camera_name}")
|
||||
self.state_manager.update_camera_status(name=camera_name, status="not_found", device_info=None)
|
||||
return False
|
||||
|
||||
# Create new recorder (uses lazy initialization)
|
||||
recorder = CameraRecorder(camera_config=camera_config, device_info=device_info, state_manager=self.state_manager, event_system=self.event_system)
|
||||
|
||||
# Success - add to recorders (camera will be initialized lazily when needed)
|
||||
self.camera_recorders[camera_name] = recorder
|
||||
self.state_manager.update_camera_status(name=camera_name, status="connected", error=None)
|
||||
|
||||
self.logger.info(f"Successfully reinitialized camera recorder: {camera_name} (lazy initialization)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error reinitializing camera {camera_name}: {e}")
|
||||
self.state_manager.update_camera_status(name=camera_name, status="error", device_info={"error": str(e)})
|
||||
return False
|
||||
|
||||
def _initialize_streamers(self) -> None:
|
||||
"""Initialize camera streamers for configured cameras"""
|
||||
with self._lock:
|
||||
for camera_config in self.config.cameras:
|
||||
if not camera_config.enabled:
|
||||
continue
|
||||
|
||||
try:
|
||||
# Find matching physical camera
|
||||
device_info = self._find_camera_device(camera_config.name)
|
||||
if device_info is None:
|
||||
self.logger.warning(f"No physical camera found for streaming: {camera_config.name}")
|
||||
continue
|
||||
|
||||
# Create streamer
|
||||
streamer = CameraStreamer(camera_config=camera_config, device_info=device_info, state_manager=self.state_manager, event_system=self.event_system)
|
||||
|
||||
# Add streamer to the list
|
||||
self.camera_streamers[camera_config.name] = streamer
|
||||
self.logger.info(f"Successfully created streamer for camera: {camera_config.name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing streamer for {camera_config.name}: {e}")
|
||||
|
||||
def get_camera_streamer(self, camera_name: str) -> Optional[CameraStreamer]:
|
||||
"""Get camera streamer for a specific camera"""
|
||||
return self.camera_streamers.get(camera_name)
|
||||
|
||||
def start_camera_streaming(self, camera_name: str) -> bool:
|
||||
"""Start streaming for a specific camera"""
|
||||
streamer = self.camera_streamers.get(camera_name)
|
||||
if not streamer:
|
||||
self.logger.error(f"Camera streamer not found: {camera_name}")
|
||||
return False
|
||||
|
||||
return streamer.start_streaming()
|
||||
|
||||
def stop_camera_streaming(self, camera_name: str) -> bool:
|
||||
"""Stop streaming for a specific camera"""
|
||||
streamer = self.camera_streamers.get(camera_name)
|
||||
if not streamer:
|
||||
self.logger.error(f"Camera streamer not found: {camera_name}")
|
||||
return False
|
||||
|
||||
return streamer.stop_streaming()
|
||||
|
||||
def is_camera_streaming(self, camera_name: str) -> bool:
|
||||
"""Check if a camera is currently streaming"""
|
||||
streamer = self.camera_streamers.get(camera_name)
|
||||
if not streamer:
|
||||
return False
|
||||
|
||||
return streamer.is_streaming()
|
||||
|
||||
def get_camera_config(self, camera_name: str) -> Optional[CameraConfig]:
|
||||
"""Get camera configuration"""
|
||||
return self.config.get_camera_by_name(camera_name)
|
||||
|
||||
def update_camera_config(self, camera_name: str, **kwargs) -> bool:
|
||||
"""Update camera configuration and save to config file"""
|
||||
try:
|
||||
# Update the configuration
|
||||
success = self.config.update_camera_config(camera_name, **kwargs)
|
||||
if success:
|
||||
self.logger.info(f"Updated configuration for camera {camera_name}: {kwargs}")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to update configuration for camera {camera_name}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error updating camera configuration: {e}")
|
||||
return False
|
||||
|
||||
def apply_camera_config(self, camera_name: str) -> bool:
|
||||
"""Apply current configuration to active camera (requires camera restart)"""
|
||||
try:
|
||||
# Get the recorder for this camera
|
||||
recorder = self.camera_recorders.get(camera_name)
|
||||
if not recorder:
|
||||
self.logger.error(f"Camera recorder not found: {camera_name}")
|
||||
return False
|
||||
|
||||
# Stop recording if active
|
||||
was_recording = recorder.is_recording()
|
||||
if was_recording:
|
||||
recorder.stop_recording()
|
||||
|
||||
# Reinitialize the camera with new settings
|
||||
success = self.reinitialize_failed_camera(camera_name)
|
||||
|
||||
if success:
|
||||
self.logger.info(f"Successfully applied configuration to camera {camera_name}")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to apply configuration to camera {camera_name}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error applying camera configuration: {e}")
|
||||
return False
|
||||
256
camera-management-api/usda_vision_system/camera/monitor.py
Normal file
256
camera-management-api/usda_vision_system/camera/monitor.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""
|
||||
Camera Monitor for the USDA Vision Camera System.
|
||||
|
||||
This module monitors camera status and availability at regular intervals.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import contextlib
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk"))
|
||||
import mvsdk
|
||||
|
||||
from ..core.config import Config
|
||||
from ..core.state_manager import StateManager, CameraStatus
|
||||
from ..core.events import EventSystem, publish_camera_status_changed
|
||||
from .sdk_config import ensure_sdk_initialized
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_camera_errors():
|
||||
"""Context manager to temporarily suppress camera SDK error output"""
|
||||
# Save original file descriptors
|
||||
original_stderr = os.dup(2)
|
||||
original_stdout = os.dup(1)
|
||||
|
||||
try:
|
||||
# Redirect stderr and stdout to devnull
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(devnull, 2) # stderr
|
||||
os.dup2(devnull, 1) # stdout (in case SDK uses stdout)
|
||||
os.close(devnull)
|
||||
|
||||
yield
|
||||
|
||||
finally:
|
||||
# Restore original file descriptors
|
||||
os.dup2(original_stderr, 2)
|
||||
os.dup2(original_stdout, 1)
|
||||
os.close(original_stderr)
|
||||
os.close(original_stdout)
|
||||
|
||||
|
||||
class CameraMonitor:
|
||||
"""Monitors camera status and availability"""
|
||||
|
||||
def __init__(self, config: Config, state_manager: StateManager, event_system: EventSystem, camera_manager=None):
|
||||
self.config = config
|
||||
self.state_manager = state_manager
|
||||
self.event_system = event_system
|
||||
self.camera_manager = camera_manager # Reference to camera manager
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Monitoring settings
|
||||
self.check_interval = config.system.camera_check_interval_seconds
|
||||
|
||||
# Threading
|
||||
self.running = False
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
# Status tracking
|
||||
self.last_check_time: Optional[float] = None
|
||||
self.check_count = 0
|
||||
self.error_count = 0
|
||||
|
||||
def start(self) -> bool:
|
||||
"""Start camera monitoring"""
|
||||
if self.running:
|
||||
self.logger.warning("Camera monitor is already running")
|
||||
return True
|
||||
|
||||
self.logger.info(f"Starting camera monitor (check interval: {self.check_interval}s)")
|
||||
self.running = True
|
||||
self._stop_event.clear()
|
||||
|
||||
# Start monitoring thread
|
||||
self._thread = threading.Thread(target=self._monitoring_loop, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
return True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop camera monitoring"""
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.logger.info("Stopping camera monitor...")
|
||||
self.running = False
|
||||
self._stop_event.set()
|
||||
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._thread.join(timeout=5)
|
||||
|
||||
self.logger.info("Camera monitor stopped")
|
||||
|
||||
def _monitoring_loop(self) -> None:
|
||||
"""Main monitoring loop"""
|
||||
self.logger.info("Camera monitoring loop started")
|
||||
|
||||
while self.running and not self._stop_event.is_set():
|
||||
try:
|
||||
self.last_check_time = time.time()
|
||||
self.check_count += 1
|
||||
|
||||
# Check all configured cameras
|
||||
self._check_all_cameras()
|
||||
|
||||
# Wait for next check
|
||||
if self._stop_event.wait(self.check_interval):
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
self.error_count += 1
|
||||
self.logger.error(f"Error in camera monitoring loop: {e}")
|
||||
|
||||
# Wait a bit before retrying
|
||||
if self._stop_event.wait(min(self.check_interval, 10)):
|
||||
break
|
||||
|
||||
self.logger.info("Camera monitoring loop ended")
|
||||
|
||||
def _check_all_cameras(self) -> None:
|
||||
"""Check status of all configured cameras"""
|
||||
for camera_config in self.config.cameras:
|
||||
if not camera_config.enabled:
|
||||
continue
|
||||
|
||||
try:
|
||||
self._check_camera_status(camera_config.name)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking camera {camera_config.name}: {e}")
|
||||
|
||||
def _check_camera_status(self, camera_name: str) -> None:
|
||||
"""Check status of a specific camera"""
|
||||
try:
|
||||
# Get current status from state manager
|
||||
current_info = self.state_manager.get_camera_status(camera_name)
|
||||
|
||||
# Perform actual camera check
|
||||
status, details, device_info = self._perform_camera_check(camera_name)
|
||||
|
||||
# Update state if changed
|
||||
old_status = current_info.status.value if current_info else "unknown"
|
||||
if old_status != status:
|
||||
self.state_manager.update_camera_status(name=camera_name, status=status, error=details if status == "error" else None, device_info=device_info)
|
||||
|
||||
# Publish status change event
|
||||
publish_camera_status_changed(camera_name=camera_name, status=status, details=details)
|
||||
|
||||
self.logger.info(f"Camera {camera_name} status changed: {old_status} -> {status}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking camera {camera_name}: {e}")
|
||||
|
||||
# Update to error state
|
||||
self.state_manager.update_camera_status(name=camera_name, status="error", error=str(e))
|
||||
|
||||
def _perform_camera_check(self, camera_name: str) -> tuple[str, str, Optional[Dict[str, Any]]]:
|
||||
"""Perform actual camera availability check"""
|
||||
try:
|
||||
# Get camera device info from camera manager
|
||||
if not self.camera_manager:
|
||||
return "error", "Camera manager not available", None
|
||||
|
||||
device_info = self.camera_manager._find_camera_device(camera_name)
|
||||
if not device_info:
|
||||
return "disconnected", "Camera device not found", None
|
||||
|
||||
# Check if camera is already opened by another process
|
||||
if mvsdk.CameraIsOpened(device_info):
|
||||
# Camera is opened - check if it's our recorder that's currently recording
|
||||
recorder = self.camera_manager.camera_recorders.get(camera_name)
|
||||
if recorder and recorder.hCamera and recorder.recording:
|
||||
return "available", "Camera recording (in use by system)", self._get_device_info_dict(device_info)
|
||||
else:
|
||||
return "busy", "Camera opened by another process", self._get_device_info_dict(device_info)
|
||||
|
||||
# Try to initialize camera briefly to test availability
|
||||
try:
|
||||
# Ensure SDK is initialized
|
||||
ensure_sdk_initialized()
|
||||
|
||||
# Suppress output to avoid MVCAMAPI error messages during camera testing
|
||||
with suppress_camera_errors():
|
||||
hCamera = mvsdk.CameraInit(device_info, -1, -1)
|
||||
|
||||
# Quick test - try to get one frame
|
||||
try:
|
||||
mvsdk.CameraSetTriggerMode(hCamera, 0)
|
||||
mvsdk.CameraPlay(hCamera)
|
||||
|
||||
# Try to capture with short timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, 500)
|
||||
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
|
||||
|
||||
# Success - camera is available
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
return "available", "Camera test successful", self._get_device_info_dict(device_info)
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
if e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT:
|
||||
return "available", "Camera available but slow response", self._get_device_info_dict(device_info)
|
||||
else:
|
||||
return "error", f"Camera test failed: {e.message}", self._get_device_info_dict(device_info)
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
return "error", f"Camera initialization failed: {e.message}", self._get_device_info_dict(device_info)
|
||||
|
||||
except Exception as e:
|
||||
return "error", f"Camera check failed: {str(e)}", None
|
||||
|
||||
def _get_device_info_dict(self, device_info) -> Dict[str, Any]:
|
||||
"""Convert device info to dictionary"""
|
||||
try:
|
||||
return {"friendly_name": device_info.GetFriendlyName(), "port_type": device_info.GetPortType(), "serial_number": getattr(device_info, "acSn", "Unknown"), "last_checked": time.time()}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting device info: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def check_camera_now(self, camera_name: str) -> Dict[str, Any]:
|
||||
"""Manually check a specific camera status"""
|
||||
try:
|
||||
status, details, device_info = self._perform_camera_check(camera_name)
|
||||
|
||||
# Update state
|
||||
self.state_manager.update_camera_status(name=camera_name, status=status, error=details if status == "error" else None, device_info=device_info)
|
||||
|
||||
return {"camera_name": camera_name, "status": status, "details": details, "device_info": device_info, "check_time": time.time()}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Manual camera check failed: {e}"
|
||||
self.logger.error(error_msg)
|
||||
return {"camera_name": camera_name, "status": "error", "details": error_msg, "device_info": None, "check_time": time.time()}
|
||||
|
||||
def check_all_cameras_now(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""Manually check all cameras"""
|
||||
results = {}
|
||||
for camera_config in self.config.cameras:
|
||||
if camera_config.enabled:
|
||||
results[camera_config.name] = self.check_camera_now(camera_config.name)
|
||||
return results
|
||||
|
||||
def get_monitoring_stats(self) -> Dict[str, Any]:
|
||||
"""Get monitoring statistics"""
|
||||
return {"running": self.running, "check_interval_seconds": self.check_interval, "total_checks": self.check_count, "error_count": self.error_count, "last_check_time": self.last_check_time, "success_rate": (self.check_count - self.error_count) / max(self.check_count, 1) * 100}
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""Check if monitor is running"""
|
||||
return self.running
|
||||
915
camera-management-api/usda_vision_system/camera/recorder.py
Normal file
915
camera-management-api/usda_vision_system/camera/recorder.py
Normal file
@@ -0,0 +1,915 @@
|
||||
"""
|
||||
Camera Recorder for the USDA Vision Camera System.
|
||||
|
||||
This module handles video recording from GigE cameras using the camera SDK library (mvsdk).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import cv2
|
||||
import numpy as np
|
||||
import contextlib
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk"))
|
||||
import mvsdk
|
||||
|
||||
from ..core.config import CameraConfig
|
||||
from ..core.state_manager import StateManager
|
||||
from ..core.events import EventSystem, publish_recording_started, publish_recording_stopped, publish_recording_error
|
||||
from ..core.timezone_utils import now_atlanta, format_filename_timestamp
|
||||
from .sdk_config import ensure_sdk_initialized
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_camera_errors():
|
||||
"""Context manager to temporarily suppress camera SDK error output"""
|
||||
# Save original file descriptors
|
||||
original_stderr = os.dup(2)
|
||||
original_stdout = os.dup(1)
|
||||
|
||||
try:
|
||||
# Redirect stderr and stdout to devnull
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(devnull, 2) # stderr
|
||||
os.dup2(devnull, 1) # stdout (in case SDK uses stdout)
|
||||
os.close(devnull)
|
||||
|
||||
yield
|
||||
|
||||
finally:
|
||||
# Restore original file descriptors
|
||||
os.dup2(original_stderr, 2)
|
||||
os.dup2(original_stdout, 1)
|
||||
os.close(original_stderr)
|
||||
os.close(original_stdout)
|
||||
|
||||
|
||||
class CameraRecorder:
|
||||
"""Handles video recording for a single camera"""
|
||||
|
||||
def __init__(self, camera_config: CameraConfig, device_info: Any, state_manager: StateManager, event_system: EventSystem, storage_manager=None):
|
||||
self.camera_config = camera_config
|
||||
self.device_info = device_info
|
||||
self.state_manager = state_manager
|
||||
self.event_system = event_system
|
||||
self.storage_manager = storage_manager
|
||||
self.logger = logging.getLogger(f"{__name__}.{camera_config.name}")
|
||||
|
||||
# Camera handle and properties
|
||||
self.hCamera: Optional[int] = None
|
||||
self.cap = None
|
||||
self.monoCamera = False
|
||||
self.frame_buffer = None
|
||||
self.frame_buffer_size = 0
|
||||
|
||||
# Recording state
|
||||
self.recording = False
|
||||
self.video_writer: Optional[cv2.VideoWriter] = None
|
||||
self.output_filename: Optional[str] = None
|
||||
self.frame_count = 0
|
||||
self.start_time: Optional[datetime] = None
|
||||
|
||||
# Threading
|
||||
self._recording_thread: Optional[threading.Thread] = None
|
||||
self._stop_recording_event = threading.Event()
|
||||
self._lock = threading.RLock()
|
||||
|
||||
# Don't initialize camera immediately - use lazy initialization
|
||||
# Camera will be initialized when recording starts
|
||||
self.logger.info(f"Camera recorder created for: {self.camera_config.name} (lazy initialization)")
|
||||
|
||||
def _initialize_camera(self) -> bool:
|
||||
"""Initialize the camera with configured settings"""
|
||||
try:
|
||||
self.logger.info(f"Initializing camera: {self.camera_config.name}")
|
||||
|
||||
# Ensure SDK is initialized
|
||||
ensure_sdk_initialized()
|
||||
|
||||
# Check if device_info is valid
|
||||
if self.device_info is None:
|
||||
self.logger.error("No device info provided for camera initialization")
|
||||
return False
|
||||
|
||||
# Initialize camera (suppress output to avoid MVCAMAPI error messages)
|
||||
with suppress_camera_errors():
|
||||
self.hCamera = mvsdk.CameraInit(self.device_info, -1, -1)
|
||||
self.logger.info("Camera initialized successfully")
|
||||
|
||||
# Get camera capabilities
|
||||
self.cap = mvsdk.CameraGetCapability(self.hCamera)
|
||||
self.monoCamera = self.cap.sIspCapacity.bMonoSensor != 0
|
||||
self.logger.info(f"Camera type: {'Monochrome' if self.monoCamera else 'Color'}")
|
||||
|
||||
# Set output format based on bit depth configuration
|
||||
if self.monoCamera:
|
||||
if self.camera_config.bit_depth == 16:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO16)
|
||||
elif self.camera_config.bit_depth == 12:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO12)
|
||||
elif self.camera_config.bit_depth == 10:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO10)
|
||||
else: # Default to 8-bit
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
||||
else:
|
||||
if self.camera_config.bit_depth == 16:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_RGB16)
|
||||
elif self.camera_config.bit_depth == 12:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR12)
|
||||
elif self.camera_config.bit_depth == 10:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR10)
|
||||
else: # Default to 8-bit
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
||||
|
||||
self.logger.info(f"Output format set to {self.camera_config.bit_depth}-bit {'mono' if self.monoCamera else 'color'}")
|
||||
|
||||
# Configure camera settings
|
||||
self._configure_camera_settings()
|
||||
|
||||
# Allocate frame buffer based on bit depth
|
||||
bytes_per_pixel = self._get_bytes_per_pixel()
|
||||
self.frame_buffer_size = self.cap.sResolutionRange.iWidthMax * self.cap.sResolutionRange.iHeightMax * bytes_per_pixel
|
||||
self.frame_buffer = mvsdk.CameraAlignMalloc(self.frame_buffer_size, 16)
|
||||
|
||||
# Start camera
|
||||
mvsdk.CameraPlay(self.hCamera)
|
||||
self.logger.info("Camera started successfully")
|
||||
|
||||
return True
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
error_msg = f"Camera initialization failed({e.error_code}): {e.message}"
|
||||
if e.error_code == 32774:
|
||||
error_msg += " - This may indicate the camera is already in use by another process or there's a resource conflict"
|
||||
self.logger.error(error_msg)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected error during camera initialization: {e}")
|
||||
return False
|
||||
|
||||
def _get_bytes_per_pixel(self) -> int:
|
||||
"""Calculate bytes per pixel based on camera type and bit depth"""
|
||||
if self.monoCamera:
|
||||
# Monochrome camera
|
||||
if self.camera_config.bit_depth >= 16:
|
||||
return 2 # 16-bit mono
|
||||
elif self.camera_config.bit_depth >= 12:
|
||||
return 2 # 12-bit mono (stored in 16-bit)
|
||||
elif self.camera_config.bit_depth >= 10:
|
||||
return 2 # 10-bit mono (stored in 16-bit)
|
||||
else:
|
||||
return 1 # 8-bit mono
|
||||
else:
|
||||
# Color camera
|
||||
if self.camera_config.bit_depth >= 16:
|
||||
return 6 # 16-bit RGB (2 bytes × 3 channels)
|
||||
elif self.camera_config.bit_depth >= 12:
|
||||
return 6 # 12-bit RGB (stored as 16-bit)
|
||||
elif self.camera_config.bit_depth >= 10:
|
||||
return 6 # 10-bit RGB (stored as 16-bit)
|
||||
else:
|
||||
return 3 # 8-bit RGB
|
||||
|
||||
def _configure_camera_settings(self) -> None:
|
||||
"""Configure camera settings from config"""
|
||||
try:
|
||||
# Set trigger mode (continuous acquisition)
|
||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||
|
||||
# Set manual exposure
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0) # Disable auto exposure
|
||||
exposure_us = int(self.camera_config.exposure_ms * 1000) # Convert ms to microseconds
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
|
||||
# Set analog gain
|
||||
gain_value = int(self.camera_config.gain * 100) # Convert to camera units
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
|
||||
# Configure image quality settings
|
||||
self._configure_image_quality()
|
||||
|
||||
# Configure noise reduction
|
||||
self._configure_noise_reduction()
|
||||
|
||||
# Configure color settings (for color cameras)
|
||||
if not self.monoCamera:
|
||||
self._configure_color_settings()
|
||||
|
||||
# Configure advanced settings
|
||||
self._configure_advanced_settings()
|
||||
|
||||
self.logger.info(f"Camera settings configured - Exposure: {exposure_us}μs, Gain: {gain_value}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring camera settings: {e}")
|
||||
|
||||
def _configure_image_quality(self) -> None:
|
||||
"""Configure image quality settings"""
|
||||
try:
|
||||
# Set sharpness (0-200, default 100)
|
||||
mvsdk.CameraSetSharpness(self.hCamera, self.camera_config.sharpness)
|
||||
|
||||
# Set contrast (0-200, default 100)
|
||||
mvsdk.CameraSetContrast(self.hCamera, self.camera_config.contrast)
|
||||
|
||||
# Set gamma (0-300, default 100)
|
||||
mvsdk.CameraSetGamma(self.hCamera, self.camera_config.gamma)
|
||||
|
||||
# Set saturation for color cameras (0-200, default 100)
|
||||
if not self.monoCamera:
|
||||
mvsdk.CameraSetSaturation(self.hCamera, self.camera_config.saturation)
|
||||
|
||||
self.logger.info(f"Image quality configured - Sharpness: {self.camera_config.sharpness}, " f"Contrast: {self.camera_config.contrast}, Gamma: {self.camera_config.gamma}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring image quality: {e}")
|
||||
|
||||
def _configure_noise_reduction(self) -> None:
|
||||
"""Configure noise reduction settings"""
|
||||
try:
|
||||
# Enable/disable basic noise filter
|
||||
mvsdk.CameraSetNoiseFilter(self.hCamera, self.camera_config.noise_filter_enabled)
|
||||
|
||||
# Configure 3D denoising if enabled
|
||||
if self.camera_config.denoise_3d_enabled:
|
||||
# Enable 3D denoising with default parameters (3 frames, equal weights)
|
||||
mvsdk.CameraSetDenoise3DParams(self.hCamera, True, 3, None)
|
||||
self.logger.info("3D denoising enabled")
|
||||
else:
|
||||
mvsdk.CameraSetDenoise3DParams(self.hCamera, False, 2, None)
|
||||
|
||||
self.logger.info(f"Noise reduction configured - Filter: {self.camera_config.noise_filter_enabled}, " f"3D Denoise: {self.camera_config.denoise_3d_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring noise reduction: {e}")
|
||||
|
||||
def _configure_color_settings(self) -> None:
|
||||
"""Configure color settings for color cameras"""
|
||||
try:
|
||||
# Set white balance mode
|
||||
mvsdk.CameraSetWbMode(self.hCamera, self.camera_config.auto_white_balance)
|
||||
|
||||
# Set color temperature preset if not using auto white balance
|
||||
if not self.camera_config.auto_white_balance:
|
||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, self.camera_config.color_temperature_preset)
|
||||
|
||||
# Set manual RGB gains for manual white balance
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100) # Convert to camera units
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
self.logger.info(f"Color settings configured - Auto WB: {self.camera_config.auto_white_balance}, " f"Color Temp Preset: {self.camera_config.color_temperature_preset}, " f"RGB Gains: R={self.camera_config.wb_red_gain}, G={self.camera_config.wb_green_gain}, B={self.camera_config.wb_blue_gain}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring color settings: {e}")
|
||||
|
||||
def _configure_advanced_settings(self) -> None:
|
||||
"""Configure advanced camera settings"""
|
||||
try:
|
||||
# Set anti-flicker
|
||||
mvsdk.CameraSetAntiFlick(self.hCamera, self.camera_config.anti_flicker_enabled)
|
||||
|
||||
# Set light frequency (0=50Hz, 1=60Hz)
|
||||
mvsdk.CameraSetLightFrequency(self.hCamera, self.camera_config.light_frequency)
|
||||
|
||||
# Configure HDR if enabled (check if HDR functions are available)
|
||||
try:
|
||||
if self.camera_config.hdr_enabled:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 1) # Enable HDR
|
||||
mvsdk.CameraSetHDRGainMode(self.hCamera, self.camera_config.hdr_gain_mode)
|
||||
self.logger.info(f"HDR enabled with gain mode: {self.camera_config.hdr_gain_mode}")
|
||||
else:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 0) # Disable HDR
|
||||
except AttributeError:
|
||||
self.logger.info("HDR functions not available in this SDK version, skipping HDR configuration")
|
||||
|
||||
self.logger.info(f"Advanced settings configured - Anti-flicker: {self.camera_config.anti_flicker_enabled}, " f"Light Freq: {self.camera_config.light_frequency}Hz, HDR: {self.camera_config.hdr_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring advanced settings: {e}")
|
||||
|
||||
def update_camera_settings(self, exposure_ms: Optional[float] = None, gain: Optional[float] = None, target_fps: Optional[float] = None) -> bool:
|
||||
"""Update camera settings dynamically"""
|
||||
if not self.hCamera:
|
||||
self.logger.error("Camera not initialized")
|
||||
return False
|
||||
|
||||
try:
|
||||
settings_updated = False
|
||||
|
||||
# Update exposure if provided
|
||||
if exposure_ms is not None:
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0) # Disable auto exposure
|
||||
exposure_us = int(exposure_ms * 1000) # Convert ms to microseconds
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
self.camera_config.exposure_ms = exposure_ms
|
||||
self.logger.info(f"Updated exposure time: {exposure_ms}ms")
|
||||
settings_updated = True
|
||||
|
||||
# Update gain if provided
|
||||
if gain is not None:
|
||||
gain_value = int(gain * 100) # Convert to camera units
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
self.camera_config.gain = gain
|
||||
self.logger.info(f"Updated gain: {gain}x")
|
||||
settings_updated = True
|
||||
|
||||
# Update target FPS if provided
|
||||
if target_fps is not None:
|
||||
self.camera_config.target_fps = target_fps
|
||||
self.logger.info(f"Updated target FPS: {target_fps}")
|
||||
settings_updated = True
|
||||
|
||||
return settings_updated
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error updating camera settings: {e}")
|
||||
return False
|
||||
|
||||
def update_advanced_camera_settings(self, **kwargs) -> bool:
|
||||
"""Update advanced camera settings dynamically"""
|
||||
if not self.hCamera:
|
||||
self.logger.error("Camera not initialized")
|
||||
return False
|
||||
|
||||
try:
|
||||
settings_updated = False
|
||||
|
||||
# Update basic settings
|
||||
if "exposure_ms" in kwargs and kwargs["exposure_ms"] is not None:
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0)
|
||||
exposure_us = int(kwargs["exposure_ms"] * 1000)
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
self.camera_config.exposure_ms = kwargs["exposure_ms"]
|
||||
settings_updated = True
|
||||
|
||||
if "gain" in kwargs and kwargs["gain"] is not None:
|
||||
gain_value = int(kwargs["gain"] * 100)
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
self.camera_config.gain = kwargs["gain"]
|
||||
settings_updated = True
|
||||
|
||||
if "target_fps" in kwargs and kwargs["target_fps"] is not None:
|
||||
self.camera_config.target_fps = kwargs["target_fps"]
|
||||
settings_updated = True
|
||||
|
||||
# Update image quality settings
|
||||
if "sharpness" in kwargs and kwargs["sharpness"] is not None:
|
||||
mvsdk.CameraSetSharpness(self.hCamera, kwargs["sharpness"])
|
||||
self.camera_config.sharpness = kwargs["sharpness"]
|
||||
settings_updated = True
|
||||
|
||||
if "contrast" in kwargs and kwargs["contrast"] is not None:
|
||||
mvsdk.CameraSetContrast(self.hCamera, kwargs["contrast"])
|
||||
self.camera_config.contrast = kwargs["contrast"]
|
||||
settings_updated = True
|
||||
|
||||
if "gamma" in kwargs and kwargs["gamma"] is not None:
|
||||
mvsdk.CameraSetGamma(self.hCamera, kwargs["gamma"])
|
||||
self.camera_config.gamma = kwargs["gamma"]
|
||||
settings_updated = True
|
||||
|
||||
if "saturation" in kwargs and kwargs["saturation"] is not None and not self.monoCamera:
|
||||
mvsdk.CameraSetSaturation(self.hCamera, kwargs["saturation"])
|
||||
self.camera_config.saturation = kwargs["saturation"]
|
||||
settings_updated = True
|
||||
|
||||
# Update noise reduction settings
|
||||
if "noise_filter_enabled" in kwargs and kwargs["noise_filter_enabled"] is not None:
|
||||
# Note: Noise filter settings may require camera restart to take effect
|
||||
self.camera_config.noise_filter_enabled = kwargs["noise_filter_enabled"]
|
||||
settings_updated = True
|
||||
|
||||
if "denoise_3d_enabled" in kwargs and kwargs["denoise_3d_enabled"] is not None:
|
||||
# Note: 3D denoise settings may require camera restart to take effect
|
||||
self.camera_config.denoise_3d_enabled = kwargs["denoise_3d_enabled"]
|
||||
settings_updated = True
|
||||
|
||||
# Update color settings (for color cameras)
|
||||
if not self.monoCamera:
|
||||
if "auto_white_balance" in kwargs and kwargs["auto_white_balance"] is not None:
|
||||
mvsdk.CameraSetWbMode(self.hCamera, kwargs["auto_white_balance"])
|
||||
self.camera_config.auto_white_balance = kwargs["auto_white_balance"]
|
||||
settings_updated = True
|
||||
|
||||
if "color_temperature_preset" in kwargs and kwargs["color_temperature_preset"] is not None:
|
||||
if not self.camera_config.auto_white_balance:
|
||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, kwargs["color_temperature_preset"])
|
||||
self.camera_config.color_temperature_preset = kwargs["color_temperature_preset"]
|
||||
settings_updated = True
|
||||
|
||||
# Update RGB gains for manual white balance
|
||||
rgb_gains_updated = False
|
||||
if "wb_red_gain" in kwargs and kwargs["wb_red_gain"] is not None:
|
||||
self.camera_config.wb_red_gain = kwargs["wb_red_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
if "wb_green_gain" in kwargs and kwargs["wb_green_gain"] is not None:
|
||||
self.camera_config.wb_green_gain = kwargs["wb_green_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
if "wb_blue_gain" in kwargs and kwargs["wb_blue_gain"] is not None:
|
||||
self.camera_config.wb_blue_gain = kwargs["wb_blue_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
# Apply RGB gains if any were updated and we're in manual white balance mode
|
||||
if rgb_gains_updated and not self.camera_config.auto_white_balance:
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100)
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
# Update advanced settings
|
||||
if "anti_flicker_enabled" in kwargs and kwargs["anti_flicker_enabled"] is not None:
|
||||
mvsdk.CameraSetAntiFlick(self.hCamera, kwargs["anti_flicker_enabled"])
|
||||
self.camera_config.anti_flicker_enabled = kwargs["anti_flicker_enabled"]
|
||||
settings_updated = True
|
||||
|
||||
if "light_frequency" in kwargs and kwargs["light_frequency"] is not None:
|
||||
mvsdk.CameraSetLightFrequency(self.hCamera, kwargs["light_frequency"])
|
||||
self.camera_config.light_frequency = kwargs["light_frequency"]
|
||||
settings_updated = True
|
||||
|
||||
# Update HDR settings (if supported)
|
||||
if "hdr_enabled" in kwargs and kwargs["hdr_enabled"] is not None:
|
||||
try:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 1 if kwargs["hdr_enabled"] else 0)
|
||||
self.camera_config.hdr_enabled = kwargs["hdr_enabled"]
|
||||
settings_updated = True
|
||||
except AttributeError:
|
||||
self.logger.warning("HDR functions not available in this SDK version")
|
||||
|
||||
if "hdr_gain_mode" in kwargs and kwargs["hdr_gain_mode"] is not None:
|
||||
try:
|
||||
if self.camera_config.hdr_enabled:
|
||||
mvsdk.CameraSetHDRGainMode(self.hCamera, kwargs["hdr_gain_mode"])
|
||||
self.camera_config.hdr_gain_mode = kwargs["hdr_gain_mode"]
|
||||
settings_updated = True
|
||||
except AttributeError:
|
||||
self.logger.warning("HDR gain mode functions not available in this SDK version")
|
||||
|
||||
if settings_updated:
|
||||
updated_settings = [k for k, v in kwargs.items() if v is not None]
|
||||
self.logger.info(f"Updated camera settings: {updated_settings}")
|
||||
|
||||
return settings_updated
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error updating advanced camera settings: {e}")
|
||||
return False
|
||||
|
||||
def start_recording(self, filename: str) -> bool:
|
||||
"""Start video recording"""
|
||||
with self._lock:
|
||||
if self.recording:
|
||||
self.logger.warning("Already recording!")
|
||||
return False
|
||||
|
||||
# Initialize camera if not already initialized (lazy initialization)
|
||||
if not self.hCamera:
|
||||
self.logger.info("Camera not initialized, initializing now...")
|
||||
if not self._initialize_camera():
|
||||
self.logger.error("Failed to initialize camera for recording")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Prepare output path
|
||||
output_path = os.path.join(self.camera_config.storage_path, filename)
|
||||
Path(self.camera_config.storage_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Test camera capture before starting recording
|
||||
if not self._test_camera_capture():
|
||||
self.logger.error("Camera capture test failed")
|
||||
return False
|
||||
|
||||
# Initialize recording state
|
||||
self.output_filename = output_path
|
||||
self.frame_count = 0
|
||||
self.start_time = now_atlanta() # Use Atlanta timezone
|
||||
self._stop_recording_event.clear()
|
||||
|
||||
# Start recording thread
|
||||
self._recording_thread = threading.Thread(target=self._recording_loop, daemon=True)
|
||||
self._recording_thread.start()
|
||||
|
||||
# Update state
|
||||
self.recording = True
|
||||
recording_id = self.state_manager.start_recording(self.camera_config.name, output_path)
|
||||
|
||||
# Publish event
|
||||
publish_recording_started(self.camera_config.name, output_path)
|
||||
|
||||
self.logger.info(f"Started recording to: {output_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error starting recording: {e}")
|
||||
publish_recording_error(self.camera_config.name, str(e))
|
||||
return False
|
||||
|
||||
def _test_camera_capture(self) -> bool:
|
||||
"""Test if camera can capture frames"""
|
||||
try:
|
||||
# Try to capture one frame
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 1000) # 1 second timeout
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Camera capture test failed: {e}")
|
||||
return False
|
||||
|
||||
def stop_recording(self) -> bool:
|
||||
"""Stop video recording"""
|
||||
with self._lock:
|
||||
if not self.recording:
|
||||
self.logger.warning("Not currently recording")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Signal recording thread to stop
|
||||
self._stop_recording_event.set()
|
||||
|
||||
# Wait for recording thread to finish
|
||||
if self._recording_thread and self._recording_thread.is_alive():
|
||||
self._recording_thread.join(timeout=5)
|
||||
|
||||
# Update state
|
||||
self.recording = False
|
||||
|
||||
# Calculate duration and file size
|
||||
duration = 0
|
||||
file_size = 0
|
||||
if self.start_time:
|
||||
duration = (now_atlanta() - self.start_time).total_seconds()
|
||||
|
||||
if self.output_filename and os.path.exists(self.output_filename):
|
||||
file_size = os.path.getsize(self.output_filename)
|
||||
|
||||
# Update state manager
|
||||
if self.output_filename:
|
||||
self.state_manager.stop_recording(self.output_filename, file_size, self.frame_count)
|
||||
|
||||
# Publish event
|
||||
publish_recording_stopped(self.camera_config.name, self.output_filename or "unknown", duration)
|
||||
|
||||
# Clean up camera resources after recording (lazy cleanup)
|
||||
self._cleanup_camera()
|
||||
self.logger.info("Camera resources cleaned up after recording")
|
||||
|
||||
self.logger.info(f"Stopped recording - Duration: {duration:.1f}s, Frames: {self.frame_count}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error stopping recording: {e}")
|
||||
return False
|
||||
|
||||
def _recording_loop(self) -> None:
|
||||
"""Main recording loop running in separate thread"""
|
||||
try:
|
||||
# Initialize video writer
|
||||
if not self._initialize_video_writer():
|
||||
self.logger.error("Failed to initialize video writer")
|
||||
return
|
||||
|
||||
self.logger.info("Recording loop started")
|
||||
|
||||
while not self._stop_recording_event.is_set():
|
||||
try:
|
||||
# Capture frame
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 200) # 200ms timeout
|
||||
|
||||
# Process frame
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
|
||||
# Convert to OpenCV format
|
||||
frame = self._convert_frame_to_opencv(FrameHead)
|
||||
|
||||
# Write frame to video
|
||||
if frame is not None and self.video_writer:
|
||||
self.video_writer.write(frame)
|
||||
self.frame_count += 1
|
||||
|
||||
# Release buffer
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
|
||||
# Control frame rate (skip sleep if target_fps is 0 for maximum speed)
|
||||
if self.camera_config.target_fps > 0:
|
||||
time.sleep(1.0 / self.camera_config.target_fps)
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
if e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT:
|
||||
continue # Timeout is normal, continue
|
||||
else:
|
||||
self.logger.error(f"Camera error during recording: {e.message}")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in recording loop: {e}")
|
||||
break
|
||||
|
||||
self.logger.info("Recording loop ended")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fatal error in recording loop: {e}")
|
||||
publish_recording_error(self.camera_config.name, str(e))
|
||||
finally:
|
||||
self._cleanup_recording()
|
||||
|
||||
def _initialize_video_writer(self) -> bool:
|
||||
"""Initialize OpenCV video writer"""
|
||||
try:
|
||||
# Get frame dimensions by capturing a test frame
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 1000)
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
|
||||
# Set up video writer with configured codec
|
||||
fourcc = cv2.VideoWriter_fourcc(*self.camera_config.video_codec)
|
||||
frame_size = (FrameHead.iWidth, FrameHead.iHeight)
|
||||
|
||||
# Use 30 FPS for video writer if target_fps is 0 (unlimited)
|
||||
video_fps = self.camera_config.target_fps if self.camera_config.target_fps > 0 else 30.0
|
||||
|
||||
# Create video writer with quality settings
|
||||
self.video_writer = cv2.VideoWriter(self.output_filename, fourcc, video_fps, frame_size)
|
||||
|
||||
# Set quality if supported (for some codecs)
|
||||
if hasattr(self.video_writer, "set") and self.camera_config.video_quality:
|
||||
try:
|
||||
self.video_writer.set(cv2.VIDEOWRITER_PROP_QUALITY, self.camera_config.video_quality)
|
||||
except:
|
||||
pass # Quality setting not supported for this codec
|
||||
|
||||
if not self.video_writer.isOpened():
|
||||
self.logger.error(f"Failed to open video writer for {self.output_filename}")
|
||||
return False
|
||||
|
||||
self.logger.info(f"Video writer initialized - Size: {frame_size}, FPS: {self.camera_config.target_fps}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing video writer: {e}")
|
||||
return False
|
||||
|
||||
def _convert_frame_to_opencv(self, frame_head) -> Optional[np.ndarray]:
|
||||
"""Convert camera frame to OpenCV format"""
|
||||
try:
|
||||
# Convert the frame buffer memory address to a proper buffer
|
||||
# that numpy can work with using mvsdk.c_ubyte
|
||||
frame_data_buffer = (mvsdk.c_ubyte * frame_head.uBytes).from_address(self.frame_buffer)
|
||||
|
||||
# Handle different bit depths
|
||||
if self.camera_config.bit_depth > 8:
|
||||
# For >8-bit, data is stored as 16-bit values
|
||||
frame_data = np.frombuffer(frame_data_buffer, dtype=np.uint16)
|
||||
|
||||
if self.monoCamera:
|
||||
# Monochrome camera - convert to 8-bit BGR for video
|
||||
frame = frame_data.reshape((frame_head.iHeight, frame_head.iWidth))
|
||||
# Scale down to 8-bit (simple right shift)
|
||||
frame_8bit = (frame >> (self.camera_config.bit_depth - 8)).astype(np.uint8)
|
||||
frame_bgr = cv2.cvtColor(frame_8bit, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
# Color camera - convert to 8-bit BGR
|
||||
frame = frame_data.reshape((frame_head.iHeight, frame_head.iWidth, 3))
|
||||
# Scale down to 8-bit
|
||||
frame_bgr = (frame >> (self.camera_config.bit_depth - 8)).astype(np.uint8)
|
||||
else:
|
||||
# 8-bit data
|
||||
frame_data = np.frombuffer(frame_data_buffer, dtype=np.uint8)
|
||||
|
||||
if self.monoCamera:
|
||||
# Monochrome camera - convert to BGR
|
||||
frame = frame_data.reshape((frame_head.iHeight, frame_head.iWidth))
|
||||
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
# Color camera - already in BGR format
|
||||
frame_bgr = frame_data.reshape((frame_head.iHeight, frame_head.iWidth, 3))
|
||||
|
||||
return frame_bgr
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error converting frame: {e}")
|
||||
return None
|
||||
|
||||
def _cleanup_recording(self) -> None:
|
||||
"""Clean up recording resources"""
|
||||
try:
|
||||
if self.video_writer:
|
||||
self.video_writer.release()
|
||||
self.video_writer = None
|
||||
|
||||
self.recording = False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during recording cleanup: {e}")
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""Test camera connection"""
|
||||
try:
|
||||
if self.hCamera is None:
|
||||
self.logger.error("Camera not initialized")
|
||||
return False
|
||||
|
||||
# Test connection using SDK function
|
||||
result = mvsdk.CameraConnectTest(self.hCamera)
|
||||
if result == 0: # CAMERA_STATUS_SUCCESS
|
||||
self.logger.info("Camera connection test passed")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Camera connection test failed with code: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error testing camera connection: {e}")
|
||||
return False
|
||||
|
||||
def reconnect(self) -> bool:
|
||||
"""Attempt to reconnect to the camera"""
|
||||
try:
|
||||
if self.hCamera is None:
|
||||
self.logger.error("Camera not initialized, cannot reconnect")
|
||||
return False
|
||||
|
||||
self.logger.info("Attempting to reconnect camera...")
|
||||
|
||||
# Stop any ongoing operations
|
||||
if self.recording:
|
||||
self.logger.info("Stopping recording before reconnect")
|
||||
self.stop_recording()
|
||||
|
||||
# Attempt reconnection using SDK function
|
||||
result = mvsdk.CameraReConnect(self.hCamera)
|
||||
if result == 0: # CAMERA_STATUS_SUCCESS
|
||||
self.logger.info("Camera reconnected successfully")
|
||||
|
||||
# Restart camera if it was playing
|
||||
try:
|
||||
mvsdk.CameraPlay(self.hCamera)
|
||||
self.logger.info("Camera restarted after reconnection")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to restart camera after reconnection: {e}")
|
||||
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Camera reconnection failed with code: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during camera reconnection: {e}")
|
||||
return False
|
||||
|
||||
def restart_grab(self) -> bool:
|
||||
"""Restart the camera grab process"""
|
||||
try:
|
||||
if self.hCamera is None:
|
||||
self.logger.error("Camera not initialized")
|
||||
return False
|
||||
|
||||
self.logger.info("Restarting camera grab process...")
|
||||
|
||||
# Stop any ongoing recording
|
||||
if self.recording:
|
||||
self.logger.info("Stopping recording before restart")
|
||||
self.stop_recording()
|
||||
|
||||
# Restart grab using SDK function
|
||||
result = mvsdk.CameraRestartGrab(self.hCamera)
|
||||
if result == 0: # CAMERA_STATUS_SUCCESS
|
||||
self.logger.info("Camera grab restarted successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Camera grab restart failed with code: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error restarting camera grab: {e}")
|
||||
return False
|
||||
|
||||
def reset_timestamp(self) -> bool:
|
||||
"""Reset camera timestamp"""
|
||||
try:
|
||||
if self.hCamera is None:
|
||||
self.logger.error("Camera not initialized")
|
||||
return False
|
||||
|
||||
self.logger.info("Resetting camera timestamp...")
|
||||
|
||||
result = mvsdk.CameraRstTimeStamp(self.hCamera)
|
||||
if result == 0: # CAMERA_STATUS_SUCCESS
|
||||
self.logger.info("Camera timestamp reset successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Camera timestamp reset failed with code: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error resetting camera timestamp: {e}")
|
||||
return False
|
||||
|
||||
def full_reset(self) -> bool:
|
||||
"""Perform a full camera reset (uninitialize and reinitialize)"""
|
||||
try:
|
||||
self.logger.info("Performing full camera reset...")
|
||||
|
||||
# Stop any ongoing recording
|
||||
if self.recording:
|
||||
self.logger.info("Stopping recording before reset")
|
||||
self.stop_recording()
|
||||
|
||||
# Store device info for reinitialization
|
||||
device_info = self.device_info
|
||||
|
||||
# Cleanup current camera
|
||||
self._cleanup_camera()
|
||||
|
||||
# Wait a moment
|
||||
time.sleep(1)
|
||||
|
||||
# Reinitialize camera
|
||||
self.device_info = device_info
|
||||
success = self._initialize_camera()
|
||||
|
||||
if success:
|
||||
self.logger.info("Full camera reset completed successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error("Full camera reset failed during reinitialization")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during full camera reset: {e}")
|
||||
return False
|
||||
|
||||
def _cleanup_camera(self) -> None:
|
||||
"""Clean up camera resources"""
|
||||
try:
|
||||
# Stop camera if running
|
||||
if self.hCamera is not None:
|
||||
try:
|
||||
mvsdk.CameraStop(self.hCamera)
|
||||
except:
|
||||
pass # Ignore errors during stop
|
||||
|
||||
# Uninitialize camera
|
||||
try:
|
||||
mvsdk.CameraUnInit(self.hCamera)
|
||||
except:
|
||||
pass # Ignore errors during uninit
|
||||
|
||||
self.hCamera = None
|
||||
|
||||
# Free frame buffer
|
||||
if self.frame_buffer is not None:
|
||||
try:
|
||||
mvsdk.CameraAlignFree(self.frame_buffer)
|
||||
except:
|
||||
pass # Ignore errors during free
|
||||
|
||||
self.frame_buffer = None
|
||||
|
||||
self.logger.info("Camera resources cleaned up")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during camera cleanup: {e}")
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Clean up camera resources"""
|
||||
try:
|
||||
# Stop recording if active
|
||||
if self.recording:
|
||||
self.stop_recording()
|
||||
|
||||
# Clean up camera
|
||||
if self.hCamera:
|
||||
mvsdk.CameraUnInit(self.hCamera)
|
||||
self.hCamera = None
|
||||
|
||||
# Free frame buffer
|
||||
if self.frame_buffer:
|
||||
mvsdk.CameraAlignFree(self.frame_buffer)
|
||||
self.frame_buffer = None
|
||||
|
||||
self.logger.info("Camera resources cleaned up")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during cleanup: {e}")
|
||||
|
||||
def is_recording(self) -> bool:
|
||||
"""Check if currently recording"""
|
||||
return self.recording
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""Get recorder status"""
|
||||
return {"camera_name": self.camera_config.name, "is_recording": self.recording, "current_file": self.output_filename, "frame_count": self.frame_count, "start_time": self.start_time.isoformat() if self.start_time else None, "camera_initialized": self.hCamera is not None, "storage_path": self.camera_config.storage_path}
|
||||
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
SDK Configuration for the USDA Vision Camera System.
|
||||
|
||||
This module handles SDK initialization and configuration to suppress error messages.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk"))
|
||||
import mvsdk
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Global flag to track SDK initialization
|
||||
_sdk_initialized = False
|
||||
|
||||
|
||||
def initialize_sdk_with_suppression():
|
||||
"""Initialize the camera SDK with error suppression"""
|
||||
global _sdk_initialized
|
||||
|
||||
if _sdk_initialized:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Initialize SDK with English language
|
||||
result = mvsdk.CameraSdkInit(1)
|
||||
if result == 0:
|
||||
logger.info("Camera SDK initialized successfully")
|
||||
|
||||
# Try to set system options to suppress logging
|
||||
try:
|
||||
# These are common options that might control logging
|
||||
# We'll try them and ignore failures since they might not be supported
|
||||
|
||||
# Try to disable debug output
|
||||
try:
|
||||
mvsdk.CameraSetSysOption("DebugLevel", "0")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try to disable console output
|
||||
try:
|
||||
mvsdk.CameraSetSysOption("ConsoleOutput", "0")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try to disable error logging
|
||||
try:
|
||||
mvsdk.CameraSetSysOption("ErrorLog", "0")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try to set log level to none
|
||||
try:
|
||||
mvsdk.CameraSetSysOption("LogLevel", "0")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try to disable verbose mode
|
||||
try:
|
||||
mvsdk.CameraSetSysOption("Verbose", "0")
|
||||
except:
|
||||
pass
|
||||
|
||||
logger.debug("Attempted to configure SDK logging options")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not configure SDK logging options: {e}")
|
||||
|
||||
_sdk_initialized = True
|
||||
return True
|
||||
else:
|
||||
logger.error(f"SDK initialization failed with code: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"SDK initialization failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def ensure_sdk_initialized():
|
||||
"""Ensure the SDK is initialized before camera operations"""
|
||||
if not _sdk_initialized:
|
||||
return initialize_sdk_with_suppression()
|
||||
return True
|
||||
412
camera-management-api/usda_vision_system/camera/streamer.py
Normal file
412
camera-management-api/usda_vision_system/camera/streamer.py
Normal file
@@ -0,0 +1,412 @@
|
||||
"""
|
||||
Camera Streamer for the USDA Vision Camera System.
|
||||
|
||||
This module provides live preview streaming from GigE cameras without blocking recording.
|
||||
It creates a separate camera connection for streaming that doesn't interfere with recording.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import cv2
|
||||
import numpy as np
|
||||
import contextlib
|
||||
from typing import Optional, Dict, Any, Generator
|
||||
from datetime import datetime
|
||||
import queue
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "camera_sdk"))
|
||||
import mvsdk
|
||||
|
||||
from ..core.config import CameraConfig
|
||||
from ..core.state_manager import StateManager
|
||||
from ..core.events import EventSystem
|
||||
from .sdk_config import ensure_sdk_initialized
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_camera_errors():
|
||||
"""Context manager to temporarily suppress camera SDK error output"""
|
||||
# Save original file descriptors
|
||||
original_stderr = os.dup(2)
|
||||
original_stdout = os.dup(1)
|
||||
|
||||
try:
|
||||
# Redirect stderr and stdout to devnull
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(devnull, 2) # stderr
|
||||
os.dup2(devnull, 1) # stdout (in case SDK uses stdout)
|
||||
os.close(devnull)
|
||||
|
||||
yield
|
||||
|
||||
finally:
|
||||
# Restore original file descriptors
|
||||
os.dup2(original_stderr, 2)
|
||||
os.dup2(original_stdout, 1)
|
||||
os.close(original_stderr)
|
||||
os.close(original_stdout)
|
||||
|
||||
|
||||
class CameraStreamer:
|
||||
"""Provides live preview streaming from cameras without blocking recording"""
|
||||
|
||||
def __init__(self, camera_config: CameraConfig, device_info: Any, state_manager: StateManager, event_system: EventSystem):
|
||||
self.camera_config = camera_config
|
||||
self.device_info = device_info
|
||||
self.state_manager = state_manager
|
||||
self.event_system = event_system
|
||||
self.logger = logging.getLogger(f"{__name__}.{camera_config.name}")
|
||||
|
||||
# Camera handle and properties (separate from recorder)
|
||||
self.hCamera: Optional[int] = None
|
||||
self.cap = None
|
||||
self.monoCamera = False
|
||||
self.frame_buffer = None
|
||||
self.frame_buffer_size = 0
|
||||
|
||||
# Streaming state
|
||||
self.streaming = False
|
||||
self._streaming_thread: Optional[threading.Thread] = None
|
||||
self._stop_streaming_event = threading.Event()
|
||||
self._frame_queue = queue.Queue(maxsize=5) # Buffer for latest frames
|
||||
self._lock = threading.RLock()
|
||||
|
||||
# Stream settings (optimized for preview)
|
||||
self.preview_fps = 10.0 # Lower FPS for preview to reduce load
|
||||
self.preview_quality = 70 # JPEG quality for streaming
|
||||
|
||||
def start_streaming(self) -> bool:
|
||||
"""Start streaming preview frames"""
|
||||
with self._lock:
|
||||
if self.streaming:
|
||||
self.logger.warning("Streaming already active")
|
||||
return True
|
||||
|
||||
try:
|
||||
# Initialize camera for streaming
|
||||
if not self._initialize_camera():
|
||||
return False
|
||||
|
||||
# Start streaming thread
|
||||
self._stop_streaming_event.clear()
|
||||
self._streaming_thread = threading.Thread(target=self._streaming_loop, daemon=True)
|
||||
self._streaming_thread.start()
|
||||
|
||||
self.streaming = True
|
||||
self.logger.info(f"Started streaming for camera: {self.camera_config.name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error starting streaming: {e}")
|
||||
self._cleanup_camera()
|
||||
return False
|
||||
|
||||
def stop_streaming(self) -> bool:
|
||||
"""Stop streaming preview frames"""
|
||||
with self._lock:
|
||||
if not self.streaming:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Signal streaming thread to stop
|
||||
self._stop_streaming_event.set()
|
||||
|
||||
# Wait for thread to finish
|
||||
if self._streaming_thread and self._streaming_thread.is_alive():
|
||||
self._streaming_thread.join(timeout=5.0)
|
||||
|
||||
# Cleanup camera resources
|
||||
self._cleanup_camera()
|
||||
|
||||
self.streaming = False
|
||||
self.logger.info(f"Stopped streaming for camera: {self.camera_config.name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error stopping streaming: {e}")
|
||||
return False
|
||||
|
||||
def get_latest_frame(self) -> Optional[bytes]:
|
||||
"""Get the latest frame as JPEG bytes for streaming"""
|
||||
try:
|
||||
# Get latest frame from queue (non-blocking)
|
||||
frame = self._frame_queue.get_nowait()
|
||||
|
||||
# Encode as JPEG
|
||||
_, buffer = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, self.preview_quality])
|
||||
return buffer.tobytes()
|
||||
|
||||
except queue.Empty:
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting latest frame: {e}")
|
||||
return None
|
||||
|
||||
def get_frame_generator(self) -> Generator[bytes, None, None]:
|
||||
"""Generator for MJPEG streaming"""
|
||||
while self.streaming:
|
||||
frame_bytes = self.get_latest_frame()
|
||||
if frame_bytes:
|
||||
yield (b"--frame\r\n" b"Content-Type: image/jpeg\r\n\r\n" + frame_bytes + b"\r\n")
|
||||
else:
|
||||
time.sleep(0.1) # Wait a bit if no frame available
|
||||
|
||||
def _initialize_camera(self) -> bool:
|
||||
"""Initialize camera for streaming (separate from recording)"""
|
||||
try:
|
||||
self.logger.info(f"Initializing camera for streaming: {self.camera_config.name}")
|
||||
|
||||
# Ensure SDK is initialized
|
||||
ensure_sdk_initialized()
|
||||
|
||||
# Check if device_info is valid
|
||||
if self.device_info is None:
|
||||
self.logger.error("No device info provided for camera initialization")
|
||||
return False
|
||||
|
||||
# Initialize camera (suppress output to avoid MVCAMAPI error messages)
|
||||
with suppress_camera_errors():
|
||||
self.hCamera = mvsdk.CameraInit(self.device_info, -1, -1)
|
||||
self.logger.info("Camera initialized successfully for streaming")
|
||||
|
||||
# Get camera capabilities
|
||||
self.cap = mvsdk.CameraGetCapability(self.hCamera)
|
||||
|
||||
# Determine if camera is monochrome
|
||||
self.monoCamera = self.cap.sIspCapacity.bMonoSensor != 0
|
||||
|
||||
# Set output format based on camera type and bit depth
|
||||
if self.monoCamera:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
||||
else:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
||||
|
||||
# Configure camera settings for streaming (optimized for preview)
|
||||
self._configure_streaming_settings()
|
||||
|
||||
# Allocate frame buffer
|
||||
bytes_per_pixel = 1 if self.monoCamera else 3
|
||||
self.frame_buffer_size = self.cap.sResolutionRange.iWidthMax * self.cap.sResolutionRange.iHeightMax * bytes_per_pixel
|
||||
self.frame_buffer = mvsdk.CameraAlignMalloc(self.frame_buffer_size, 16)
|
||||
|
||||
# Start camera
|
||||
mvsdk.CameraPlay(self.hCamera)
|
||||
self.logger.info("Camera started successfully for streaming")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing camera for streaming: {e}")
|
||||
self._cleanup_camera()
|
||||
return False
|
||||
|
||||
def _configure_streaming_settings(self):
|
||||
"""Configure camera settings from config.json for streaming"""
|
||||
try:
|
||||
# Set trigger mode to free run for continuous streaming
|
||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||
|
||||
# Set manual exposure
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0) # Disable auto exposure
|
||||
exposure_us = int(self.camera_config.exposure_ms * 1000) # Convert ms to microseconds
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
|
||||
# Set analog gain
|
||||
gain_value = int(self.camera_config.gain * 100) # Convert to camera units
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
|
||||
# Set frame rate for streaming (lower than recording)
|
||||
if hasattr(mvsdk, "CameraSetFrameSpeed"):
|
||||
mvsdk.CameraSetFrameSpeed(self.hCamera, int(self.preview_fps))
|
||||
|
||||
# Configure image quality settings
|
||||
self._configure_image_quality()
|
||||
|
||||
# Configure noise reduction
|
||||
self._configure_noise_reduction()
|
||||
|
||||
# Configure color settings (for color cameras)
|
||||
if not self.monoCamera:
|
||||
self._configure_color_settings()
|
||||
|
||||
# Configure advanced settings
|
||||
self._configure_advanced_settings()
|
||||
|
||||
self.logger.info(f"Streaming settings configured: exposure={self.camera_config.exposure_ms}ms, gain={self.camera_config.gain}, fps={self.preview_fps}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not configure some streaming settings: {e}")
|
||||
|
||||
def _streaming_loop(self):
|
||||
"""Main streaming loop that captures frames continuously"""
|
||||
self.logger.info("Starting streaming loop")
|
||||
|
||||
try:
|
||||
while not self._stop_streaming_event.is_set():
|
||||
try:
|
||||
# Capture frame with timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 200) # 200ms timeout
|
||||
|
||||
# Process frame
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
|
||||
# Convert to OpenCV format
|
||||
frame = self._convert_frame_to_opencv(FrameHead)
|
||||
|
||||
if frame is not None:
|
||||
# Add frame to queue (replace oldest if queue is full)
|
||||
try:
|
||||
self._frame_queue.put_nowait(frame)
|
||||
except queue.Full:
|
||||
# Remove oldest frame and add new one
|
||||
try:
|
||||
self._frame_queue.get_nowait()
|
||||
self._frame_queue.put_nowait(frame)
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# Release buffer
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
|
||||
# Control frame rate
|
||||
time.sleep(1.0 / self.preview_fps)
|
||||
|
||||
except Exception as e:
|
||||
if not self._stop_streaming_event.is_set():
|
||||
self.logger.error(f"Error in streaming loop: {e}")
|
||||
time.sleep(0.1) # Brief pause before retrying
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fatal error in streaming loop: {e}")
|
||||
finally:
|
||||
self.logger.info("Streaming loop ended")
|
||||
|
||||
def _convert_frame_to_opencv(self, FrameHead) -> Optional[np.ndarray]:
|
||||
"""Convert camera frame to OpenCV format"""
|
||||
try:
|
||||
# Convert the frame buffer memory address to a proper buffer
|
||||
# that numpy can work with using mvsdk.c_ubyte
|
||||
frame_data_buffer = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.frame_buffer)
|
||||
|
||||
if self.monoCamera:
|
||||
# Monochrome camera
|
||||
frame_data = np.frombuffer(frame_data_buffer, dtype=np.uint8)
|
||||
frame = frame_data.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
# Convert to 3-channel for consistency
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
# Color camera (BGR format)
|
||||
frame_data = np.frombuffer(frame_data_buffer, dtype=np.uint8)
|
||||
frame = frame_data.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
|
||||
return frame
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error converting frame: {e}")
|
||||
return None
|
||||
|
||||
def _cleanup_camera(self):
|
||||
"""Clean up camera resources"""
|
||||
try:
|
||||
if self.frame_buffer:
|
||||
mvsdk.CameraAlignFree(self.frame_buffer)
|
||||
self.frame_buffer = None
|
||||
|
||||
if self.hCamera is not None:
|
||||
mvsdk.CameraUnInit(self.hCamera)
|
||||
self.hCamera = None
|
||||
|
||||
self.logger.info("Camera resources cleaned up for streaming")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error cleaning up camera resources: {e}")
|
||||
|
||||
def is_streaming(self) -> bool:
|
||||
"""Check if streaming is active"""
|
||||
return self.streaming
|
||||
|
||||
def _configure_image_quality(self) -> None:
|
||||
"""Configure image quality settings"""
|
||||
try:
|
||||
# Set sharpness (0-200, default 100)
|
||||
mvsdk.CameraSetSharpness(self.hCamera, self.camera_config.sharpness)
|
||||
|
||||
# Set contrast (0-200, default 100)
|
||||
mvsdk.CameraSetContrast(self.hCamera, self.camera_config.contrast)
|
||||
|
||||
# Set gamma (0-300, default 100)
|
||||
mvsdk.CameraSetGamma(self.hCamera, self.camera_config.gamma)
|
||||
|
||||
# Set saturation for color cameras (0-200, default 100)
|
||||
if not self.monoCamera:
|
||||
mvsdk.CameraSetSaturation(self.hCamera, self.camera_config.saturation)
|
||||
|
||||
self.logger.info(f"Image quality configured - Sharpness: {self.camera_config.sharpness}, " f"Contrast: {self.camera_config.contrast}, Gamma: {self.camera_config.gamma}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring image quality: {e}")
|
||||
|
||||
def _configure_noise_reduction(self) -> None:
|
||||
"""Configure noise reduction settings"""
|
||||
try:
|
||||
# Note: Some noise reduction settings may require specific SDK functions
|
||||
# that might not be available in all SDK versions
|
||||
self.logger.info(f"Noise reduction configured - Filter: {self.camera_config.noise_filter_enabled}, " f"3D Denoise: {self.camera_config.denoise_3d_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring noise reduction: {e}")
|
||||
|
||||
def _configure_color_settings(self) -> None:
|
||||
"""Configure color settings for color cameras"""
|
||||
try:
|
||||
# Set white balance mode
|
||||
mvsdk.CameraSetWbMode(self.hCamera, self.camera_config.auto_white_balance)
|
||||
|
||||
# Set color temperature preset if not using auto white balance
|
||||
if not self.camera_config.auto_white_balance:
|
||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, self.camera_config.color_temperature_preset)
|
||||
|
||||
# Set manual RGB gains for manual white balance
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100) # Convert to camera units
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
self.logger.info(f"Color settings configured - Auto WB: {self.camera_config.auto_white_balance}, " f"Color Temp Preset: {self.camera_config.color_temperature_preset}, " f"RGB Gains: R={self.camera_config.wb_red_gain}, G={self.camera_config.wb_green_gain}, B={self.camera_config.wb_blue_gain}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring color settings: {e}")
|
||||
|
||||
def _configure_advanced_settings(self) -> None:
|
||||
"""Configure advanced camera settings"""
|
||||
try:
|
||||
# Set anti-flicker
|
||||
mvsdk.CameraSetAntiFlick(self.hCamera, self.camera_config.anti_flicker_enabled)
|
||||
|
||||
# Set light frequency (0=50Hz, 1=60Hz)
|
||||
mvsdk.CameraSetLightFrequency(self.hCamera, self.camera_config.light_frequency)
|
||||
|
||||
# Configure HDR if enabled (check if HDR functions are available)
|
||||
try:
|
||||
if self.camera_config.hdr_enabled:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 1) # Enable HDR
|
||||
mvsdk.CameraSetHDRGainMode(self.hCamera, self.camera_config.hdr_gain_mode)
|
||||
self.logger.info(f"HDR enabled with gain mode: {self.camera_config.hdr_gain_mode}")
|
||||
else:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 0) # Disable HDR
|
||||
except AttributeError:
|
||||
self.logger.info("HDR functions not available in this SDK version, skipping HDR configuration")
|
||||
|
||||
self.logger.info(f"Advanced settings configured - Anti-flicker: {self.camera_config.anti_flicker_enabled}, " f"Light Freq: {self.camera_config.light_frequency}Hz, HDR: {self.camera_config.hdr_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring advanced settings: {e}")
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor to ensure cleanup"""
|
||||
if self.streaming:
|
||||
self.stop_streaming()
|
||||
Reference in New Issue
Block a user