Refactor API route setup and enhance modularity
- Consolidated API route definitions by registering routes from separate modules for better organization and maintainability. - Removed redundant route definitions from the APIServer class, improving code clarity. - Updated camera monitoring and recording modules to utilize a shared context manager for suppressing camera SDK errors, enhancing error handling. - Adjusted timeout settings in camera operations for improved reliability during frame capture. - Enhanced logging and error handling across camera operations to facilitate better debugging and monitoring.
This commit is contained in:
30
camera-management-api/usda_vision_system/camera/constants.py
Normal file
30
camera-management-api/usda_vision_system/camera/constants.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Constants for camera operations.
|
||||
"""
|
||||
|
||||
# Timeouts (milliseconds)
|
||||
CAMERA_GET_BUFFER_TIMEOUT = 200 # Standard frame capture timeout
|
||||
CAMERA_INIT_TIMEOUT = 1000 # Camera initialization timeout
|
||||
CAMERA_TEST_CAPTURE_TIMEOUT = 1000 # Test capture timeout
|
||||
CAMERA_GET_BUFFER_SHORT_TIMEOUT = 100 # Shorter timeout for quick checks
|
||||
|
||||
# Frame queue sizes
|
||||
MJPEG_QUEUE_MAXSIZE = 5 # Buffer for latest frames (for MJPEG streaming)
|
||||
RTSP_QUEUE_MAXSIZE = 10 # Buffer for RTSP frames (larger buffer for smoother streaming)
|
||||
RECORDING_QUEUE_MAXSIZE = 30 # Buffer for recording frames (shared with recorder)
|
||||
|
||||
# Frame rates (FPS)
|
||||
PREVIEW_FPS = 10.0 # Lower FPS for preview to reduce load
|
||||
RTSP_FPS = 15.0 # RTSP FPS (can be higher than MJPEG preview)
|
||||
DEFAULT_VIDEO_FPS = 30.0 # Default video FPS when target_fps is 0 or unspecified
|
||||
|
||||
# Sleep intervals (seconds)
|
||||
STREAMING_LOOP_SLEEP = 0.1 # Sleep interval in streaming loops when waiting
|
||||
BRIEF_PAUSE_SLEEP = 0.1 # Brief pause before retrying operations
|
||||
|
||||
# JPEG quality (0-100)
|
||||
PREVIEW_JPEG_QUALITY = 70 # JPEG quality for streaming preview
|
||||
|
||||
# Video writer buffer size
|
||||
VIDEO_WRITER_CHUNK_SIZE = 8192 # Buffer size for video writer operations
|
||||
|
||||
@@ -9,7 +9,6 @@ import os
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import contextlib
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
# Add camera SDK to path
|
||||
@@ -20,30 +19,8 @@ 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)
|
||||
from .utils import suppress_camera_errors
|
||||
from .constants import CAMERA_TEST_CAPTURE_TIMEOUT
|
||||
|
||||
|
||||
class CameraMonitor:
|
||||
@@ -219,7 +196,7 @@ class CameraMonitor:
|
||||
mvsdk.CameraPlay(hCamera)
|
||||
|
||||
# Try to capture with short timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, 500)
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, CAMERA_TEST_CAPTURE_TIMEOUT)
|
||||
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
|
||||
|
||||
# Success - camera is available
|
||||
|
||||
@@ -11,7 +11,6 @@ import time
|
||||
import logging
|
||||
import cv2
|
||||
import numpy as np
|
||||
import contextlib
|
||||
import queue
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
@@ -26,30 +25,7 @@ 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)
|
||||
from .utils import suppress_camera_errors
|
||||
|
||||
|
||||
class CameraRecorder:
|
||||
@@ -537,7 +513,7 @@ class CameraRecorder:
|
||||
"""Test if camera can capture frames"""
|
||||
try:
|
||||
# Try to capture one frame
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 1000) # 1 second timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_TEST_CAPTURE_TIMEOUT)
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
return True
|
||||
@@ -686,7 +662,7 @@ class CameraRecorder:
|
||||
continue
|
||||
else:
|
||||
# Capture frame directly from camera
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 200) # 200ms timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_GET_BUFFER_TIMEOUT)
|
||||
|
||||
# Process frame
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
@@ -770,7 +746,7 @@ class CameraRecorder:
|
||||
self.logger.info(f"Using frame dimensions from streamer frame: {frame_size}")
|
||||
elif self.hCamera:
|
||||
# Get frame dimensions by capturing a test frame from camera
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 1000)
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_INIT_TIMEOUT)
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
frame_size = (FrameHead.iWidth, FrameHead.iHeight)
|
||||
@@ -779,7 +755,7 @@ class CameraRecorder:
|
||||
if self.streamer and self.streamer.hCamera:
|
||||
try:
|
||||
with suppress_camera_errors():
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.streamer.hCamera, 1000)
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.streamer.hCamera, CAMERA_INIT_TIMEOUT)
|
||||
mvsdk.CameraReleaseImageBuffer(self.streamer.hCamera, pRawData)
|
||||
frame_size = (FrameHead.iWidth, FrameHead.iHeight)
|
||||
self.logger.info(f"Got frame dimensions from streamer's camera: {frame_size}")
|
||||
@@ -798,8 +774,8 @@ class CameraRecorder:
|
||||
# Set up video writer with configured codec
|
||||
fourcc = cv2.VideoWriter_fourcc(*self.camera_config.video_codec)
|
||||
|
||||
# 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
|
||||
# Use default 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 DEFAULT_VIDEO_FPS
|
||||
|
||||
# Create video writer with quality settings
|
||||
self.video_writer = cv2.VideoWriter(self.output_filename, fourcc, video_fps, frame_size)
|
||||
@@ -883,7 +859,7 @@ class CameraRecorder:
|
||||
|
||||
# Small delay to ensure file system sync
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
time.sleep(BRIEF_PAUSE_SLEEP)
|
||||
|
||||
# Verify file exists and has content
|
||||
if self.output_filename and os.path.exists(self.output_filename):
|
||||
|
||||
@@ -12,7 +12,6 @@ import time
|
||||
import logging
|
||||
import cv2
|
||||
import numpy as np
|
||||
import contextlib
|
||||
import subprocess
|
||||
from typing import Optional, Dict, Any, Generator
|
||||
from datetime import datetime
|
||||
@@ -26,30 +25,19 @@ 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)
|
||||
from .utils import suppress_camera_errors
|
||||
from .constants import (
|
||||
MJPEG_QUEUE_MAXSIZE,
|
||||
RTSP_QUEUE_MAXSIZE,
|
||||
RECORDING_QUEUE_MAXSIZE,
|
||||
PREVIEW_FPS,
|
||||
RTSP_FPS,
|
||||
PREVIEW_JPEG_QUALITY,
|
||||
CAMERA_GET_BUFFER_TIMEOUT,
|
||||
CAMERA_TEST_CAPTURE_TIMEOUT,
|
||||
STREAMING_LOOP_SLEEP,
|
||||
BRIEF_PAUSE_SLEEP,
|
||||
)
|
||||
|
||||
|
||||
class CameraStreamer:
|
||||
@@ -78,17 +66,17 @@ class CameraStreamer:
|
||||
self._rtsp_thread: Optional[threading.Thread] = None
|
||||
self._stop_streaming_event = threading.Event()
|
||||
self._stop_rtsp_event = threading.Event()
|
||||
self._frame_queue = queue.Queue(maxsize=5) # Buffer for latest frames (for MJPEG streaming)
|
||||
self._rtsp_frame_queue = queue.Queue(maxsize=10) # Buffer for RTSP frames (larger buffer for smoother streaming)
|
||||
self._recording_frame_queue = queue.Queue(maxsize=30) # Buffer for recording frames (shared with recorder)
|
||||
self._frame_queue = queue.Queue(maxsize=MJPEG_QUEUE_MAXSIZE) # Buffer for latest frames (for MJPEG streaming)
|
||||
self._rtsp_frame_queue = queue.Queue(maxsize=RTSP_QUEUE_MAXSIZE) # Buffer for RTSP frames (larger buffer for smoother streaming)
|
||||
self._recording_frame_queue = queue.Queue(maxsize=RECORDING_QUEUE_MAXSIZE) # Buffer for recording frames (shared with recorder)
|
||||
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
|
||||
self.preview_fps = PREVIEW_FPS # Lower FPS for preview to reduce load
|
||||
self.preview_quality = PREVIEW_JPEG_QUALITY # JPEG quality for streaming
|
||||
|
||||
# RTSP settings
|
||||
self.rtsp_fps = 15.0 # RTSP FPS (can be higher than MJPEG preview)
|
||||
self.rtsp_fps = RTSP_FPS # RTSP FPS (can be higher than MJPEG preview)
|
||||
# Use MEDIAMTX_HOST env var if set, otherwise default to localhost
|
||||
# Note: If API uses network_mode: host, MediaMTX container ports are exposed to host
|
||||
# So localhost should work, but MediaMTX must be accessible on that port
|
||||
@@ -254,7 +242,7 @@ class CameraStreamer:
|
||||
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
|
||||
time.sleep(STREAMING_LOOP_SLEEP) # Wait a bit if no frame available
|
||||
|
||||
def _initialize_camera(self) -> bool:
|
||||
"""Initialize camera for streaming (separate from recording)"""
|
||||
@@ -366,11 +354,11 @@ class CameraStreamer:
|
||||
try:
|
||||
# If using shared camera, skip capture - recorder will populate queues
|
||||
if self._using_shared_camera:
|
||||
time.sleep(0.1) # Just wait, recorder populates queues
|
||||
time.sleep(STREAMING_LOOP_SLEEP) # Just wait, recorder populates queues
|
||||
continue
|
||||
|
||||
# Capture frame with timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 200) # 200ms timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_GET_BUFFER_TIMEOUT)
|
||||
|
||||
# Process frame
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
|
||||
@@ -431,7 +419,7 @@ class CameraStreamer:
|
||||
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
|
||||
time.sleep(BRIEF_PAUSE_SLEEP) # Brief pause before retrying
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fatal error in streaming loop: {e}")
|
||||
|
||||
31
camera-management-api/usda_vision_system/camera/utils.py
Normal file
31
camera-management-api/usda_vision_system/camera/utils.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Shared utilities for camera operations.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
Reference in New Issue
Block a user