Simple Examples

Code examples that use the Simple API.

Note

The examples require Python 3.7+ to run.

Find one or more devices

1from pupil_labs.realtime_api.simple import discover_devices, discover_one_device
2
3# Look for devices. Returns as soon as it has found the first device.
4print("Looking for the next best device...\n\t", end="")
5print(discover_one_device(max_search_duration_seconds=10.0))
6
7# List all devices that could be found within 10 seconds
8print("Starting 10 second search...\n\t", end="")
9print(discover_devices(search_duration_seconds=10.0))

Remote control devices

Get current status

 1from pupil_labs.realtime_api.simple import discover_one_device
 2
 3# Look for devices. Returns as soon as it has found the first device.
 4print("Looking for the next best device...")
 5device = discover_one_device(max_search_duration_seconds=10)
 6if device is None:
 7    print("No device found.")
 8    raise SystemExit(-1)
 9
10# Device status is fetched on initialization and kept up-to-date in the background
11
12print(f"Phone IP address: {device.phone_ip}")
13print(f"Phone name: {device.phone_name}")
14print(f"Phone unique ID: {device.phone_id}")
15
16print(f"Battery level: {device.battery_level_percent}%")
17print(f"Battery state: {device.battery_state}")
18
19print(f"Free storage: {device.memory_num_free_bytes / 1024**3}GB")
20print(f"Storage level: {device.memory_state}")
21
22print(f"Connected glasses: SN {device.serial_number_glasses}")
23print(f"Connected scene camera: SN {device.serial_number_scene_cam}")
24
25device.close()  # explicitly stop auto-update

Automatic status updates

The Device class monitors a Pupil Invisible Companion device in the background and mirrors its state accordingly.

 1from pupil_labs.realtime_api.simple import discover_one_device
 2
 3# Look for devices. Returns as soon as it has found the first device.
 4print("Looking for the next best device...")
 5device = discover_one_device(max_search_duration_seconds=10)
 6if device is None:
 7    print("No device found.")
 8    raise SystemExit(-1)
 9
10scene_camera = device.world_sensor()
11connected = False if scene_camera is None else scene_camera.connected
12print("Scene camera connected:", connected)
13
14input("(Dis)connect the scene camera and hit enter...")
15
16scene_camera = device.world_sensor()
17connected = False if scene_camera is None else scene_camera.connected
18print("Scene camera connected:", connected)
19
20device.close()

Send event

An event without an explicit timestamp, will be timestamped on arrival at the Pupil Invisible Companion device.

 1import time
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5# Look for devices. Returns as soon as it has found the first device.
 6print("Looking for the next best device...")
 7device = discover_one_device(max_search_duration_seconds=10)
 8if device is None:
 9    print("No device found.")
10    raise SystemExit(-1)
11
12print(device.send_event("test event; timestamped at arrival"))
13
14# send event with current timestamp
15print(
16    device.send_event(
17        "test event; timestamped by the client, relying on NTP for sync",
18        event_timestamp_unix_ns=time.time_ns(),
19    )
20)
21
22# Estimate clock offset between Companion device and client script
23# (only needs to be done once)
24estimate = device.estimate_time_offset()
25clock_offset_ns = round(estimate.time_offset_ms.mean * 1_000_000)
26print(f"Clock offset: {clock_offset_ns:_d} ns")
27
28# send event with current timestamp, but correct it manual for possible clock offset
29current_time_ns_in_client_clock = time.time_ns()
30current_time_ns_in_companion_clock = current_time_ns_in_client_clock - clock_offset_ns
31print(
32    device.send_event(
33        "test event; timestamped by the client, manual clock offset correction",
34        event_timestamp_unix_ns=current_time_ns_in_companion_clock,
35    )
36)
37
38
39device.close()

Start, stop and save, and cancel recordings

 1import time
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5# Look for devices. Returns as soon as it has found the first device.
 6print("Looking for the next best device...")
 7device = discover_one_device(max_search_duration_seconds=10)
 8if device is None:
 9    print("No device found.")
10    raise SystemExit(-1)
11
12print(f"Starting recording")
13recording_id = device.recording_start()
14print(f"Started recording with id {recording_id}")
15
16time.sleep(5)
17
18device.recording_stop_and_save()
19
20print("Recording stopped and saved")
21# device.recording_cancel()  # uncomment to cancel recording
22
23device.close()

Streaming

Gaze data

 1from pupil_labs.realtime_api.simple import discover_one_device
 2
 3# Look for devices. Returns as soon as it has found the first device.
 4print("Looking for the next best device...")
 5device = discover_one_device(max_search_duration_seconds=10)
 6if device is None:
 7    print("No device found.")
 8    raise SystemExit(-1)
 9
10# device.streaming_start()  # optional, if not called, stream is started on-demand
11
12try:
13    while True:
14        print(device.receive_gaze_datum())
15except KeyboardInterrupt:
16    pass
17finally:
18    print("Stopping...")
19    # device.streaming_stop()  # optional, if not called, stream is stopped on close
20    device.close()  # explicitly stop auto-update

Scene camera video

 1import cv2
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5
 6def main():
 7    # Look for devices. Returns as soon as it has found the first device.
 8    print("Looking for the next best device...")
 9    device = discover_one_device(max_search_duration_seconds=10)
10    if device is None:
11        print("No device found.")
12        raise SystemExit(-1)
13
14    print(f"Connecting to {device}...")
15
16    try:
17        while True:
18            bgr_pixels, frame_datetime = device.receive_scene_video_frame()
19            draw_time(bgr_pixels, frame_datetime)
20            cv2.imshow("Scene Camera - Press ESC to quit", bgr_pixels)
21            if cv2.waitKey(1) & 0xFF == 27:
22                break
23    except KeyboardInterrupt:
24        pass
25    finally:
26        print("Stopping...")
27        device.close()  # explicitly stop auto-update
28
29
30def draw_time(frame, time):
31    frame_txt_font_name = cv2.FONT_HERSHEY_SIMPLEX
32    frame_txt_font_scale = 1.0
33    frame_txt_thickness = 1
34
35    # first line: frame index
36    frame_txt = str(time)
37
38    cv2.putText(
39        frame,
40        frame_txt,
41        (20, 50),
42        frame_txt_font_name,
43        frame_txt_font_scale,
44        (255, 255, 255),
45        thickness=frame_txt_thickness,
46        lineType=cv2.LINE_8,
47    )
48
49
50if __name__ == "__main__":
51    main()

Eyes camera video

Note

Only available when connecting to a Neon Companion app

 1import cv2
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5
 6def main():
 7    # Look for devices. Returns as soon as it has found the first device.
 8    print("Looking for the next best device...")
 9    device = discover_one_device(max_search_duration_seconds=10)
10    if device is None:
11        print("No device found.")
12        raise SystemExit(-1)
13
14    print(f"Connecting to {device}...")
15
16    try:
17        while True:
18            bgr_pixels, frame_datetime = device.receive_eyes_video_frame()
19            draw_time(bgr_pixels, frame_datetime)
20            cv2.imshow("Eyes Camera - Press ESC to quit", bgr_pixels)
21            if cv2.waitKey(1) & 0xFF == 27:
22                break
23    except KeyboardInterrupt:
24        pass
25    finally:
26        print("Stopping...")
27        device.close()  # explicitly stop auto-update
28
29
30def draw_time(frame, time):
31    frame_txt_font_name = cv2.FONT_HERSHEY_SIMPLEX
32    frame_txt_font_scale = 1.0
33    frame_txt_thickness = 1
34
35    # first line: frame index
36    frame_txt = str(time)
37
38    cv2.putText(
39        frame,
40        frame_txt,
41        (20, 50),
42        frame_txt_font_name,
43        frame_txt_font_scale,
44        (255, 255, 255),
45        thickness=frame_txt_thickness,
46        lineType=cv2.LINE_8,
47    )
48
49
50if __name__ == "__main__":
51    main()

Camera calibration

Note

Only available when connecting to a Neon Companion app

 1from pupil_labs.realtime_api.simple import discover_one_device
 2
 3# Look for devices. Returns as soon as it has found the first device.
 4print("Looking for the next best device...")
 5device = discover_one_device(max_search_duration_seconds=10)
 6if device is None:
 7    print("No device found.")
 8    raise SystemExit(-1)
 9
10# Device status is fetched on initialization and kept up-to-date in the background
11
12calibration = device.get_calibration()
13
14print("Scene camera matrix:")
15print(calibration["scene_camera_matrix"][0])
16print("\nScene distortion coefficients:")
17print(calibration["scene_distortion_coefficients"][0])
18
19print("\nRight camera matrix:")
20print(calibration["right_camera_matrix"][0])
21print("\nRight distortion coefficients:")
22print(calibration["right_distortion_coefficients"][0])
23
24print("\nLeft camera matrix:")
25print(calibration["left_camera_matrix"][0])
26print("\nLeft distortion coefficients:")
27print(calibration["left_distortion_coefficients"][0])
28
29device.close()

Scene camera video with overlayed gaze

 1import cv2
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5
 6def main():
 7    # Look for devices. Returns as soon as it has found the first device.
 8    print("Looking for the next best device...")
 9    device = discover_one_device(max_search_duration_seconds=10)
10    if device is None:
11        print("No device found.")
12        raise SystemExit(-1)
13
14    print(f"Connecting to {device}...")
15
16    try:
17        while True:
18            frame, gaze = device.receive_matched_scene_video_frame_and_gaze()
19            cv2.circle(
20                frame.bgr_pixels,
21                (int(gaze.x), int(gaze.y)),
22                radius=80,
23                color=(0, 0, 255),
24                thickness=15,
25            )
26
27            cv2.imshow("Scene camera with gaze overlay", frame.bgr_pixels)
28            if cv2.waitKey(1) & 0xFF == 27:
29                break
30    except KeyboardInterrupt:
31        pass
32    finally:
33        print("Stopping...")
34        device.close()  # explicitly stop auto-update
35
36
37if __name__ == "__main__":
38    main()

Scene camera video with overlayed eyes video and gaze circle

Note

Only available when connecting to a Neon Companion app

 1import cv2
 2
 3from pupil_labs.realtime_api.simple import discover_one_device
 4
 5
 6def main():
 7    # Look for devices. Returns as soon as it has found the first device.
 8    print("Looking for the next best device...")
 9    device = discover_one_device(max_search_duration_seconds=10)
10    if device is None:
11        print("No device found.")
12        raise SystemExit(-1)
13
14    print(f"Connecting to {device}...")
15
16    try:
17        while True:
18            matched = device.receive_matched_scene_and_eyes_video_frames_and_gaze()
19            if not matched:
20                print(
21                    "Not able to find a match! Note: Pupil Invisible does not support "
22                    "streaming eyes video"
23                )
24                continue
25
26            cv2.circle(
27                matched.scene.bgr_pixels,
28                (int(matched.gaze.x), int(matched.gaze.y)),
29                radius=80,
30                color=(0, 0, 255),
31                thickness=15,
32            )
33
34            # Render eyes video into the scene video
35            height, width, _ = matched.eyes.bgr_pixels.shape
36            matched.scene.bgr_pixels[:height, :width, :] = matched.eyes.bgr_pixels
37
38            cv2.imshow(
39                "Scene camera with eyes and gaze overlay", matched.scene.bgr_pixels
40            )
41            if cv2.waitKey(1) & 0xFF == 27:
42                break
43    except KeyboardInterrupt:
44        pass
45    finally:
46        print("Stopping...")
47        device.close()  # explicitly stop auto-update
48
49
50if __name__ == "__main__":
51    main()

Time Offset Estimation

See pupil_labs.realtime_api.time_echo for details.

 1from pupil_labs.realtime_api.simple import discover_one_device
 2
 3# Look for devices. Returns as soon as it has found the first device.
 4print("Looking for the next best device...")
 5device = discover_one_device(max_search_duration_seconds=10)
 6if device is None:
 7    raise SystemExit("No device found.")
 8
 9estimate = device.estimate_time_offset()
10if estimate is None:
11    device.close()
12    raise SystemExit("Pupil Companion app is too old")
13
14print(f"Mean time offset: {estimate.time_offset_ms.mean} ms")
15print(f"Mean roundtrip duration: {estimate.roundtrip_duration_ms.mean} ms")
16
17device.close()