From ab3af2f675ea6fd049e41cb7f1a0b375ab141045 Mon Sep 17 00:00:00 2001 From: Dominic Canare Date: Mon, 9 Oct 2023 01:29:44 -0500 Subject: [PATCH 1/3] Adds a function for calibration retrieval --- src/pupil_labs/realtime_api/device.py | 30 ++++++++++++++++++++ src/pupil_labs/realtime_api/models.py | 1 + src/pupil_labs/realtime_api/simple/device.py | 7 +++++ 3 files changed, 38 insertions(+) diff --git a/src/pupil_labs/realtime_api/device.py b/src/pupil_labs/realtime_api/device.py index a4fa2a3..b0ef12a 100644 --- a/src/pupil_labs/realtime_api/device.py +++ b/src/pupil_labs/realtime_api/device.py @@ -7,6 +7,7 @@ import aiohttp import websockets +import numpy as np import pupil_labs # noqa: F401 @@ -165,6 +166,35 @@ async def __aexit__( def _create_client_session(self): self.session = aiohttp.ClientSession() + async def get_calibration(self) -> np.ndarray: + """ + :raises pupil_labs.realtime_api.device.DeviceError: if the request fails + """ + async with self.session.get(self.api_url(APIPath.CALIBRATION)) as response: + if response.status != 200: + raise DeviceError(response.status, "Failed to fetch calibration") + + raw_data = await response.read() + return np.frombuffer( + raw_data, + np.dtype( + [ + ("version", "u1"), + ("serial", "6a"), + ("scene_camera_matrix", "(3,3)d"), + ("scene_distortion_coefficients", "8d"), + ("scene_extrinsics_affine_matrix", "(4,4)d"), + ("right_camera_matrix", "(3,3)d"), + ("right_distortion_coefficients", "8d"), + ("right_extrinsics_affine_matrix", "(4,4)d"), + ("left_camera_matrix", "(3,3)d"), + ("left_distortion_coefficients", "8d"), + ("left_extrinsics_affine_matrix", "(4,4)d"), + ("crc", "u4"), + ] + ), + ) + class StatusUpdateNotifier: def __init__(self, device: Device, callbacks: T.List[UpdateCallback]) -> None: diff --git a/src/pupil_labs/realtime_api/models.py b/src/pupil_labs/realtime_api/models.py index b730a08..4176660 100644 --- a/src/pupil_labs/realtime_api/models.py +++ b/src/pupil_labs/realtime_api/models.py @@ -19,6 +19,7 @@ class APIPath(enum.Enum): RECORDING_STOP_AND_SAVE = "/recording:stop_and_save" RECORDING_CANCEL = "/recording:cancel" EVENT = "/event" + CALIBRATION = "/../calibration.bin" def full_address( self, address: str, port: int, protocol: str = "http", prefix: str = "/api" diff --git a/src/pupil_labs/realtime_api/simple/device.py b/src/pupil_labs/realtime_api/simple/device.py index f44cd23..d43bc6f 100644 --- a/src/pupil_labs/realtime_api/simple/device.py +++ b/src/pupil_labs/realtime_api/simple/device.py @@ -114,6 +114,13 @@ def world_sensor(self) -> T.Optional[Sensor]: def gaze_sensor(self) -> T.Optional[Sensor]: return self._status.direct_gaze_sensor() + def get_calibration(self): + async def _get_calibration(): + async with _DeviceAsync.convert_from(self) as control: + return await control.get_calibration() + + return asyncio.run(_get_calibration()) + def recording_start(self) -> str: """Wraps :py:meth:`pupil_labs.realtime_api.device.Device.recording_start` From 14585d8e174774f0343ef485537eedab39d52bff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 06:32:45 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pupil_labs/realtime_api/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pupil_labs/realtime_api/device.py b/src/pupil_labs/realtime_api/device.py index b0ef12a..90bb7ba 100644 --- a/src/pupil_labs/realtime_api/device.py +++ b/src/pupil_labs/realtime_api/device.py @@ -6,8 +6,8 @@ import typing as T import aiohttp -import websockets import numpy as np +import websockets import pupil_labs # noqa: F401 From 5e66be3661437af7fe1f2cddeea3e0db856efe4c Mon Sep 17 00:00:00 2001 From: Dominic Canare Date: Tue, 10 Oct 2023 02:34:09 -0500 Subject: [PATCH 3/3] Adds example for camera calibration --- docs/examples/simple.rst | 10 +++++++++ examples/simple/camera_calibration.py | 29 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 examples/simple/camera_calibration.py diff --git a/docs/examples/simple.rst b/docs/examples/simple.rst index da1c3a7..042b225 100644 --- a/docs/examples/simple.rst +++ b/docs/examples/simple.rst @@ -91,6 +91,16 @@ Eyes camera video :emphasize-lines: 18 :linenos: +Camera calibration +------------------ + +.. note:: Only available when connecting to a Neon Companion app + +.. literalinclude:: ../../examples/simple/camera_calibration.py + :language: python + :emphasize-lines: 12 + :linenos: + .. _stream_video_with_overlayed_gaze_example_simple: diff --git a/examples/simple/camera_calibration.py b/examples/simple/camera_calibration.py new file mode 100644 index 0000000..8218e29 --- /dev/null +++ b/examples/simple/camera_calibration.py @@ -0,0 +1,29 @@ +from pupil_labs.realtime_api.simple import discover_one_device + +# Look for devices. Returns as soon as it has found the first device. +print("Looking for the next best device...") +device = discover_one_device(max_search_duration_seconds=10) +if device is None: + print("No device found.") + raise SystemExit(-1) + +# Device status is fetched on initialization and kept up-to-date in the background + +calibration = device.get_calibration() + +print("Scene camera matrix:") +print(calibration["scene_camera_matrix"][0]) +print("\nScene distortion coefficients:") +print(calibration["scene_distortion_coefficients"][0]) + +print("\nRight camera matrix:") +print(calibration["right_camera_matrix"][0]) +print("\nRight distortion coefficients:") +print(calibration["right_distortion_coefficients"][0]) + +print("\nLeft camera matrix:") +print(calibration["left_camera_matrix"][0]) +print("\nLeft distortion coefficients:") +print(calibration["left_distortion_coefficients"][0]) + +device.close()