Skip to content

Scene Camera Video

It's very easy to stream the scene camera feed with timestamps for real-time monitoring. Simply use the device.receive_scene_video_frame method.

bgr_pixels, frame_datetime = device.receive_scene_video_frame()
Scene camera
SimpleVideoFrame

SimpleVideoFrame

Bases: NamedTuple

A simplified video frame representation.

This class provides a simplified representation of a video frame with BGR pixel data and timestamp information.

Attributes:

bgr_pixels instance-attribute

bgr_pixels: BGRBuffer

BGR pixel data as a NumPy array.

timestamp_unix_seconds instance-attribute

timestamp_unix_seconds: float

Timestamp in seconds since Unix epoch.

Check the whole example code here
stream_scene_camera_video.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import cv2
import numpy as np

# Workaround for https://github.com/opencv/opencv/issues/21952
cv2.imshow("cv/av bug", np.zeros(1))
cv2.destroyAllWindows()

from pupil_labs.realtime_api.simple import discover_one_device  # noqa: E402


def main():
    # 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)

    print(f"Connecting to {device}...")

    try:
        while True:
            bgr_pixels, frame_datetime = device.receive_scene_video_frame()
            draw_time(bgr_pixels, frame_datetime)
            cv2.imshow("Scene Camera - Press ESC to quit", bgr_pixels)
            if cv2.waitKey(1) & 0xFF == 27:
                break
    except KeyboardInterrupt:
        pass
    finally:
        print("Stopping...")
        device.close()  # explicitly stop auto-update


def draw_time(frame, time):
    frame_txt_font_name = cv2.FONT_HERSHEY_SIMPLEX
    frame_txt_font_scale = 1.0
    frame_txt_thickness = 1

    # first line: frame index
    frame_txt = str(time)

    cv2.putText(
        frame,
        frame_txt,
        (20, 50),
        frame_txt_font_name,
        frame_txt_font_scale,
        (255, 255, 255),
        thickness=frame_txt_thickness,
        lineType=cv2.LINE_8,
    )


if __name__ == "__main__":
    main()

Scene Camera Video with Overlaid Gaze

For additional context about where the wearer was gazing, it's useful to overlay gaze measurements onto the scene camera video stream. Since the scene camera and gaze signal can have different sampling rates, we need to be sure they are matched. For that, you can use (device.receive_matched_scene_video_frame_and_gaze). This receives a pair of scene camera video and gaze data already matched.

frame, gaze = device.receive_matched_scene_video_frame_and_gaze()
Scene camera with gaze overlay
MatchedItem

MatchedItem

Bases: NamedTuple

A matched pair of scene video frame and gaze data.

This class represents a scene video frame and gaze data point that occurred at approximately the same time.

Note

The name MatchedItem is maintained for backward compatibility. It represents a matched pair of scene video frame and gaze data.

Attributes:

frame instance-attribute

Scene video frame.

gaze instance-attribute

Corresponding gaze data.

Check the whole example code here
stream_video_with_overlayed_gaze.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import cv2
import numpy as np

# Workaround for https://github.com/opencv/opencv/issues/21952
cv2.imshow("cv/av bug", np.zeros(1))
cv2.destroyAllWindows()

from pupil_labs.realtime_api.simple import discover_one_device  # noqa: E402


def main():
    # 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)

    print(f"Connecting to {device}...")

    try:
        while True:
            frame, gaze = device.receive_matched_scene_video_frame_and_gaze()
            cv2.circle(
                frame.bgr_pixels,
                (int(gaze.x), int(gaze.y)),
                radius=80,
                color=(0, 0, 255),
                thickness=15,
            )

            cv2.imshow("Scene camera with gaze overlay", frame.bgr_pixels)
            if cv2.waitKey(1) & 0xFF == 27:
                break
    except KeyboardInterrupt:
        pass
    finally:
        print("Stopping...")
        device.close()  # explicitly stop auto-update


if __name__ == "__main__":
    main()

Scene Camera Video with Overlayed Eyes Video and Gaze Circle

Neon

It might also be useful to overlay the eye camera video frames, e.g. if you want to manually inspect the eye data or blinking behaviour. This can be achieved using the device.receive_matched_scene_and_eyes_video_frames_and_gaze method.

matched = device.receive_matched_scene_and_eyes_video_frames_and_gaze()
Scene camera with gaze and eyes overlay
MatchedGazeEyesSceneItem

MatchedGazeEyesSceneItem

Bases: NamedTuple

A matched triplet of scene video frame, eye video frame, and gaze data.

This class represents scene and eye video frames along with gaze data that occurred at approximately the same time.

Attributes:

eyes instance-attribute

Eye camera video frame.

gaze instance-attribute

Corresponding gaze data.

scene instance-attribute

Scene video frame.

Check the whole example code here
stream_scene_eyes_and_gaze.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import cv2
import numpy as np

# Workaround for https://github.com/opencv/opencv/issues/21952
cv2.imshow("cv/av bug", np.zeros(1))
cv2.destroyAllWindows()

from pupil_labs.realtime_api.simple import discover_one_device  # noqa: E402


def main():
    # 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)

    print(f"Connecting to {device}...")

    try:
        while True:
            matched = device.receive_matched_scene_and_eyes_video_frames_and_gaze()
            if not matched:
                print(
                    "Not able to find a match! Note: Pupil Invisible does not support "
                    "streaming eyes video"
                )
                continue

            cv2.circle(
                matched.scene.bgr_pixels,
                (int(matched.gaze.x), int(matched.gaze.y)),
                radius=80,
                color=(0, 0, 255),
                thickness=15,
            )

            # Render eyes video into the scene video
            height, width, _ = matched.eyes.bgr_pixels.shape
            matched.scene.bgr_pixels[:height, :width, :] = matched.eyes.bgr_pixels

            cv2.imshow(
                "Scene camera with eyes and gaze overlay", matched.scene.bgr_pixels
            )
            if cv2.waitKey(1) & 0xFF == 27:
                break
    except KeyboardInterrupt:
        pass
    finally:
        print("Stopping...")
        device.close()  # explicitly stop auto-update


if __name__ == "__main__":
    main()