"""Intel RealSense scan profile utilities."""
from __future__ import annotations
from typing import Dict, List, Sequence, Tuple
import numpy as np
import pyrealsense2 as rs # type: ignore
[docs]
def calculate_fov(width: int, height: int, fx: float, fy: float) -> Tuple[float, float]:
"""
Compute horizontal and vertical field-of-view angles in degrees.
Parameters
----------
width : int
Image width in pixels.
height : int
Image height in pixels.
fx : float
Focal length in x direction (pixels).
fy : float
Focal length in y direction (pixels).
Returns
-------
tuple[float, float]
Horizontal and vertical FoV in degrees.
Raises
------
ValueError
If the dimensions or focal lengths are not positive and finite.
"""
if width <= 0 or height <= 0:
raise ValueError("width and height must be positive")
if not np.isfinite(fx) or not np.isfinite(fy) or fx <= 0 or fy <= 0:
raise ValueError("fx and fy must be positive and finite")
fov_x = np.rad2deg(2 * np.arctan2(width, 2 * fx))
fov_y = np.rad2deg(2 * np.arctan2(height, 2 * fy))
return (float(fov_x), float(fov_y))
[docs]
def scan_profiles(
stream_profiles: Sequence[rs.stream_profile], # type: ignore
) -> Tuple[Dict[Tuple[int, int], List[int]], Dict[Tuple[int, int], Tuple[float, float]]]:
"""
Scan stream profiles and collect supported resolution/FPS/FoV values.
Parameters
----------
stream_profiles : List[rs.stream_profile]
RealSense stream profiles to inspect.
Returns
-------
tuple[dict[tuple[int, int], list[int]], dict[tuple[int, int], tuple[float, float]]]
Two mappings: supported FPS values per resolution and FoV per
resolution.
"""
resolution_fps_combinations: Dict[Tuple[int, int], List[int]] = {}
resolution_fov_combinations: Dict[Tuple[int, int], Tuple[float, float]] = {}
for stream_profile in stream_profiles:
if not stream_profile.is_video_stream_profile():
continue
profile = stream_profile.as_video_stream_profile()
if profile.format() != rs.format.rgb8 and profile.format() != rs.format.z16:
continue
if profile.stream_type() == rs.stream.infrared:
continue
resolution = (profile.width(), profile.height())
fps = int(profile.fps())
intrinsics = profile.get_intrinsics()
fov_H, fov_V = calculate_fov(intrinsics.width, intrinsics.height, intrinsics.fx, intrinsics.fy)
if resolution not in resolution_fps_combinations:
resolution_fps_combinations[resolution] = [fps]
resolution_fov_combinations[resolution] = (fov_H, fov_V)
else:
if fps not in resolution_fps_combinations[resolution]:
resolution_fps_combinations[resolution].append(fps)
# sort the fps lists in ascending order
for resolution in resolution_fps_combinations:
resolution_fps_combinations[resolution].sort()
return resolution_fps_combinations, resolution_fov_combinations
[docs]
def print_profile_info(
resolution_fps_combinations: Dict[Tuple[int, int], List[int]],
resolution_fov_combinations: Dict[Tuple[int, int], Tuple[float, float]],
) -> None:
"""
Print available frame rates and FoV grouped by resolution.
Parameters
----------
resolution_fps_combinations : Dict[Tuple[int, int], List[float]]
Supported FPS values per resolution.
resolution_fov_combinations : Dict[Tuple[int, int], Tuple[float, float]]
Horizontal and vertical FoV per resolution.
Returns
-------
None
"""
for resolution in resolution_fps_combinations:
fov_string = f"({resolution_fov_combinations[resolution][0]:.1f}°, {resolution_fov_combinations[resolution][1]:.1f}°)"
print(f"{resolution}: {resolution_fps_combinations[resolution]} fps, {fov_string}")
[docs]
def check_aligned_depth_resolutions(
device_serial: str,
color_framerates: Dict[Tuple[int, int], List[int]],
depth_framerates: Dict[Tuple[int, int], List[int]],
) -> None:
"""Check depth-to-color alignment for each supported resolution pair."""
color_resolution_max_fps = {resolution: framerates[-1] for resolution, framerates in color_framerates.items()}
depth_resolution_max_fps = {resolution: framerates[-1] for resolution, framerates in depth_framerates.items()}
print("Checking aligned depth resolution (should equal color resolution):")
for color_resolution, color_fps in color_resolution_max_fps.items():
for depth_resolution, depth_fps in depth_resolution_max_fps.items():
pipeline = rs.pipeline()
config = rs.config()
config.enable_device(device_serial)
config.enable_stream(rs.stream.color, *color_resolution, rs.format.rgb8, color_fps)
config.enable_stream(rs.stream.depth, *depth_resolution, rs.format.z16, depth_fps)
pipeline_wrapper = rs.pipeline_wrapper(pipeline)
if not config.can_resolve(pipeline_wrapper):
print(
f"color_resolution = {color_resolution}, depth_resolution = {depth_resolution}: "
"unsupported stream combination"
)
continue
pipeline_started = False
try:
pipeline.start(config)
pipeline_started = True
composite_frame = pipeline.wait_for_frames()
color_frame = composite_frame.get_color_frame()
depth_frame = composite_frame.get_depth_frame()
if not color_frame or not depth_frame:
print(
f"color_resolution = {color_resolution}, depth_resolution = {depth_resolution}: "
"missing color or depth frame"
)
continue
aligned_frames = rs.align(rs.stream.color).process(composite_frame)
aligned_depth_frame = aligned_frames.get_depth_frame()
if not aligned_depth_frame:
print(
f"color_resolution = {color_resolution}, depth_resolution = {depth_resolution}: "
"alignment did not produce a depth frame"
)
continue
aligned_resolution = (aligned_depth_frame.get_width(), aligned_depth_frame.get_height())
print(
f"color_resolution = {color_resolution}, depth_resolution = {depth_resolution}, "
f"aligned_depth = {aligned_resolution}"
)
except RuntimeError as error:
print(
f"color_resolution = {color_resolution}, depth_resolution = {depth_resolution}: "
f"RealSense error: {error}"
)
finally:
if pipeline_started:
pipeline.stop()
[docs]
def main() -> int:
"""Scan the first connected RealSense device and print its stream profiles."""
context = rs.context()
devices = context.query_devices()
if len(devices) == 0:
print("No Intel RealSense device was found.")
return 1
device = devices[0]
try:
color_sensor = device.first_color_sensor()
depth_sensor = device.first_depth_sensor()
except RuntimeError as error:
print(f"The selected device does not provide the required color and depth sensors: {error}")
return 1
device_name = device.get_info(rs.camera_info.name)
device_serial = device.get_info(rs.camera_info.serial_number)
print(f"Device Name: {device_name}")
print(f"Serial Number: {device_serial}")
color_framerates, color_fovs = scan_profiles(color_sensor.get_stream_profiles())
depth_framerates, depth_fovs = scan_profiles(depth_sensor.get_stream_profiles())
print("Available color resolutions, framerates and FoV:")
print_profile_info(color_framerates, color_fovs)
print("Available depth resolutions, framerates and FoV:")
print_profile_info(depth_framerates, depth_fovs)
if not color_framerates or not depth_framerates:
print("No compatible RGB8 color and Z16 depth profiles were found.")
return 1
check_aligned_depth_resolutions(device_serial, color_framerates, depth_framerates)
return 0
if __name__ == "__main__":
raise SystemExit(main())