Timestamps

The O3R provides multiple timestamps that correspond to different times in the acquisition and processing of an image.

Acquisition timestamps

The acquisition timestamps refer to the center of each individual HDR exposure time (the O3R uses three exposures, a short, a medium, and a long, in that order). For the RGB image, there is only one exposure, so there is only one timestamp.

These timestamps can be retrieved with the ifm3d library respectively in the TOF_INFO or RGB_INFO buffers.

Receive timestamps

When a frame is received by the end device using the ifm3d API, a timestamp is attached to it (see frame documentation). Depending on the network configuration, the difference between the capture time and the reception time may vary. The ifm3d API appends a timestamp corresponding to the local time of the end device. If the ifm3d API runs on the VPU, the local CPU time of the VPU is used. If ifm3d API runs on the user’s machine, a different time zone may be used depending on the local time settings.

Note that NTP does not take time zones into account, but uses UTC as the reference for all timestamps. The user may need to convert the receive timestamps to UTC to match the NTP-synchronized capture timestamp.

The ifm3d API provides the timestamp of the frame as an array of two datetime objects. The second element of this table is inherited from legacy products that provided multiple timestamps. For the O3R, the user can use the first element of the table.

Synchronization

All timestamps can be synchronized using sNTP.

Example

The example below displays the acquisition and receive timestamps for 2D and 3D frames. It also shows timestamps when NTP synchronization is enabled: this relies on having a NTP server running on the user’s machine.

#############################################
# Copyright 2021-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
# This examples shows how to retrieve image
# timestamps for the O3R.
# In a second part it inspects timestamps for
# a system synchronized with NTP. This relies
# on an internet connection on the local machine.

# Expected setup:
# RGB camera in port 0
# 3D camera in port 2
import time
import datetime
from decimal import Decimal

from ifm3dpy.device import O3R
from ifm3dpy.framegrabber import FrameGrabber, buffer_id
from ifm3dpy.deserialize import TOFInfoV4, RGBInfoV1


def main(local_ip, o3r_ip, port_2d, port_3d):
    PORT_2D = port_2d
    PORT_3D = port_3d
    O3R_IP = o3r_ip
    LOCAL_IP = local_ip
    o3r = O3R(O3R_IP)
    PCIC_PORT_2D = o3r.get([f"/ports/{PORT_2D}/data/pcicTCPPort"])["ports"][PORT_2D][
        "data"
    ]["pcicTCPPort"]
    PCIC_PORT_3D = o3r.get([f"/ports/{PORT_3D}/data/pcicTCPPort"])["ports"][PORT_3D][
        "data"
    ]["pcicTCPPort"]

    fg_2d = FrameGrabber(o3r, pcic_port=PCIC_PORT_2D)
    fg_3d = FrameGrabber(o3r, pcic_port=PCIC_PORT_3D)

    epoch = datetime.datetime.utcfromtimestamp(0)
    local_tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
    print("All the timestamps are displayed in nanoseconds since epoch.")
    print(f"Epoch date in UTC: {epoch}")
    print(f"Local time zone: {local_tz}")
    print("/////////////////////////////////")
    ####################
    # For the 2D camera
    ####################
    fg_2d.start([buffer_id.RGB_INFO])
    [ok, frame] = fg_2d.wait_for_frame().wait_for(1000)

    if not ok:
        raise RuntimeError("Timeout while waiting for a RGB_INFO.")

    fg_2d.stop()

    # Get the acquisition timestamps
    rgb_info = RGBInfoV1().deserialize(frame.get_buffer(buffer_id.RGB_INFO))
    # Convert frame datetime to timestamp since epoch
    frame_ts = frame.timestamps()[0].timestamp()
    # Display results
    print(f"2D acquisition timestamp:           {rgb_info.timestamp_ns}")
    print(f"2D receive timestamp:               {frame_ts*1e9:.0f}")
    print(
        f"Acquisition to reception latency:   {abs(int(rgb_info.timestamp_ns) - int(frame_ts*1e9)):.0f}"
    )
    print("/////////////////////////////////")
    ####################
    # For the 3D camera
    ####################
    fg_3d.start([buffer_id.TOF_INFO])
    [ok, frame] = fg_3d.wait_for_frame().wait_for(1000)

    if not ok:
        raise RuntimeError("Timeout while waiting for a TOF_INFO.")

    fg_3d.stop()
    tof_info = TOFInfoV4().deserialize(frame.get_buffer(buffer_id.TOF_INFO))
    frame_ts = frame.timestamps()[0].timestamp()
    print(
        f"3D acquisition timestamps:                      {tof_info.exposure_timestamps_ns}"
    )
    print(f"3D reception timestamps:                        {frame_ts*1e9:.0f}")
    print(
        f"Last exposure acquisition to reception latency: {abs(int(tof_info.exposure_timestamps_ns[0]) - int(frame_ts*1e9)):.0f}"
    )

    print("/////////////////////////////////")
    ##########################################
    # Inspecting timestamps with sNTP enabled
    ##########################################
    print("Enabling NTP synchronization on the device.")
    print("Make sure you activate the NTP server on your local machine.")
    o3r.set({"device": {"clock": {"sntp": {"availableServers": [f"{LOCAL_IP}"]}}}})
    time.sleep(3)
    curr_local_time = datetime.datetime.now().timestamp() * 1e9
    curr_time_o3r = o3r.get(["/device/clock/currentTime"])["device"]["clock"][
        "currentTime"
    ]
    print(f"Current local timestamp:    {curr_local_time:.0f}")
    print(f"Current time on device:     {int(curr_time_o3r)}")

    fg_3d.start([buffer_id.TOF_INFO])
    [ok, frame] = fg_3d.wait_for_frame().wait_for(1000)

    if not ok:
        raise RuntimeError("Timeout while waiting for a TOF_INFO.")

    fg_3d.stop()

    tof_info = TOFInfoV4().deserialize(frame.get_buffer(buffer_id.TOF_INFO))
    frame_ts = frame.timestamps()[0].timestamp()
    print(
        f"3D acquisition timestamps (with sNTP):          {tof_info.exposure_timestamps_ns}"
    )
    print(f"3D reception timestamps (with sNTP):            {frame_ts*1e9:.0f}")
    print(
        f"Last exposure acquisition to reception latency: {abs(int(tof_info.exposure_timestamps_ns[0]) - int(frame_ts*1e9)):.0f}"
    )


if __name__ == "__main__":
    try:
        # If the example python package was build, import the configuration
        from ovp8xxexamples import config

        O3R_IP = config.IP
        LOCAL_IP = config.LOCAL_IP
        PORT_2D = config.PORT_2D
        PORT_3D = config.PORT_3D

    except ImportError:
        # Otherwise, use default values
        print(
            "Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
        )
        print("Defaulting to the default configuration.")
        O3R_IP = "192.168.0.69"
        LOCAL_IP = "192.168.0.111"
        PORT_2D = "port0"
        PORT_3D = "port2"

    main(local_ip=LOCAL_IP, o3r_ip=O3R_IP, port_2d=PORT_2D, port_3d=PORT_3D)

Expected output:

$ python3 timestamps.py 
All the timestamps are displayed in nanoseconds since epoch.
Epoch date in UTC: 1970-01-01 05:00:00+00:00
Local time zone: EDT
/////////////////////////////////
2D acquisition timestamp:           1692812209955356240
2D receive timestamp:               1692812209955355904
Acquisition to reception latency:   256
/////////////////////////////////
3D acquisition timestamps:                      [1692812210115606937, 1692812210111675942, 1692812210098277180]
3D reception timestamps:                        1692812210115606016
Last exposure acquisition to reception latency: 1024
/////////////////////////////////
Enabling NTP synchronization on the device.
Make sure you activate the NTP server on your local machine.
Current local timestamp:    1692812213285470976
Current time on device:     1692812213291698338
3D acquisition timestamps (with sNTP):          [1692812213310297968, 1692812213306366973, 1692812213292968211]
3D reception timestamps (with sNTP):            1692812213310297088
Last exposure acquisition to reception latency: 768