Files
lerobot/additional_functions/test_zmq_camera.py
T
2025-11-21 14:13:05 +01:00

299 lines
8.7 KiB
Python

#!/usr/bin/env python3
"""
Comprehensive test script for ZMQ camera integration.
Tests all camera functionalities: find_cameras, connect, read, async_read, disconnect.
"""
import time
import numpy as np
from pathlib import Path
from lerobot.cameras.zmq import ZMQCamera, ZMQCameraConfig
from lerobot.cameras.configs import ColorMode
def test_find_cameras():
"""Test 1: Camera Discovery"""
print("\n" + "="*60)
print("TEST 1: Camera Discovery (find_cameras)")
print("="*60)
cameras = ZMQCamera.find_cameras()
if not cameras:
print("❌ No ZMQ cameras found!")
print(" Make sure you have configured cameras in ~/.lerobot/zmq_cameras.json")
print(" or set LEROBOT_ZMQ_CAMERAS environment variable")
return None
print(f"✅ Found {len(cameras)} ZMQ camera(s):")
for i, cam in enumerate(cameras):
print(f"\n Camera {i}:")
for key, value in cam.items():
if key == "default_stream_profile":
print(f" {key}:")
for sub_key, sub_value in value.items():
print(f" {sub_key}: {sub_value}")
else:
print(f" {key}: {value}")
return cameras[0] if cameras else None
def test_connect_disconnect(cam_info):
"""Test 2: Connect and Disconnect"""
print("\n" + "="*60)
print("TEST 2: Connect and Disconnect")
print("="*60)
config = ZMQCameraConfig(
server_address=cam_info["server_address"],
port=cam_info["port"],
camera_name=cam_info["camera_name"],
color_mode=ColorMode.RGB,
)
camera = ZMQCamera(config)
# Test is_connected before connection
print(f"Before connect - is_connected: {camera.is_connected}")
assert not camera.is_connected, "❌ Camera should not be connected initially"
# Test connect
print("Connecting to camera...")
start = time.time()
camera.connect(warmup=True)
connect_time = time.time() - start
print(f"✅ Connected in {connect_time:.2f}s")
print(f" Camera resolution: {camera.width}x{camera.height}")
assert camera.is_connected, "❌ Camera should be connected after connect()"
# Test disconnect
print("Disconnecting camera...")
camera.disconnect()
print("✅ Disconnected")
assert not camera.is_connected, "❌ Camera should not be connected after disconnect()"
return config
def test_synchronous_read(config):
"""Test 3: Synchronous Read"""
print("\n" + "="*60)
print("TEST 3: Synchronous Read (read)")
print("="*60)
camera = ZMQCamera(config)
camera.connect(warmup=False)
print("Reading 10 frames synchronously...")
read_times = []
for i in range(10):
start = time.time()
frame = camera.read()
read_time = (time.time() - start) * 1000 # ms
read_times.append(read_time)
# Validate frame
assert isinstance(frame, np.ndarray), "❌ Frame should be numpy array"
assert frame.ndim == 3, "❌ Frame should be 3D (H, W, C)"
assert frame.shape[2] == 3, "❌ Frame should have 3 channels"
if i == 0:
print(f" Frame shape: {frame.shape}")
print(f" Frame dtype: {frame.dtype}")
print(f" Frame range: [{frame.min()}, {frame.max()}]")
avg_time = np.mean(read_times)
std_time = np.std(read_times)
fps = 1000 / avg_time if avg_time > 0 else 0
print(f"✅ Read 10 frames successfully")
print(f" Average read time: {avg_time:.2f} ± {std_time:.2f} ms")
print(f" Estimated FPS: {fps:.1f}")
camera.disconnect()
def test_color_mode_conversion(config):
"""Test 4: Color Mode Conversion"""
print("\n" + "="*60)
print("TEST 4: Color Mode Conversion")
print("="*60)
# Test RGB mode
config_rgb = ZMQCameraConfig(
server_address=config.server_address,
port=config.port,
camera_name=config.camera_name,
color_mode=ColorMode.RGB,
)
camera_rgb = ZMQCamera(config_rgb)
camera_rgb.connect(warmup=False)
frame_rgb = camera_rgb.read()
camera_rgb.disconnect()
# Test BGR mode
config_bgr = ZMQCameraConfig(
server_address=config.server_address,
port=config.port,
camera_name=config.camera_name,
color_mode=ColorMode.BGR,
)
camera_bgr = ZMQCamera(config_bgr)
camera_bgr.connect(warmup=False)
frame_bgr = camera_bgr.read()
camera_bgr.disconnect()
# Frames should be different (color channels swapped)
assert not np.array_equal(frame_rgb, frame_bgr), "❌ RGB and BGR frames should differ"
# But shapes should be the same
assert frame_rgb.shape == frame_bgr.shape, "❌ RGB and BGR frames should have same shape"
print("✅ RGB mode works correctly")
print("✅ BGR mode works correctly")
print(f" Frame shapes match: {frame_rgb.shape}")
def test_asynchronous_read(config):
"""Test 5: Asynchronous Read"""
print("\n" + "="*60)
print("TEST 5: Asynchronous Read (async_read)")
print("="*60)
camera = ZMQCamera(config)
camera.connect(warmup=False)
print("Reading 10 frames asynchronously...")
read_times = []
for i in range(10):
start = time.time()
frame = camera.async_read(timeout_ms=1000)
read_time = (time.time() - start) * 1000 # ms
read_times.append(read_time)
# Validate frame
assert isinstance(frame, np.ndarray), "❌ Frame should be numpy array"
assert frame.ndim == 3, "❌ Frame should be 3D (H, W, C)"
if i == 0:
print(f" Frame shape: {frame.shape}")
print(f" Background thread alive: {camera.thread.is_alive()}")
avg_time = np.mean(read_times)
std_time = np.std(read_times)
fps = 1000 / avg_time if avg_time > 0 else 0
print(f"✅ Read 10 frames asynchronously")
print(f" Average read time: {avg_time:.2f} ± {std_time:.2f} ms")
print(f" Estimated FPS: {fps:.1f}")
camera.disconnect()
def test_reconnection(config):
"""Test 6: Reconnection"""
print("\n" + "="*60)
print("TEST 6: Reconnection")
print("="*60)
camera = ZMQCamera(config)
# Connect, read, disconnect cycle 1
print("Connection cycle 1...")
camera.connect(warmup=False)
frame1 = camera.read()
camera.disconnect()
# Wait a bit
time.sleep(0.5)
# Connect, read, disconnect cycle 2
print("Connection cycle 2...")
camera.connect(warmup=False)
frame2 = camera.read()
camera.disconnect()
# Frames should be valid
assert frame1.shape == frame2.shape, "❌ Frames should have consistent shape"
print("✅ Reconnection works correctly")
print(f" Both frames have shape: {frame1.shape}")
def test_timeout_handling(config):
"""Test 7: Timeout Handling"""
print("\n" + "="*60)
print("TEST 7: Timeout Handling")
print("="*60)
# Create config with very short timeout
config_short = ZMQCameraConfig(
server_address=config.server_address,
port=config.port,
camera_name=config.camera_name,
timeout_ms=50, # Very short timeout
)
camera = ZMQCamera(config_short)
camera.connect(warmup=False)
try:
# This should work if server is fast enough
frame = camera.read()
print(f"✅ Read succeeded even with {config_short.timeout_ms}ms timeout")
except TimeoutError:
print(f"⚠️ Timeout occurred (expected with {config_short.timeout_ms}ms timeout)")
camera.disconnect()
def main():
"""Run all tests"""
print("\n" + "="*60)
print("ZMQ CAMERA COMPREHENSIVE TEST SUITE")
print("="*60)
try:
# Test 1: Discovery
cam_info = test_find_cameras()
if not cam_info:
print("\n❌ Cannot proceed without configured cameras")
return
# Test 2: Connect/Disconnect
config = test_connect_disconnect(cam_info)
# Test 3: Synchronous Read
test_synchronous_read(config)
# Test 4: Color Mode
test_color_mode_conversion(config)
# Test 5: Asynchronous Read
test_asynchronous_read(config)
# Test 6: Reconnection
test_reconnection(config)
# Test 7: Timeout
test_timeout_handling(config)
# Summary
print("\n" + "="*60)
print("✅ ALL TESTS PASSED!")
print("="*60)
print("\nZMQ Camera integration is working correctly! 🎉")
except Exception as e:
print(f"\n❌ TEST FAILED: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()