refactor(cameras): improvements utils functionalities v0.1

This commit is contained in:
Steven Palma
2025-05-07 18:28:25 +02:00
parent a26cfe34b0
commit 06e02f6d17
6 changed files with 378 additions and 390 deletions
@@ -154,9 +154,8 @@ class RealSenseCamera(Camera):
"""Checks if the camera pipeline is started and streams are active."""
return self.rs_pipeline is not None and self.rs_profile is not None
# NOTE(Steven): Make it a class method and an util for calling it in utils.py (don't raise)
@staticmethod
def find_cameras(raise_when_empty: bool = True) -> List[Dict[str, str]]:
def find_cameras(raise_when_empty: bool = True) -> List[Dict[str, Union[str, int, float]]]:
"""
Detects available Intel RealSense cameras connected to the system.
@@ -164,38 +163,47 @@ class RealSenseCamera(Camera):
raise_when_empty (bool): If True, raises an OSError if no cameras are found.
Returns:
list[dict]: A list of dictionaries, where each dictionary contains
'serial_number' and 'name' of a found camera.
List[Dict[str, Union[str, int, float]]]: A list of dictionaries,
where each dictionary contains 'type', 'serial_number', 'name',
firmware version, USB type, and other available specs.
Raises:
OSError: If `raise_when_empty` is True and no cameras are detected.
OSError: If `raise_when_empty` is True and no cameras are detected,
or if pyrealsense2 is not installed.
"""
cameras_info = []
found_cameras_info = []
context = rs.context()
devices = context.query_devices()
if not devices:
logger.warning("No RealSense devices detected.")
if raise_when_empty:
raise OSError(
"No RealSense devices detected. Ensure cameras are connected, "
"drivers (`librealsense`) and wrapper (`pyrealsense2`) are installed, "
"and firmware is up-to-date."
"library (`pyrealsense2`) is installed, and firmware is up-to-date."
)
return []
for device in devices:
serial = device.get_info(rs.camera_info.serial_number)
name = device.get_info(rs.camera_info.name)
cameras_info.append({"serial_number": serial, "name": name})
logger.debug(f"Found RealSense camera: Name='{name}', SN='{serial}'")
camera_info = {
"type": "RealSense",
"serial_number": device.get_info(rs.camera_info.serial_number),
"firmware_version": device.get_info(rs.camera_info.firmware_version),
"usb_type_descriptor": device.get_info(rs.camera_info.usb_type_descriptor),
"physical_port": device.get_info(rs.camera_info.physical_port),
"product_id": device.get_info(rs.camera_info.product_id),
"product_line": device.get_info(rs.camera_info.product_line),
"name": device.get_info(rs.camera_info.name),
}
found_cameras_info.append(camera_info)
logger.debug(f"Found RealSense camera: {camera_info}")
logger.info(f"Detected RealSense cameras: {cameras_info}")
return cameras_info
logger.info(f"Detected RealSense cameras: {[cam['serial_number'] for cam in found_cameras_info]}")
return found_cameras_info
def _find_serial_number_from_name(self, name: str) -> str:
"""Finds the serial number for a given unique camera name."""
camera_infos = self.find_cameras(raise_when_empty=True) # Raises if none found
found_devices = [cam for cam in camera_infos if cam["name"] == name]
camera_infos = self.find_cameras(raise_when_empty=True)
found_devices = [cam for cam in camera_infos if str(cam["name"]) == name]
if not found_devices:
available_names = [cam["name"] for cam in camera_infos]
@@ -210,7 +218,7 @@ class RealSenseCamera(Camera):
f"Please use a unique serial number instead. Found SNs: {serial_numbers}"
)
serial_number = found_devices[0]["serial_number"]
serial_number = str(found_devices[0]["serial_number"])
logger.info(f"Found serial number '{serial_number}' for camera name '{name}'.")
return serial_number
@@ -265,7 +273,6 @@ class RealSenseCamera(Camera):
self._validate_capture_width()
self._validate_capture_height()
# Validate Depth Stream if enabled
if self.use_depth:
try:
depth_stream = self.rs_profile.get_stream(rs.stream.depth).as_video_stream_profile()
@@ -329,8 +336,9 @@ class RealSenseCamera(Camera):
self.rs_profile = None
self.rs_pipeline = None
raise ConnectionError(
f"Failed to open camera {self.serial_number}. Run NOTE(Steven): Add right file to scan for available cameras."
) from e # NOTE(Steven): Run find_cameras: available_sns = [cam["serial_number"] for cam in self.find_cameras(raise_when_empty=False)]
f"Failed to open RealSense camera {self.serial_number}. Error: {e}. "
f"Run 'python -m find_cameras list-cameras' for details."
) from e
logger.debug(f"Validating stream configuration for {self.serial_number}...")
self._validate_capture_settings()
@@ -362,14 +370,13 @@ class RealSenseCamera(Camera):
"""Validates and sets the internal capture width based on actual stream width."""
color_stream = self.rs_profile.get_stream(rs.stream.color).as_video_stream_profile()
actual_width = color_stream.width()
actual_width = int(round(color_stream.width()))
if self.capture_width is None:
self.capture_width = actual_width
logger.info(f"Capture width not specified, using camera default: {self.capture_width} pixels.")
return
actual_width = round(actual_width)
if self.capture_width != actual_width:
logger.warning(
f"Requested capture width {self.capture_width} for {self}, but camera reported {actual_width}."
@@ -383,14 +390,13 @@ class RealSenseCamera(Camera):
"""Validates and sets the internal capture height based on actual stream height."""
color_stream = self.rs_profile.get_stream(rs.stream.color).as_video_stream_profile()
actual_height = color_stream.height()
actual_height = int(round(color_stream.height()))
if self.capture_height is None:
self.capture_height = actual_height
logger.info(f"Capture height not specified, using camera default: {self.capture_height} pixels.")
return
actual_height = round(actual_height)
if self.capture_height != actual_height:
logger.warning(
f"Requested capture height {self.capture_height} for {self}, but camera reported {actual_height}."
@@ -531,7 +537,7 @@ class RealSenseCamera(Camera):
logger.debug(f"Starting read loop thread for {self}.")
while not self.stop_event.is_set():
try:
frame_data = self.read()
frame_data = self.read(timeout_ms=500)
with contextlib.suppress(queue.Empty):
_ = self.frame_queue.get_nowait()
@@ -548,7 +554,7 @@ class RealSenseCamera(Camera):
def _ensure_read_thread_running(self):
"""Starts or restarts the background read thread if it's not running."""
if self.thread is not None:
if self.thread is not None and self.thread.is_alive():
self.thread.join(timeout=0.1)
if self.stop_event is not None:
self.stop_event.set()
@@ -615,18 +621,14 @@ class RealSenseCamera(Camera):
if self.stop_event is not None:
logger.debug(f"Signaling stop event for read thread of {self}.")
self.stop_event.set()
else:
logger.warning(f"Read thread exists for {self}, but stop event is None. Cannot signal.")
if self.thread.is_alive():
if self.thread is not None and self.thread.is_alive():
logger.debug(f"Waiting for read thread of {self} to join...")
self.thread.join(timeout=2.0)
if self.thread.is_alive():
logger.warning(f"Read thread for {self} did not terminate gracefully after 2 seconds.")
else:
logger.debug(f"Read thread for {self} joined successfully.")
else:
logger.debug(f"Read thread for {self} was already stopped.")
self.thread = None
self.stop_event = None