Skip to content

Allow passing seconds/nanoseconds to VideoFrameReference archetype #7833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions docs/snippets/all/archetypes/video_manual_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,11 @@
# Create two entities, showing the same video frozen at different times.
rr.log(
"frame_1s",
rr.VideoFrameReference(
timestamp=rr.components.VideoTimestamp(seconds=1.0),
video_reference="video_asset",
),
rr.VideoFrameReference(seconds=1.0, video_reference="video_asset"),
)
rr.log(
"frame_2s",
rr.VideoFrameReference(
timestamp=rr.components.VideoTimestamp(seconds=2.0),
video_reference="video_asset",
),
rr.VideoFrameReference(seconds=2.0, video_reference="video_asset"),
)

# Send blueprint that shows two 2D views next to each other.
Expand Down
10 changes: 2 additions & 8 deletions rerun_py/rerun_sdk/rerun/archetypes/asset_video.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 6 additions & 44 deletions rerun_py/rerun_sdk/rerun/archetypes/video_frame_reference.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions rerun_py/rerun_sdk/rerun/archetypes/video_frame_reference_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from __future__ import annotations

from typing import Any

from .. import components, datatypes
from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions


class VideoFrameReferenceExt:
"""Extension for [VideoFrameReference][rerun.archetypes.VideoFrameReference]."""

def __init__(
self: Any,
timestamp: datatypes.VideoTimestampLike | None = None,
*,
seconds: float | None = None,
nanoseconds: int | None = None,
video_reference: datatypes.EntityPathLike | None = None,
) -> None:
"""
Create a new instance of the VideoFrameReference archetype.

Parameters
----------
timestamp:
References the closest video frame to this timestamp.

Note that this uses the closest video frame instead of the latest at this timestamp
in order to be more forgiving of rounding errors for inprecise timestamp types.

Mutally exclusive with `seconds` and `nanoseconds`.
seconds:
Sets the timestamp to the given number of seconds.

Mutally exclusive with `timestamp` and `nanoseconds`.
nanoseconds:
Sets the timestamp to the given number of nanoseconds.

Mutally exclusive with `timestamp` and `seconds`.
video_reference:
Optional reference to an entity with a [`archetypes.AssetVideo`][rerun.archetypes.AssetVideo].

If none is specified, the video is assumed to be at the same entity.
Note that blueprint overrides on the referenced video will be ignored regardless,
as this is always interpreted as a reference to the data store.

For a series of video frame references, it is recommended to specify this path only once
at the beginning of the series and then rely on latest-at query semantics to
keep the video reference active.

"""

with catch_and_log_exceptions(context=self.__class__.__name__):
if timestamp is None:
if seconds is None and nanoseconds is None:
raise ValueError("Either timestamp or seconds/nanoseconds must be specified.")
timestamp = components.VideoTimestamp(seconds=seconds, nanoseconds=nanoseconds)
elif seconds is not None or nanoseconds is not None:
raise ValueError("Cannot specify both `timestamp` and `seconds`/`nanoseconds`.")
elif isinstance(timestamp, float):
_send_warning_or_raise("Timestamp can't be specified as a float, use `seconds` instead.")

self.__attrs_init__(
timestamp=timestamp,
video_reference=video_reference,
)
return

self.__attrs_clear__()
2 changes: 2 additions & 0 deletions rerun_py/rerun_sdk/rerun/components/video_timestamp_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def __init__(
if nanoseconds is not None:
raise ValueError("Cannot specify both `seconds` and `nanoseconds`.")
nanoseconds = int(seconds * 1e9 + 0.5)
elif nanoseconds is None:
raise ValueError("Either `seconds` or `nanoseconds` must be specified.")

self.__attrs_init__(timestamp_ns=nanoseconds)
return
Expand Down
33 changes: 33 additions & 0 deletions rerun_py/tests/unit/test_video_frame_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import annotations

import pytest
import rerun as rr


def test_video_frame_reference() -> None:
rr.set_strict_mode(True)

# Too many args:
with pytest.raises(ValueError):
rr.VideoFrameReference(timestamp=rr.components.VideoTimestamp(seconds=12.3), seconds=12.3, nanoseconds=123)
with pytest.raises(ValueError):
rr.VideoFrameReference(seconds=12.3, nanoseconds=123)
with pytest.raises(ValueError):
rr.VideoFrameReference(timestamp=rr.components.VideoTimestamp(seconds=12.3), nanoseconds=123)
with pytest.raises(ValueError):
rr.VideoFrameReference(seconds=12.3, nanoseconds=123)

# No args:
with pytest.raises(ValueError):
rr.VideoFrameReference()

# Correct usages:
assert rr.VideoFrameReference(seconds=12.3).timestamp == rr.components.VideoTimestampBatch(
rr.components.VideoTimestamp(seconds=12.3)
)
assert rr.VideoFrameReference(nanoseconds=123).timestamp == rr.components.VideoTimestampBatch(
rr.components.VideoTimestamp(nanoseconds=123)
)
assert rr.VideoFrameReference(
timestamp=rr.components.VideoTimestamp(nanoseconds=123)
).timestamp == rr.components.VideoTimestampBatch(rr.components.VideoTimestamp(nanoseconds=123))
Loading