chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""Logs a very complex/long Annotation context for the purpose of testing/debugging the related Selection Panel UI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
import rerun as rr
|
||||
from rerun.datatypes import ClassDescription
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
rr.script_setup(args, "rerun_example_annotation_context_ui_stress")
|
||||
|
||||
|
||||
annotation_context = rr.AnnotationContext([
|
||||
ClassDescription(
|
||||
info=(0, "class_info", (255, 0, 0)),
|
||||
keypoint_annotations=[(i, f"keypoint {i}", (255, 255 - i, 0)) for i in range(100)],
|
||||
keypoint_connections=[(i, 99 - i) for i in range(50)],
|
||||
),
|
||||
ClassDescription(
|
||||
info=(1, "another_class_info", (255, 0, 255)),
|
||||
keypoint_annotations=[(i, f"keypoint {i}", (255, 255, i)) for i in range(100)],
|
||||
keypoint_connections=[(0, 2), (1, 2), (2, 3)],
|
||||
),
|
||||
])
|
||||
|
||||
# log two of those to test multi-selection
|
||||
rr.log("annotation1", annotation_context)
|
||||
rr.log("annotation2", annotation_context)
|
||||
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from numpy.random import default_rng
|
||||
|
||||
import rerun as rr
|
||||
from rerun.blueprint import (
|
||||
Blueprint,
|
||||
Grid,
|
||||
Horizontal,
|
||||
Spatial2DView,
|
||||
Spatial3DView,
|
||||
Tabs,
|
||||
TimePanel,
|
||||
Vertical,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
blueprint = Blueprint(
|
||||
Vertical(
|
||||
Spatial3DView(origin="/test1"),
|
||||
Horizontal(
|
||||
Tabs(
|
||||
Spatial3DView(origin="/test1"),
|
||||
Spatial2DView(origin="/test2"),
|
||||
),
|
||||
Grid(
|
||||
Spatial3DView(origin="/test1"),
|
||||
Spatial2DView(origin="/test2"),
|
||||
Spatial3DView(origin="/test1"),
|
||||
Spatial2DView(origin="/test2"),
|
||||
grid_columns=3,
|
||||
column_shares=[1, 1, 1],
|
||||
),
|
||||
column_shares=[1, 2],
|
||||
),
|
||||
row_shares=[2, 1],
|
||||
),
|
||||
TimePanel(state="collapsed"),
|
||||
)
|
||||
|
||||
rr.init(
|
||||
"rerun_example_blueprint_test",
|
||||
spawn=True,
|
||||
default_blueprint=blueprint,
|
||||
)
|
||||
|
||||
rng = default_rng(12345)
|
||||
positions = rng.uniform(-5, 5, size=[10, 3])
|
||||
colors = rng.uniform(0, 255, size=[10, 3])
|
||||
radii = rng.uniform(0, 1, size=[10])
|
||||
|
||||
rr.log("test1", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
rr.log("test2", rr.Points2D(positions[:, :2], colors=colors, radii=radii))
|
||||
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
blueprint = rrb.Blueprint(
|
||||
rrb.Spatial3DView(origin="/test1"),
|
||||
rrb.TimePanel(state="collapsed"),
|
||||
rrb.SelectionPanel(state="collapsed"),
|
||||
rrb.BlueprintPanel(state="collapsed"),
|
||||
)
|
||||
|
||||
blueprint.save("rerun_example_blueprint_test.rbl")
|
||||
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
blueprint = rrb.Blueprint(
|
||||
rrb.Spatial3DView(origin="/test1"),
|
||||
rrb.TimePanel(state="collapsed"),
|
||||
rrb.SelectionPanel(state="collapsed"),
|
||||
rrb.BlueprintPanel(state="collapsed"),
|
||||
)
|
||||
|
||||
blueprint.spawn("rerun_example_blueprint_test")
|
||||
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
A collection of delightfully unique chunk specimens, for science.
|
||||
|
||||
IMPORTANT: the viewer should be set with `RERUN_CHUNK_MAX_BYTES=0` to disable the compactor.
|
||||
|
||||
To add new specimens to the zoo, add a function whose name starts with "specimen_".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def frame_times(t: int | Sequence[int], *args: int) -> list[rr.TimeColumn]:
|
||||
if isinstance(t, int):
|
||||
t = [t]
|
||||
else:
|
||||
t = list(t)
|
||||
if args:
|
||||
t.extend(args)
|
||||
return [rr.TimeColumn("frame", sequence=t)]
|
||||
|
||||
|
||||
def set_frame_time(t: int) -> None:
|
||||
rr.set_time("frame", sequence=t)
|
||||
|
||||
|
||||
def specimen_two_rows_span_two_chunks() -> None:
|
||||
"""Two rows spanning two chunks."""
|
||||
|
||||
rr.send_columns("/rows_span_two_chunks", frame_times(0, 2), rr.Points2D.columns(positions=[(0, 1), (2, 3)]))
|
||||
rr.send_columns("/rows_span_two_chunks", frame_times(0, 2), rr.Points2D.columns(radii=[10, 11]))
|
||||
|
||||
|
||||
def specimen_two_rows_span_two_chunks_sparse() -> None:
|
||||
"""Two rows spanning two chunks with partially matching timestamps (so sparse results)."""
|
||||
|
||||
rr.send_columns(
|
||||
"/rows_span_two_chunks_sparse",
|
||||
frame_times(0, 2, 3),
|
||||
rr.Points2D.columns(positions=[(0, 1), (2, 3), (4, 5)]),
|
||||
)
|
||||
rr.send_columns("/rows_span_two_chunks_sparse", frame_times(0, 2, 4), rr.Points2D.columns(radii=[10, 11, 12]))
|
||||
|
||||
|
||||
def specimen_archetype_with_clamp_join_semantics() -> None:
|
||||
"""Single row of an archetype with clamp join semantics (Points2D)."""
|
||||
rr.send_columns(
|
||||
"/archetype_with_clamp_join_semantics",
|
||||
frame_times(0),
|
||||
[
|
||||
*rr.Points2D.columns(
|
||||
positions=[(i, i) for i in range(10)],
|
||||
).partition([10]),
|
||||
*rr.Points2D.columns(radii=2),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def specimen_archetype_with_latest_at_semantics() -> None:
|
||||
"""Archetype spread over a multi-row chunk and a single row chunk, with latest-at semantics."""
|
||||
rr.send_columns(
|
||||
"/archetype_chunk_with_latest_at_semantics",
|
||||
frame_times(range(10)),
|
||||
rr.Points2D.columns(positions=[(i, i) for i in range(10)], class_ids=range(10)),
|
||||
)
|
||||
|
||||
set_frame_time(5)
|
||||
rr.log("/archetype_chunk_with_latest_at_semantics", rr.Points2D.from_fields(radii=2))
|
||||
|
||||
|
||||
def specimen_archetype_with_clamp_join_semantics_two_chunks() -> None:
|
||||
"""Single row of an archetype with clamp join semantics (Points2D), across two chunks."""
|
||||
rr.send_columns(
|
||||
"/archetype_with_clamp_join_semantics_two_batches",
|
||||
frame_times(0),
|
||||
rr.Points2D.columns(positions=[(i, i) for i in range(10)]).partition([10]),
|
||||
)
|
||||
|
||||
rr.send_columns(
|
||||
"/archetype_with_clamp_join_semantics_two_batches",
|
||||
frame_times(0),
|
||||
rr.Points2D.columns(radii=2),
|
||||
)
|
||||
|
||||
|
||||
def specimen_archetype_without_clamp_join_semantics() -> None:
|
||||
"""Single row of an archetype without clamp join semantics (Mesh3D)."""
|
||||
rr.send_columns(
|
||||
"/archetype_without_clamp_join_semantics",
|
||||
frame_times(0),
|
||||
[
|
||||
*rr.Mesh3D.columns(
|
||||
vertex_positions=[(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 0, 0)],
|
||||
vertex_colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)],
|
||||
).partition([4]),
|
||||
*rr.Mesh3D.columns(triangle_indices=[(0, 1, 2), (0, 2, 3)]).partition([2]),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def specimen_many_rows_with_mismatched_instance_count() -> None:
|
||||
"""Points2D across many timestamps with varying and mismatch instance counts."""
|
||||
|
||||
# Useful for dataframe view row expansion testing.
|
||||
|
||||
np.random.seed(0)
|
||||
positions_partitions = np.random.randint(
|
||||
3,
|
||||
15,
|
||||
size=100,
|
||||
)
|
||||
batch_size = int(np.sum(positions_partitions))
|
||||
|
||||
# Shuffle the color partitions to induce the mismatch
|
||||
colors_partitions = positions_partitions.copy()
|
||||
np.random.shuffle(colors_partitions)
|
||||
|
||||
positions = np.random.rand(batch_size, 2)
|
||||
colors = np.random.randint(0, 255, size=(batch_size, 4))
|
||||
|
||||
rr.send_columns(
|
||||
"/many_rows_with_mismatched_instance_count",
|
||||
frame_times(range(len(positions_partitions))),
|
||||
[
|
||||
*rr.Points2D.columns(positions=positions).partition(positions_partitions),
|
||||
*rr.Points2D.columns(colors=colors).partition(colors_partitions),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# TODO(ab): add variants (unordered, overlapping timestamps, etc.)
|
||||
def specimen_scalars_interlaced_in_two_chunks() -> None:
|
||||
"""Scalar column stored in two chunks, with interlaced timestamps."""
|
||||
rr.send_columns(
|
||||
"/scalars_interlaced_in_two_chunks",
|
||||
frame_times(0, 2, 5, 6, 8),
|
||||
rr.Scalars.columns(scalars=[0, 2, 5, 6, 8]),
|
||||
)
|
||||
rr.send_columns(
|
||||
"/scalars_interlaced_in_two_chunks",
|
||||
frame_times(1, 3, 7),
|
||||
rr.Scalars.columns(scalars=[1, 3, 7]),
|
||||
)
|
||||
|
||||
|
||||
def specimen_archetype_chunk_with_clear() -> None:
|
||||
"""Archetype spread on multi-row and single-row chunks, with a `Clear` in the middle."""
|
||||
rr.send_columns(
|
||||
"/archetype_chunk_with_clear",
|
||||
frame_times(range(10)),
|
||||
rr.Points2D.columns(positions=[(i, i) for i in range(10)], class_ids=range(10)),
|
||||
)
|
||||
|
||||
set_frame_time(0)
|
||||
rr.log("/archetype_chunk_with_clear", rr.Points2D.from_fields(radii=2))
|
||||
|
||||
set_frame_time(5)
|
||||
rr.log("/archetype_chunk_with_clear", rr.Clear(recursive=False))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Logs a bunch of chunks of various typologies. Use `RERUN_CHUNK_MAX_BYTES=0`!",
|
||||
)
|
||||
parser.add_argument("--filter", type=str, help="Only run specimens whose name contains this substring")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
rr.script_setup(args, "rerun_example_chunk_zoo", default_blueprint=rr.blueprint.TextDocumentView(origin="/info"))
|
||||
|
||||
# Round up the specimens
|
||||
specimens = [
|
||||
globals()[name]
|
||||
for name in globals()
|
||||
if name.startswith("specimen_") and callable(globals()[name]) and (not args.filter or args.filter in name)
|
||||
]
|
||||
|
||||
specimen_list = "\n".join([f"| {s.__name__.removeprefix('specimen_')} | {s.__doc__} |" for s in specimens])
|
||||
markdown = (
|
||||
"# Chunk Zoo\n\n"
|
||||
"This recording contains a variety of chunks of various typologies, for testing purposes.\n\n"
|
||||
"**IMPORTANT**: The viewer should be set with `RERUN_CHUNK_MAX_BYTES=0` to disable the compactor.\n\n"
|
||||
"### Specimens\n\n"
|
||||
f"| **Item** | **Description** |\n| --- | --- |\n{specimen_list}"
|
||||
)
|
||||
rr.log("info", rr.TextDocument(text=markdown, media_type="text/markdown"), static=True)
|
||||
|
||||
# Set the specimens loose
|
||||
for specimen in specimens:
|
||||
specimen()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Logs all videos in a folder to a single entity in order."""
|
||||
|
||||
# Order is either alphabetical or - if present - by last filename digits parsed as integer.
|
||||
#
|
||||
# Any folder of .mp4 files can be used.
|
||||
# For Rerun internal users, there are examples assets available at
|
||||
# https://github.com/rerun-io/internal-test-assets/tree/main/video
|
||||
#
|
||||
# Things to look out for:
|
||||
# * are all chunks arriving, are things crashing
|
||||
# * is the video playing smooth on video asset transitions
|
||||
# * does seeking across and within video assets work
|
||||
# * does memory usage induced by mp4 parsing stay low over time and doesn't accumluate
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import rerun as rr
|
||||
|
||||
|
||||
# Try sorting by last filename digits parsed as integer.
|
||||
def get_trailing_number(filename: Path) -> int:
|
||||
match = re.search(r"\d+$", filename.stem)
|
||||
return int(match.group()) if match else 0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <path_to_folder>")
|
||||
sys.exit(1)
|
||||
|
||||
rr.init("rerun_example_chunked_video", spawn=True)
|
||||
|
||||
video_folder = Path(sys.argv[1])
|
||||
video_files = [file for file in video_folder.iterdir() if file.suffix == ".mp4"]
|
||||
|
||||
video_files.sort(key=get_trailing_number)
|
||||
|
||||
last_time_ns = 0
|
||||
for file in video_files:
|
||||
print(f"Logging video {file}, start time {last_time_ns}ns")
|
||||
|
||||
rr.set_time("video_time", duration=1e-9 * last_time_ns)
|
||||
|
||||
video_asset = rr.AssetVideo(path=file)
|
||||
rr.log("video", video_asset)
|
||||
|
||||
frame_timestamps_ns = video_asset.read_frame_timestamps_nanos()
|
||||
rr.send_columns(
|
||||
"video",
|
||||
# Note timeline values don't have to be the same as the video timestamps.
|
||||
indexes=[rr.TimeColumn("video_time", duration=1e-9 * (frame_timestamps_ns + last_time_ns))],
|
||||
columns=rr.VideoFrameReference.columns_nanos(frame_timestamps_ns),
|
||||
)
|
||||
last_time_ns += frame_timestamps_ns[-1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Stress test for cross-recording garbage collection.
|
||||
|
||||
Logs many large recordings that contain a lot of large rows.
|
||||
|
||||
Usage:
|
||||
- Start a Rerun Viewer in release mode with 2GiB of memory limit:
|
||||
`cargo r -p rerun-cli --release --no-default-features --features native_viewer -- --memory-limit 2GiB`
|
||||
- Open the dev panel to see what's going on.
|
||||
- Run this script.
|
||||
- You should see recordings coming in and going out in a ringbuffer-like rolling fashion.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from numpy.random import default_rng
|
||||
|
||||
import rerun as rr
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
for i in range(20000000):
|
||||
rr.init("rerun_example_recording_gc", recording_id=f"image-rec-{i}", spawn=True)
|
||||
for _j in range(10000):
|
||||
positions = rng.uniform(-5, 5, size=[10000000, 3])
|
||||
colors = rng.uniform(0, 255, size=[10000000, 3])
|
||||
radii = rng.uniform(0, 1, size=[10000000])
|
||||
rr.log("points", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Stress test for cross-recording garbage collection.
|
||||
|
||||
Logs many large recordings that contain a single large row.
|
||||
|
||||
Usage:
|
||||
- Start a Rerun Viewer in release mode with 2GiB of memory limit:
|
||||
`cargo r -p rerun-cli --release --no-default-features --features native_viewer -- --memory-limit 2GiB`
|
||||
- Open the dev panel to see what's going on.
|
||||
- Run this script.
|
||||
- You should see recordings coming in and going out in a ringbuffer-like rolling fashion.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from numpy.random import default_rng
|
||||
|
||||
import rerun as rr
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
for i in range(20000000):
|
||||
rr.init("rerun_example_recording_gc", recording_id=f"recording-gc-rec-{i}", spawn=True)
|
||||
|
||||
positions = rng.uniform(-5, 5, size=[10000000, 3])
|
||||
colors = rng.uniform(0, 255, size=[10000000, 3])
|
||||
radii = rng.uniform(0, 1, size=[10000000])
|
||||
rr.log("points", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
|
||||
# Sleep because large single row recordings will absolutely destroy the viewer.
|
||||
# TODO(#4185): Investigate this.
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Stress test for cross-recording garbage collection.
|
||||
|
||||
Logs many medium-sized recordings that contain a lot of small-ish rows.
|
||||
|
||||
Usage:
|
||||
- Start a Rerun Viewer in release mode with 500MiB of memory limit:
|
||||
`cargo r -p rerun-cli --release --no-default-features --features native_viewer -- --memory-limit 500MiB`
|
||||
- Open the dev panel to see what's going on.
|
||||
- Run this script.
|
||||
- You should see recordings coming in and going out in a ringbuffer-like rolling fashion.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from numpy.random import default_rng
|
||||
|
||||
import rerun as rr
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
for i in range(20000000):
|
||||
rr.init("rerun_example_recording_gc", recording_id=f"image-rec-{i}", spawn=True)
|
||||
for j in range(10000):
|
||||
rr.set_time("frame", sequence=j)
|
||||
positions = rng.uniform(-5, 5, size=[1000, 3])
|
||||
colors = rng.uniform(0, 255, size=[1000, 3])
|
||||
radii = rng.uniform(0, 1, size=[1000])
|
||||
rr.log("points", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Stress test for cross-recording garbage collection.
|
||||
|
||||
Logs many medium-sized recordings that contain a single medium-sized row.
|
||||
|
||||
Usage:
|
||||
- Start a Rerun Viewer in release mode with 200MiB of memory limit:
|
||||
`cargo r -p rerun-cli --release --no-default-features --features native_viewer -- --memory-limit 200MiB`
|
||||
- Open the dev panel to see what's going on.
|
||||
- Run this script.
|
||||
- You should see recordings coming in and going out in a ringbuffer-like rolling fashion.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from numpy.random import default_rng
|
||||
|
||||
import rerun as rr
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
for i in range(20000000):
|
||||
rr.init("rerun_example_recording_gc", recording_id=f"recording-gc-rec-{i}", spawn=True)
|
||||
|
||||
positions = rng.uniform(-5, 5, size=[10000, 3])
|
||||
colors = rng.uniform(0, 255, size=[10000, 3])
|
||||
radii = rng.uniform(0, 1, size=[10000])
|
||||
rr.log("points", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
|
||||
# Sleep so we don't run out of TCP sockets.
|
||||
# Timings might need to be adjusted depending on your hardware.
|
||||
time.sleep(0.05)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Stress test for things that tend to GIL deadlock.
|
||||
|
||||
Logs many large recordings that contain a lot of large rows.
|
||||
|
||||
Usage:
|
||||
```
|
||||
python main.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import rerun as rr
|
||||
|
||||
rec = rr.RecordingStream(application_id="test")
|
||||
|
||||
rec = rr.RecordingStream(application_id="test")
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test", make_default=True)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test", make_thread_default=True)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test") # this works
|
||||
rr.set_global_data_recording(rec)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test") # this works
|
||||
rr.set_thread_local_data_recording(rec)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test")
|
||||
rec.spawn()
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test")
|
||||
rr.connect_grpc(recording=rec)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
rec = rr.RecordingStream(application_id="test")
|
||||
rr.memory_recording(recording=rec)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
for _ in range(3):
|
||||
rec = rr.RecordingStream(application_id="test", make_default=False, make_thread_default=False)
|
||||
mem = rec.memory_recording()
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
|
||||
for _ in range(3):
|
||||
rec = rr.RecordingStream(application_id="test", make_default=False, make_thread_default=False)
|
||||
rec.log("test", rr.Points3D([1, 2, 3]))
|
||||
@@ -0,0 +1,79 @@
|
||||
# Python SDK logging benchmarks
|
||||
|
||||
Manual performance benchmarks for the Rerun Python SDK logging pipeline.
|
||||
These are **not** run in CI — they are intended for local profiling and regression checks.
|
||||
|
||||
## Running benchmarks
|
||||
|
||||
From the `rerun/` directory:
|
||||
|
||||
```bash
|
||||
# Run all benchmarks:
|
||||
pixi run py-bench
|
||||
|
||||
# Run only throughput benchmarks:
|
||||
pixi run py-bench -k "not micro"
|
||||
|
||||
# Run only micro-benchmarks:
|
||||
pixi run py-bench -k micro
|
||||
|
||||
# Run a specific benchmark:
|
||||
pixi run py-bench -k "micro_log-Points3D"
|
||||
```
|
||||
|
||||
## Running standalone (for profiling)
|
||||
|
||||
Enter the pixi shell first:
|
||||
|
||||
```bash
|
||||
pixi shell
|
||||
```
|
||||
|
||||
Then run a benchmark directly:
|
||||
|
||||
```bash
|
||||
# Run the throughput benchmark standalone:
|
||||
uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d
|
||||
|
||||
# With options:
|
||||
uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --num-entities 10 --num-time-steps 10000 --static
|
||||
|
||||
# Connect to a running Rerun viewer (start `rerun` first):
|
||||
uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --connect
|
||||
```
|
||||
|
||||
### Profiling with py-spy
|
||||
|
||||
```bash
|
||||
# Generate a flamegraph (on Linux, add --native for native stack traces):
|
||||
sudo PYTHONPATH=rerun_py/rerun_sdk:rerun_py py-spy record -o flamegraph.svg -- \
|
||||
.venv/bin/python -m tests.python.log_benchmark.test_log_benchmark transform3d
|
||||
|
||||
# Then open flamegraph.svg in a browser
|
||||
```
|
||||
|
||||
## Comparing benchmark runs
|
||||
|
||||
Use `--benchmark-save` to save benchmark results:
|
||||
|
||||
```bash
|
||||
# Save a baseline on the current branch:
|
||||
pixi run py-bench -k micro --benchmark-save=before
|
||||
|
||||
# Make changes, rebuild, then save again:
|
||||
pixi run py-bench -k micro --benchmark-save=after
|
||||
```
|
||||
|
||||
Saved results are stored in `.benchmarks/` under the project root and are automatically numbered, e.g. `0001_before` and `0002_after`.
|
||||
|
||||
You can then compare using the `pytest-benchmark` CLI:
|
||||
```
|
||||
uv run pytest-benchmark compare 0001 0002
|
||||
```
|
||||
|
||||
|
||||
## Test files
|
||||
|
||||
- `__init__.py` — Shared data classes (`Point3DInput`, `Transform3DInput`)
|
||||
- `test_log_benchmark.py` — Throughput benchmarks
|
||||
- `test_micro_benchmark.py` — Per-call overhead micro-benchmarks
|
||||
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
MAX_INT64 = 2**63 - 1
|
||||
MAX_INT32 = 2**31 - 1
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Point3DInput:
|
||||
positions: npt.NDArray[np.float32]
|
||||
colors: npt.NDArray[np.uint32]
|
||||
radii: npt.NDArray[np.float32]
|
||||
label: str = "some label"
|
||||
|
||||
@classmethod
|
||||
def prepare(cls, seed: int, num_points: int) -> Point3DInput:
|
||||
rng = np.random.default_rng(seed=seed)
|
||||
|
||||
return cls(
|
||||
positions=rng.integers(0, MAX_INT64, (num_points, 3)).astype(dtype=np.float32),
|
||||
colors=rng.integers(0, MAX_INT32, num_points, dtype=np.uint32),
|
||||
radii=rng.integers(0, MAX_INT64, num_points).astype(dtype=np.float32),
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Transform3DInput:
|
||||
"""Input data for Transform3D benchmark with translation and mat3x3."""
|
||||
|
||||
translations: npt.NDArray[np.float32] # Shape: (num_time_steps, num_entities, 3)
|
||||
mat3x3s: npt.NDArray[np.float32] # Shape: (num_time_steps, num_entities, 3, 3)
|
||||
num_entities: int
|
||||
num_time_steps: int
|
||||
|
||||
@classmethod
|
||||
def prepare(cls, seed: int, num_entities: int, num_time_steps: int) -> Transform3DInput:
|
||||
rng = np.random.default_rng(seed=seed)
|
||||
|
||||
# Generate translations in range [0, 10)
|
||||
translations = rng.random((num_time_steps, num_entities, 3), dtype=np.float32) * 10.0
|
||||
|
||||
# Generate mat3x3 values in range [-1, 1)
|
||||
mat3x3s = rng.random((num_time_steps, num_entities, 3, 3), dtype=np.float32) * 2.0 - 1.0
|
||||
|
||||
return cls(
|
||||
translations=translations,
|
||||
mat3x3s=mat3x3s,
|
||||
num_entities=num_entities,
|
||||
num_time_steps=num_time_steps,
|
||||
)
|
||||
@@ -0,0 +1,253 @@
|
||||
"""Python SDK logging throughput benchmarks. See README.md for usage."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import pytest
|
||||
|
||||
import rerun as rr
|
||||
|
||||
from . import Point3DInput, Transform3DInput
|
||||
|
||||
|
||||
def log_points3d_large_batch(data: Point3DInput) -> None:
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
rr.log(
|
||||
"large_batch",
|
||||
rr.Points3D(positions=data.positions, colors=data.colors, radii=data.radii, labels=data.label),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_points", [50_000_000])
|
||||
def test_bench_points3d_large_batch(benchmark: Any, num_points: int) -> None:
|
||||
rr.init("rerun_example_benchmark_points3d_large_batch")
|
||||
data = Point3DInput.prepare(42, num_points)
|
||||
benchmark(log_points3d_large_batch, data)
|
||||
|
||||
|
||||
def log_points3d_many_individual(data: Point3DInput) -> None:
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
for i in range(data.positions.shape[0]):
|
||||
rr.log(
|
||||
"single_point",
|
||||
rr.Points3D(positions=data.positions[i], colors=data.colors[i], radii=data.radii[i]),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_points", [100_000])
|
||||
def test_bench_points3d_many_individual(benchmark: Any, num_points: int) -> None:
|
||||
rr.init("rerun_example_benchmark_points3d_many_individual")
|
||||
data = Point3DInput.prepare(1337, num_points)
|
||||
benchmark(log_points3d_many_individual, data)
|
||||
|
||||
|
||||
def log_image(image: npt.NDArray[np.uint8], num_log_calls: int) -> None:
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
for _ in range(num_log_calls):
|
||||
rr.log("test_image", rr.Tensor(image))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["image_dimension", "image_channels", "num_log_calls"],
|
||||
[pytest.param(1024, 4, 20_000, id="1024^2px-4channels-20000calls")],
|
||||
)
|
||||
def test_bench_image(benchmark: Any, image_dimension: int, image_channels: int, num_log_calls: int) -> None:
|
||||
rr.init("rerun_example_benchmark_image")
|
||||
|
||||
image = np.zeros((image_dimension, image_dimension, image_channels), dtype=np.uint8)
|
||||
benchmark(log_image, image, num_log_calls)
|
||||
|
||||
|
||||
def test_bench_transforms_over_time_individual(
|
||||
rand_trans: npt.NDArray[np.float32],
|
||||
rand_quats: npt.NDArray[np.float32],
|
||||
rand_scales: npt.NDArray[np.float32],
|
||||
) -> None:
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
num_transforms = rand_trans.shape[0]
|
||||
for i in range(num_transforms):
|
||||
rr.set_time("frame", sequence=i)
|
||||
rr.log(
|
||||
"test_transform",
|
||||
rr.Transform3D(translation=rand_trans[i], rotation=rr.Quaternion(xyzw=rand_quats[i]), scale=rand_scales[i]),
|
||||
)
|
||||
|
||||
|
||||
def test_bench_transforms_over_time_batched(
|
||||
rand_trans: npt.NDArray[np.float32],
|
||||
rand_quats: npt.NDArray[np.float32],
|
||||
rand_scales: npt.NDArray[np.float32],
|
||||
num_transforms_per_batch: int,
|
||||
) -> None:
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
num_transforms = rand_trans.shape[0]
|
||||
num_log_calls = num_transforms // num_transforms_per_batch
|
||||
for i in range(num_log_calls):
|
||||
start = i * num_transforms_per_batch
|
||||
end = (i + 1) * num_transforms_per_batch
|
||||
times = np.arange(start, end)
|
||||
|
||||
rr.send_columns(
|
||||
"test_transform",
|
||||
indexes=[rr.TimeColumn("frame", sequence=times)],
|
||||
columns=rr.Transform3D.columns(
|
||||
translation=rand_trans[start:end],
|
||||
quaternion=rand_quats[start:end],
|
||||
scale=rand_scales[start:end],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["num_transforms", "num_transforms_per_batch"],
|
||||
[
|
||||
pytest.param(10_000, 1),
|
||||
pytest.param(10_000, 100),
|
||||
pytest.param(10_000, 1_000),
|
||||
],
|
||||
)
|
||||
def test_bench_transforms_over_time(benchmark: Any, num_transforms: int, num_transforms_per_batch: int) -> None:
|
||||
rr.init("rerun_example_benchmark_transforms_individual")
|
||||
|
||||
rand_trans = np.array(np.random.rand(num_transforms, 3), dtype=np.float32)
|
||||
rand_quats = np.array(np.random.rand(num_transforms, 4), dtype=np.float32)
|
||||
rand_scales = np.array(np.random.rand(num_transforms, 3), dtype=np.float32)
|
||||
|
||||
print(rand_trans.shape)
|
||||
|
||||
if num_transforms_per_batch > 1:
|
||||
benchmark(
|
||||
test_bench_transforms_over_time_batched,
|
||||
rand_trans,
|
||||
rand_quats,
|
||||
rand_scales,
|
||||
num_transforms_per_batch,
|
||||
)
|
||||
else:
|
||||
benchmark(test_bench_transforms_over_time_individual, rand_trans, rand_quats, rand_scales)
|
||||
|
||||
|
||||
def log_transform3d_translation_mat3x3(data: Transform3DInput, static: bool) -> None:
|
||||
"""Log Transform3D with translation and mat3x3 for each entity at each time step."""
|
||||
# create a new, empty memory sink for the current recording
|
||||
rr.memory_recording()
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
for time_index in range(data.num_time_steps):
|
||||
for entity_index in range(data.num_entities):
|
||||
entity_path = f"transform_{entity_index}"
|
||||
transform = rr.Transform3D(
|
||||
translation=data.translations[time_index, entity_index].tolist(),
|
||||
mat3x3=np.array(data.mat3x3s[time_index, entity_index], dtype=np.float32),
|
||||
)
|
||||
|
||||
if static:
|
||||
rr.log(entity_path, transform, static=True)
|
||||
else:
|
||||
rr.set_time("frame", sequence=time_index)
|
||||
rr.set_time("sim_time", duration=time_index * 0.01)
|
||||
rr.log(entity_path, transform)
|
||||
|
||||
elapsed = time.perf_counter() - start
|
||||
total_log_calls = data.num_entities * data.num_time_steps
|
||||
transforms_per_second = total_log_calls / elapsed
|
||||
print(f"Logged {total_log_calls} transforms in {elapsed:.2f}s ({transforms_per_second:.0f} transforms/second)")
|
||||
|
||||
|
||||
def test_bench_create_transform3d_translation_mat3x3(benchmark: Any) -> None:
|
||||
data = Transform3DInput.prepare(42, 1000, 100)
|
||||
benchmark(create_transform3d_translation_mat3x3, data, False)
|
||||
|
||||
|
||||
def create_transform3d_translation_mat3x3(data: Transform3DInput, static: bool) -> None:
|
||||
"""Just create Transform3D with translation and mat3x3 for each entity at each time step."""
|
||||
start = time.perf_counter()
|
||||
|
||||
transforms = []
|
||||
for time_index in range(data.num_time_steps):
|
||||
for entity_index in range(data.num_entities):
|
||||
transforms.append(
|
||||
rr.Transform3D(
|
||||
translation=data.translations[time_index, entity_index],
|
||||
mat3x3=np.array(data.mat3x3s[time_index, entity_index], dtype=np.float32),
|
||||
)
|
||||
)
|
||||
|
||||
elapsed = time.perf_counter() - start
|
||||
total_log_calls = data.num_entities * data.num_time_steps
|
||||
transforms_per_second = total_log_calls / elapsed
|
||||
print(f"Logged {total_log_calls} transforms in {elapsed:.2f}s ({transforms_per_second:.0f} transforms/second)")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["num_entities", "num_time_steps", "static"],
|
||||
[
|
||||
pytest.param(10, 10_000, False, id="10entities-10000steps-temporal"),
|
||||
pytest.param(10, 10_000, True, id="10entities-10000steps-static"),
|
||||
],
|
||||
)
|
||||
def test_bench_transform3d_translation_mat3x3(
|
||||
benchmark: Any, num_entities: int, num_time_steps: int, static: bool
|
||||
) -> None:
|
||||
rr.init("rerun_example_benchmark_transform3d_translation_mat3x3")
|
||||
data = Transform3DInput.prepare(42, num_entities, num_time_steps)
|
||||
benchmark(log_transform3d_translation_mat3x3, data, static)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Standalone execution (for profiling with py-spy, etc.)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Run logging benchmarks standalone (useful for profiling)")
|
||||
parser.add_argument(
|
||||
"benchmark",
|
||||
choices=["transform3d"],
|
||||
help="Which benchmark to run",
|
||||
)
|
||||
parser.add_argument("--num-entities", type=int, default=10, help="Number of entities")
|
||||
parser.add_argument("--num-time-steps", type=int, default=1_000, help="Number of time steps")
|
||||
parser.add_argument("--static", action="store_true", help="Log as static data")
|
||||
parser.add_argument(
|
||||
"--connect",
|
||||
action="store_true",
|
||||
help="Connect to a running Rerun viewer via gRPC instead of using memory recording",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-only", action="store_true", help="Only create Transform3D instances without logging them"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.benchmark == "transform3d":
|
||||
total_log_calls = args.num_entities * args.num_time_steps
|
||||
print(
|
||||
f"Preparing {total_log_calls} transforms ({args.num_entities} entities x {args.num_time_steps} time steps)…"
|
||||
)
|
||||
rr.init("rerun_example_benchmark_transform3d_translation_mat3x3", spawn=False)
|
||||
if args.connect:
|
||||
print("Connecting to Rerun viewer…")
|
||||
rr.connect_grpc()
|
||||
else:
|
||||
rr.memory_recording()
|
||||
data = Transform3DInput.prepare(42, args.num_entities, args.num_time_steps)
|
||||
print("Logging…")
|
||||
if args.create_only:
|
||||
create_transform3d_translation_mat3x3(data, args.static)
|
||||
else:
|
||||
log_transform3d_translation_mat3x3(data, args.static)
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Per-call overhead micro-benchmarks for the rr.log() pipeline.
|
||||
|
||||
See README.md for usage.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import rerun as rr
|
||||
import rerun_bindings as bindings
|
||||
from rerun._log import _log_components
|
||||
|
||||
ARCHETYPE_CASES = [
|
||||
pytest.param(lambda: rr.Scalars(42.0), id="Scalars"),
|
||||
pytest.param(
|
||||
lambda: rr.Points3D([[1, 2, 3]], colors=[0xFF0000FF], radii=[0.1]),
|
||||
id="Points3D",
|
||||
),
|
||||
pytest.param(
|
||||
lambda: rr.Transform3D(translation=[1, 2, 3], mat3x3=np.eye(3, dtype=np.float32)),
|
||||
id="Transform3D",
|
||||
),
|
||||
pytest.param(
|
||||
lambda: rr.Boxes3D(half_sizes=[[1, 2, 3]], colors=[0xFF0000FF]),
|
||||
id="Boxes3D",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _init() -> None:
|
||||
"""Common setup: init rerun + memory recording."""
|
||||
rr.init("rerun_example_micro_benchmark", spawn=False)
|
||||
rr.memory_recording()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES)
|
||||
def test_bench_micro_construct(benchmark: Any, make_archetype: Any) -> None:
|
||||
_init()
|
||||
benchmark(make_archetype)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES)
|
||||
def test_bench_micro_as_component_batches(benchmark: Any, make_archetype: Any) -> None:
|
||||
_init()
|
||||
archetype = make_archetype()
|
||||
benchmark(archetype.as_component_batches)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES)
|
||||
def test_bench_micro_log_components(benchmark: Any, make_archetype: Any) -> None:
|
||||
_init()
|
||||
batches = make_archetype().as_component_batches()
|
||||
benchmark(_log_components, "test_entity", batches)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES)
|
||||
def test_bench_micro_log_arrow_msg(benchmark: Any, make_archetype: Any) -> None:
|
||||
_init()
|
||||
batches = make_archetype().as_component_batches()
|
||||
instanced = {b.component_descriptor(): b.as_arrow_array() for b in batches if b.as_arrow_array() is not None}
|
||||
benchmark(bindings.log_arrow_msg, "test_entity", components=instanced, static_=False, recording=None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("make_archetype", ARCHETYPE_CASES)
|
||||
def test_bench_micro_log(benchmark: Any, make_archetype: Any) -> None:
|
||||
_init()
|
||||
archetype = make_archetype()
|
||||
benchmark(rr.log, "test_entity", archetype)
|
||||
|
||||
|
||||
def test_bench_micro_set_time(benchmark: Any) -> None:
|
||||
_init()
|
||||
benchmark(rr.set_time, "frame", sequence=42)
|
||||
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Simple benchmark for many transforms over time & space.")
|
||||
rr.script_add_args(parser)
|
||||
|
||||
parser.add_argument("--branching-factor", type=int, default=2, help="How many children each node has")
|
||||
parser.add_argument("--hierarchy-depth", type=int, default=10, help="How many levels of hierarchy we want")
|
||||
parser.add_argument(
|
||||
"--transforms-every-n-levels", type=int, default=2, help="At which level in the hierarchies we add transforms"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-timestamps",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Number of timestamps to log. Stamps shift for each entity a bit.",
|
||||
)
|
||||
parser.add_argument("--transforms-only", action="store_true", help="If set, don't log a point at each leaf")
|
||||
parser.add_argument(
|
||||
"--num-views", type=int, default=6, help="Number of 3D views to create (each will use a different origin)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
rr.script_setup(args, "rerun_example_benchmark_many_transforms")
|
||||
rr.set_time("sim_time", duration=0)
|
||||
|
||||
entity_paths = []
|
||||
call_id = 0
|
||||
|
||||
def log_hierarchy(entity_path: str, level: int) -> None:
|
||||
nonlocal call_id
|
||||
call_id += 1
|
||||
entity_paths.append(entity_path)
|
||||
|
||||
# Add a transform at every 'transforms_every_n_levels' level except root
|
||||
if level > 0 and level % args.transforms_every_n_levels == 0:
|
||||
# Add a static transform that has to be combined in to stress the per-timestamp transform resolve.
|
||||
rr.log(
|
||||
entity_path,
|
||||
# Have to be careful to not override all other transforms, therefore, use `from_fields`.
|
||||
rr.Transform3D.from_fields(
|
||||
mat3x3=[
|
||||
[1.0 + level * 0.1, 0.0, 0.0],
|
||||
[0.0, 1.0 + level * 0.1, 0.0],
|
||||
[0.0, 0.0, 1.0 + level * 0.1],
|
||||
]
|
||||
),
|
||||
static=True,
|
||||
)
|
||||
|
||||
# Add a transform that changes for each timestamp.
|
||||
for i in range(args.num_timestamps):
|
||||
call_id_factor = call_id * 0.02
|
||||
rr.set_time("sim_time", duration=i + call_id_factor)
|
||||
rr.log(
|
||||
entity_path,
|
||||
rr.Transform3D(
|
||||
translation=[i * 0.1 * level + call_id_factor, call_id_factor * level, 0.0],
|
||||
rotation_axis_angle=rr.RotationAxisAngle(axis=(0.0, 1.0, 0.0), degrees=i * 0.1),
|
||||
),
|
||||
)
|
||||
|
||||
if level == args.hierarchy_depth:
|
||||
if not args.transforms_only:
|
||||
# Log a single point at the leaf
|
||||
rr.set_time("sim_time", duration=0)
|
||||
rr.log(entity_path, rr.Points3D([[0.0, 0.0, 0.0]]))
|
||||
return
|
||||
|
||||
for i in range(args.branching_factor):
|
||||
child_path = f"{entity_path}/{i}_at_{level}"
|
||||
log_hierarchy(child_path, level + 1)
|
||||
|
||||
log_hierarchy("root", 0)
|
||||
|
||||
# All views display all entities.
|
||||
rr.send_blueprint(
|
||||
rrb.Blueprint(
|
||||
rrb.Grid(
|
||||
contents=[rrb.Spatial3DView(origin=path, contents="/**") for path in entity_paths[: args.num_views]]
|
||||
),
|
||||
collapse_panels=True, # Collapse panels, so perf is mostly about the data & the views.
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Test showing that memory can be drained from a memory recording as valid RRD files.
|
||||
|
||||
After running:
|
||||
```bash
|
||||
rerun *.rrd
|
||||
```
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
||||
@rr.thread_local_stream("rerun_example_memory_drain")
|
||||
def job(name: str) -> Iterator[tuple[str, int, bytes]]:
|
||||
mem = rr.memory_recording()
|
||||
|
||||
blueprint = rrb.Blueprint(rrb.TextLogView(name="My Logs", origin="test"))
|
||||
|
||||
rr.send_blueprint(blueprint)
|
||||
|
||||
for i in range(5):
|
||||
time.sleep(0.2)
|
||||
rr.log("test", rr.TextLog(f"Job {name} Message {i}"))
|
||||
|
||||
print(f"YIELD {name} {i}")
|
||||
yield (name, i, mem.drain_as_bytes())
|
||||
|
||||
|
||||
def queue_results(generator: Iterator[Any], out_queue: queue.Queue[Any]) -> None:
|
||||
for item in generator:
|
||||
out_queue.put(item)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
results_queue: queue.Queue[tuple[str, int, bytes]] = queue.Queue()
|
||||
|
||||
threads = [
|
||||
threading.Thread(target=queue_results, args=(job("A"), results_queue)),
|
||||
threading.Thread(target=queue_results, args=(job("B"), results_queue)),
|
||||
threading.Thread(target=queue_results, args=(job("C"), results_queue)),
|
||||
]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
while not results_queue.empty():
|
||||
name, i, data = results_queue.get()
|
||||
|
||||
with open(f"output_{name}_{i}.rrd", "wb") as f:
|
||||
f.write(data)
|
||||
Executable
+247
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Plot dashboard stress test.
|
||||
|
||||
Usage:
|
||||
-----
|
||||
```
|
||||
pixi run py-plot-dashboard --help
|
||||
```
|
||||
|
||||
Example:
|
||||
-------
|
||||
```
|
||||
pixi run py-plot-dashboard --num-plots 10 --num-series-per-plot 5 --num-points-per-series 5000 --freq 1000
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr # pip install rerun-sdk
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
parser = argparse.ArgumentParser(description="Plot dashboard stress test")
|
||||
rr.script_add_args(parser)
|
||||
|
||||
parser.add_argument("--num-plots", type=int, default=1, help="How many different plots?")
|
||||
parser.add_argument(
|
||||
"--num-series-per-plot",
|
||||
type=int,
|
||||
default=1,
|
||||
help="How many series in each single plot?",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-points-per-series",
|
||||
type=int,
|
||||
default=100000,
|
||||
help="How many points in each single series?",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--freq",
|
||||
type=float,
|
||||
default=1000,
|
||||
help="Frequency of logging (applies to all series)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--temporal-batch-size",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Number of rows to include in each log call",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--blueprint",
|
||||
action="store_true",
|
||||
help="Setup a blueprint for a 5s window",
|
||||
)
|
||||
|
||||
order = [
|
||||
"forwards",
|
||||
"backwards",
|
||||
"random",
|
||||
]
|
||||
parser.add_argument(
|
||||
"--order",
|
||||
type=str,
|
||||
default=order[0],
|
||||
help="What order to log the data in (applies to all series)",
|
||||
choices=order,
|
||||
)
|
||||
|
||||
series_type = [
|
||||
"gaussian-random-walk",
|
||||
"sin-uniform",
|
||||
]
|
||||
parser.add_argument(
|
||||
"--series-type",
|
||||
type=str,
|
||||
default=series_type[0],
|
||||
choices=series_type,
|
||||
help="The method used to generate time series",
|
||||
)
|
||||
|
||||
|
||||
# TODO(cmc): could have flags to add attributes (color, radius...) to put some more stress
|
||||
# on the line fragmenter.
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
rr.script_setup(args, "rerun_example_plot_dashboard_stress")
|
||||
|
||||
plot_paths = [f"plot_{i}" for i in range(args.num_plots)]
|
||||
series_paths = [f"series_{i}" for i in range(args.num_series_per_plot)]
|
||||
|
||||
if args.blueprint:
|
||||
print("logging blueprint!")
|
||||
rr.send_blueprint(
|
||||
rrb.Blueprint(
|
||||
rrb.Grid(*[
|
||||
rrb.TimeSeriesView(
|
||||
name=p,
|
||||
origin=f"/{p}",
|
||||
time_ranges=rrb.VisibleTimeRanges(
|
||||
timeline="sim_time",
|
||||
start=rrb.TimeRangeBoundary.cursor_relative(offset=rr.TimeInt(seconds=-2.5)),
|
||||
end=rrb.TimeRangeBoundary.cursor_relative(offset=rr.TimeInt(seconds=2.5)),
|
||||
),
|
||||
)
|
||||
for p in plot_paths
|
||||
]),
|
||||
rrb.BlueprintPanel(state="collapsed"),
|
||||
rrb.SelectionPanel(state="collapsed"),
|
||||
),
|
||||
)
|
||||
|
||||
time_per_sim_step = 1.0 / args.freq
|
||||
stop_time = args.num_points_per_series * time_per_sim_step
|
||||
|
||||
if args.order == "forwards":
|
||||
sim_times = np.arange(0, stop_time, time_per_sim_step)
|
||||
elif args.order == "backwards":
|
||||
sim_times = np.arange(0, stop_time, time_per_sim_step)[::-1]
|
||||
else:
|
||||
sim_times = np.random.randint(0, args.num_points_per_series)
|
||||
|
||||
num_series = len(plot_paths) * len(series_paths)
|
||||
time_per_tick = time_per_sim_step
|
||||
scalars_per_tick = num_series
|
||||
if args.temporal_batch_size is not None:
|
||||
time_per_tick *= args.temporal_batch_size
|
||||
scalars_per_tick *= args.temporal_batch_size
|
||||
|
||||
expected_total_freq = args.freq * num_series
|
||||
|
||||
values_shape = (
|
||||
len(sim_times),
|
||||
len(plot_paths),
|
||||
len(series_paths),
|
||||
)
|
||||
if args.series_type == "gaussian-random-walk":
|
||||
values = np.cumsum(np.random.normal(size=values_shape), axis=0)
|
||||
elif args.series_type == "sin-uniform":
|
||||
values = np.sin(np.random.uniform(0, math.pi, size=values_shape))
|
||||
else:
|
||||
# Just generate random numbers rather than crash
|
||||
values = np.random.normal(size=values_shape)
|
||||
|
||||
if args.temporal_batch_size is None:
|
||||
ticks: Any = enumerate(sim_times)
|
||||
else:
|
||||
offsets = range(0, len(sim_times), args.temporal_batch_size)
|
||||
ticks = zip(
|
||||
offsets,
|
||||
(sim_times[offset : offset + args.temporal_batch_size] for offset in offsets),
|
||||
strict=False,
|
||||
)
|
||||
|
||||
time_column = None
|
||||
|
||||
total_start_time = time.time()
|
||||
total_num_scalars = 0
|
||||
|
||||
tick_start_time = time.time()
|
||||
max_load = 0.0
|
||||
|
||||
for index, sim_time in ticks:
|
||||
if args.temporal_batch_size is None:
|
||||
rr.set_time("sim_time", duration=sim_time)
|
||||
else:
|
||||
time_column = rr.TimeColumn("sim_time", duration=sim_time)
|
||||
|
||||
# Log
|
||||
for plot_idx, plot_path in enumerate(plot_paths):
|
||||
for series_idx, series_path in enumerate(series_paths):
|
||||
if args.temporal_batch_size is None:
|
||||
value = values[index, plot_idx, series_idx]
|
||||
rr.log(f"{plot_path}/{series_path}", rr.Scalars(value))
|
||||
else:
|
||||
value_index = slice(index, index + args.temporal_batch_size)
|
||||
rr.send_columns(
|
||||
f"{plot_path}/{series_path}",
|
||||
indexes=[cast("rr.TimeColumn", time_column)],
|
||||
columns=rr.Scalars.columns(scalars=values[value_index, plot_idx, series_idx]),
|
||||
)
|
||||
|
||||
# Measure how long this took and how high the load was.
|
||||
|
||||
elapsed = time.time() - tick_start_time
|
||||
max_load = max(max_load, elapsed / time_per_tick)
|
||||
|
||||
# Throttle
|
||||
|
||||
sleep_duration = time_per_tick - elapsed
|
||||
if sleep_duration > 0.0:
|
||||
sleep_start_time = time.time()
|
||||
time.sleep(sleep_duration)
|
||||
sleep_elapsed = time.time() - sleep_start_time
|
||||
|
||||
# We will very likely be put to sleep for more than we asked for, and therefore need
|
||||
# to pay off that debt in order to meet our frequency goal.
|
||||
sleep_debt = sleep_elapsed - sleep_duration
|
||||
tick_start_time = time.time() - sleep_debt
|
||||
else:
|
||||
tick_start_time = time.time()
|
||||
|
||||
# Progress report
|
||||
#
|
||||
# Must come after throttle since we report every wall-clock second:
|
||||
# If ticks are large & fast, then after each send we run into throttle.
|
||||
# So if this was before throttle, we'd not report the first tick no matter how large it was.
|
||||
|
||||
total_num_scalars += scalars_per_tick
|
||||
total_elapsed = time.time() - total_start_time
|
||||
|
||||
if total_elapsed >= 1.0:
|
||||
print(
|
||||
f"logged {total_num_scalars} scalars over {round(total_elapsed, 3)}s \
|
||||
(freq={round(total_num_scalars / total_elapsed, 3)}Hz, expected={round(expected_total_freq, 3)}Hz, \
|
||||
load={round(max_load * 100.0, 3)}%)",
|
||||
)
|
||||
|
||||
elapsed_debt = total_elapsed % 1 # just keep the fractional part
|
||||
total_start_time = time.time() - elapsed_debt
|
||||
total_num_scalars = 0
|
||||
max_load = 0.0
|
||||
|
||||
if total_num_scalars > 0:
|
||||
total_elapsed = time.time() - total_start_time
|
||||
print(
|
||||
f"logged {total_num_scalars} scalars over {round(total_elapsed, 3)}s \
|
||||
(freq={round(total_num_scalars / total_elapsed, 3)}Hz, expected={round(expected_total_freq, 3)}Hz, \
|
||||
load={round(max_load * 100.0, 3)}%)",
|
||||
)
|
||||
|
||||
rr.script_teardown(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
.datasets/
|
||||
@@ -0,0 +1,30 @@
|
||||

|
||||
|
||||
# Interactive release checklist
|
||||
Welcome to the release checklist.
|
||||
|
||||
Run the checklist with:
|
||||
```
|
||||
pixi run py-build && pixi run uv run tests/python/release_checklist/main.py
|
||||
```
|
||||
|
||||
### When releasing
|
||||
Each check comes in the form a recording that contains:
|
||||
1. a markdown document specifying the user actions to be tested, and
|
||||
2. the actual data required to test these actions.
|
||||
|
||||
To go through the checklist, simply check each recording one by one, and close each one as you go if
|
||||
everything looks alright.
|
||||
|
||||
If you've closed all of them, then things are in a releasable state.
|
||||
|
||||
|
||||
### When developing
|
||||
Every time you make a PR to add a new feature or fix a bug that cannot be tested via automated means
|
||||
for whatever reason, take a moment to think: what actions did I take to manually test this, and should
|
||||
these actions be added as a new check in the checklist?
|
||||
|
||||
If so, create a new recording by creating a new `check_something_something.py` in this folder.
|
||||
Check one of the already existing ones for an example.
|
||||
|
||||
Each recording/check has a dedicated file that gets called from `main.py`; that's it.
|
||||
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Drag-and-drop selection
|
||||
|
||||
The goal of this test is to test the selection behavior of drag-and-drop.
|
||||
|
||||
#### View selects on a successful drop
|
||||
|
||||
1. Select the `cos_curve` entity in the streams tree.
|
||||
2. Drag it to the PLOT view and drop it.
|
||||
3. _Expect_: the entity is added to the view, and the view becomes selected.
|
||||
|
||||
|
||||
#### View doesn't select on a failed drop
|
||||
|
||||
1. Select the `cos_curve` entity again.
|
||||
2. Drag it to the PLOT view (it should be rejected) and drop it.
|
||||
3. _Expect_: nothing happens, and the selection is not changed.
|
||||
|
||||
|
||||
#### Dragging an unselected item doesn't change the selection
|
||||
|
||||
1. Select the PLOT view.
|
||||
2. Drag drag the `line_curve` entity to the PLOT view and drop it.
|
||||
2. _Expect_:
|
||||
- The selection remains unchanged (the PLOT view is still selected).
|
||||
- The `line_curve` entity is added to the view.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def blueprint() -> rrb.BlueprintLike:
|
||||
return rrb.Horizontal(
|
||||
rrb.TimeSeriesView(origin="/", contents=[], name="PLOT"),
|
||||
rrb.TextDocumentView(origin="readme"),
|
||||
)
|
||||
|
||||
|
||||
def log_some_scalar_entities() -> None:
|
||||
times = np.arange(100)
|
||||
curves = [
|
||||
("cos_curve", np.cos(times / 100 * 2 * np.pi)),
|
||||
("line_curve", times / 100 + 0.2),
|
||||
]
|
||||
|
||||
time_column = rr.TimeColumn("frame", sequence=times)
|
||||
|
||||
for path, curve in curves:
|
||||
rr.send_columns(path, indexes=[time_column], columns=rr.Scalars.columns(scalars=curve))
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())
|
||||
rr.send_blueprint(blueprint(), make_active=True, make_default=True)
|
||||
|
||||
log_readme()
|
||||
log_some_scalar_entities()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,117 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import rerun as rr
|
||||
|
||||
README = """\
|
||||
# Hover, Select, Deselect, and Reset
|
||||
|
||||
This checks whether different UIs behave correctly with hover and selection.
|
||||
|
||||
### Hover
|
||||
For each of the views:
|
||||
* Hover the view and verify it shows up as highlighted in the blueprint tree.
|
||||
* Hover the entity and verify it shows up highlighted in the blueprint tree.
|
||||
* For 2D and 3D views the entity itself should be outlined and show a hover element.
|
||||
* For plot view, the line-series will not highlight, but the plot should show info about the point.
|
||||
|
||||
### 2D/3D Select
|
||||
For each of the views:
|
||||
* Click on the background of the view, and verify the view becomes selected.
|
||||
* Click on an entity, and verify the it becomes selected.
|
||||
* For 2D and 3D views the selected instance will not be visible in the blueprint tree.
|
||||
* If you think this is unexpected, create an issue.
|
||||
* Double-click the entity and verify that it becomes selected and highlighted in the blueprint tree.
|
||||
|
||||
### Graph Select
|
||||
Should work just as 2D/3D views.
|
||||
|
||||
### Text view
|
||||
Clicking on a text view (what you're reading right now) should select the view.
|
||||
Hovering the view should work as well.
|
||||
|
||||
### Reset
|
||||
For each of the views:
|
||||
* Zoom and/or pan the view
|
||||
* Double-click the background of the view and verify it resets the view to its default state.
|
||||
|
||||
### Deselect
|
||||
Finally, try hitting escape and check whether that deselects whatever was currently selected and the recording is
|
||||
selected instead.
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def log_plots() -> None:
|
||||
from math import cos, sin, tau
|
||||
|
||||
rr.log("plots/cos", rr.SeriesPoints())
|
||||
|
||||
for t in range(int(tau * 2 * 10.0)):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
|
||||
sin_of_t = sin(float(t) / 10.0)
|
||||
rr.log("plots/sin", rr.Scalars(sin_of_t))
|
||||
|
||||
cos_of_t = cos(float(t) / 10.0)
|
||||
rr.log("plots/cos", rr.Scalars(cos_of_t))
|
||||
|
||||
|
||||
def log_points_3d() -> None:
|
||||
from numpy.random import default_rng
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
positions = rng.uniform(-5, 5, size=[10, 3])
|
||||
colors = rng.uniform(0, 255, size=[10, 3])
|
||||
radii = rng.uniform(0, 1, size=[10])
|
||||
|
||||
rr.log("3D/points", rr.Points3D(positions, colors=colors, radii=radii))
|
||||
|
||||
|
||||
def log_points_2d() -> None:
|
||||
from numpy.random import default_rng
|
||||
|
||||
rng = default_rng(12345)
|
||||
|
||||
positions = rng.uniform(-5, 5, size=[10, 2])
|
||||
colors = rng.uniform(0, 255, size=[10, 3])
|
||||
radii = rng.uniform(0, 1, size=[10])
|
||||
|
||||
rr.log("2D/points", rr.Points2D(positions, colors=colors, radii=radii))
|
||||
|
||||
|
||||
def log_graph() -> None:
|
||||
rr.log("graph", rr.GraphNodes(["a", "b"], labels=["A", "B"]))
|
||||
|
||||
|
||||
def log_map() -> None:
|
||||
rr.log("points", rr.GeoPoints(lat_lon=[[47.6344, 19.1397], [47.6334, 19.1399]], radii=rr.Radius.ui_points(20.0)))
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())
|
||||
|
||||
log_readme()
|
||||
log_plots()
|
||||
log_points_3d()
|
||||
log_points_2d()
|
||||
log_graph()
|
||||
log_map()
|
||||
|
||||
rr.send_blueprint(rr.blueprint.Blueprint(auto_layout=True, auto_views=True), make_active=True, make_default=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
import rerun as rr
|
||||
|
||||
README = """\
|
||||
# LeRobot importer check
|
||||
|
||||
This will load a small v2 LeRobot dataset -- simply make sure that it does.
|
||||
|
||||
The LeRobot dataset loader works by creating a new _recording_ for each episode in the dataset.
|
||||
I.e., you should see exactly 3 recordings, corresponding to episode 0, 1 and 2.
|
||||
"""
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
# NOTE: The LeRobot importer works by creating a new recording for each episode.
|
||||
# That means the `recording_id` needs to be set to "episode_0", otherwise the LeRobot importer
|
||||
# will create a new recording for episode 0, instead of merging it into the existing recording.
|
||||
# If you don't set it, you'll end up with 4 recordings, an empty one and the 3 episodes.
|
||||
rec = rr.script_setup(args, f"{Path(__file__).name}", recording_id="episode_0")
|
||||
|
||||
# load dataset from huggingface
|
||||
dataset_path = Path(__file__).parent / ".datasets" / "v21_apple_storage"
|
||||
snapshot_download(repo_id="rerun/v21_apple_storage", local_dir=dataset_path, repo_type="dataset")
|
||||
|
||||
rec.log_file_from_path(dataset_path)
|
||||
|
||||
# NOTE: This importer works by creating a new recording for each episode.
|
||||
# So that means we need to log the README to each recording.
|
||||
for i in range(3):
|
||||
rec = rr.script_setup(args, Path(__file__).name, recording_id=f"episode_{i}")
|
||||
rec.set_time("frame_index", sequence=0)
|
||||
rec.log("/readme", rr.TextDocument(README), static=True)
|
||||
|
||||
rec.send_blueprint(rr.blueprint.Blueprint(auto_layout=True, auto_views=True), make_active=True, make_default=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
import rerun as rr
|
||||
|
||||
README = """\
|
||||
# LeRobot importer check
|
||||
|
||||
This will load a small v3 LeRobot dataset -- simply make sure that it does.
|
||||
|
||||
The LeRobot dataset loader works by creating a new _recording_ for each episode in the dataset.
|
||||
I.e., you should see exactly 3 recordings, corresponding to episode 0, 1 and 2.
|
||||
"""
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
# NOTE: The LeRobot importer works by creating a new recording for each episode.
|
||||
# That means the `recording_id` needs to be set to "episode_0", otherwise the LeRobot importer
|
||||
# will create a new recording for episode 0, instead of merging it into the existing recording.
|
||||
# If you don't set it, you'll end up with 4 recordings, an empty one and the 3 episodes.
|
||||
rec = rr.script_setup(args, f"{Path(__file__).name}", recording_id="episode_0")
|
||||
|
||||
# load dataset from huggingface
|
||||
dataset_path = Path(__file__).parent / ".datasets" / "v30_apple_storage"
|
||||
snapshot_download(repo_id="rerun/v30_apple_storage", local_dir=dataset_path, repo_type="dataset")
|
||||
|
||||
rec.log_file_from_path(dataset_path)
|
||||
|
||||
# NOTE: This importer works by creating a new recording for each episode.
|
||||
# So that means we need to log the README to each recording.
|
||||
for i in range(3):
|
||||
rec = rr.script_setup(args, Path(__file__).name, recording_id=f"episode_{i}")
|
||||
rec.set_time("frame_index", sequence=0)
|
||||
rec.log("/readme", rr.TextDocument(README), static=True)
|
||||
|
||||
rec.send_blueprint(rr.blueprint.Blueprint(auto_layout=True, auto_views=True), make_active=True, make_default=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Modal scrolling
|
||||
|
||||
* Select the 2D view
|
||||
* Open the Entity Path Filter modal
|
||||
* Make sure it behaves properly, including scrolling
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def log_many_entities() -> None:
|
||||
for i in range(1000):
|
||||
rr.log(f"points/{i}", rr.Points2D([(i, i)]))
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(
|
||||
args,
|
||||
f"{os.path.basename(__file__)}",
|
||||
recording_id=uuid4(),
|
||||
)
|
||||
rr.send_blueprint(
|
||||
rrb.Grid(rrb.Spatial2DView(origin="/"), rrb.TextDocumentView(origin="readme")),
|
||||
make_active=True,
|
||||
make_default=True,
|
||||
)
|
||||
|
||||
log_readme()
|
||||
log_many_entities()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,61 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Multi-entity drag-and-drop
|
||||
|
||||
This test checks that dragging multiple entities to a view correctly adds all entities.
|
||||
|
||||
1. Multi-select `cos_curve` and `line_curve` entities in the streams tree.
|
||||
2. Drag them to the PLOT view.
|
||||
3. _Expect_: both entities are visible in the plot view and each are listed in the view's entity path filter.
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def blueprint() -> rrb.BlueprintLike:
|
||||
return rrb.Vertical(
|
||||
rrb.TextDocumentView(origin="readme"),
|
||||
rrb.TimeSeriesView(origin="/", contents=[], name="PLOT"),
|
||||
)
|
||||
|
||||
|
||||
def log_some_scalar_entities() -> None:
|
||||
times = np.arange(100)
|
||||
curves = [
|
||||
("cos_curve", np.cos(times / 100 * 2 * np.pi)),
|
||||
("line_curve", times / 100 + 0.2),
|
||||
]
|
||||
|
||||
time_column = rr.TimeColumn("frame", sequence=times)
|
||||
|
||||
for path, curve in curves:
|
||||
rr.send_columns(path, indexes=[time_column], columns=rr.Scalars.columns(scalars=curve))
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())
|
||||
rr.send_blueprint(blueprint(), make_default=True, make_active=True)
|
||||
|
||||
log_readme()
|
||||
log_some_scalar_entities()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Notebook
|
||||
|
||||
Make sure to check that Google Colab works properly with the latest release candidate.
|
||||
To do that, go to https://colab.research.google.com/drive/1R9I7s4o6wydQC_zkybqaSRFTtlEaked_
|
||||
change the version at the top to the latest alpha/rc and step through the notebook (running all at once
|
||||
might cause some viewers to stay empty).
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(
|
||||
args,
|
||||
f"{os.path.basename(__file__)}",
|
||||
recording_id=uuid4(),
|
||||
)
|
||||
rr.send_blueprint(rrb.Grid(rrb.TextDocumentView(origin="readme")), make_active=True, make_default=True)
|
||||
|
||||
log_readme()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,196 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Parallelism, caching, reentrancy, etc
|
||||
|
||||
This check simply puts a lot of pressure on all things parallel.
|
||||
|
||||
### Actions
|
||||
|
||||
* Scrub the time cursor like crazy: do your worst!
|
||||
|
||||
If nothing weird happens, you can close this recording.
|
||||
"""
|
||||
|
||||
|
||||
def blueprint() -> rrb.BlueprintLike:
|
||||
return rrb.Grid(
|
||||
rrb.Vertical(*[rrb.TimeSeriesView(name="plots", origin="/plots") for _ in range(3)]),
|
||||
rrb.Vertical(*[
|
||||
rrb.TimeSeriesView(
|
||||
name="plots",
|
||||
origin="/plots",
|
||||
time_ranges=rrb.VisibleTimeRange(
|
||||
"frame_nr",
|
||||
start=rrb.TimeRangeBoundary.cursor_relative(seq=50 - i * 10),
|
||||
end=rrb.TimeRangeBoundary.cursor_relative(seq=50 - i * 10 + 10),
|
||||
),
|
||||
)
|
||||
for i in range(10)
|
||||
]),
|
||||
rrb.Vertical(*[rrb.TextLogView(name="logs", origin="/text") for _ in range(3)]),
|
||||
rrb.Vertical(*[rrb.Spatial2DView(name="2D", origin="/2D") for _ in range(3)]),
|
||||
rrb.Vertical(*[
|
||||
rrb.Spatial2DView(
|
||||
name="2D",
|
||||
origin="/2D",
|
||||
time_ranges=rrb.VisibleTimeRange(
|
||||
"frame_nr",
|
||||
start=rrb.TimeRangeBoundary.infinite(),
|
||||
end=rrb.TimeRangeBoundary.cursor_relative(),
|
||||
),
|
||||
)
|
||||
for _ in range(3)
|
||||
]),
|
||||
rrb.Vertical(*[rrb.Spatial3DView(name="3D", origin="/3D") for _ in range(3)]),
|
||||
rrb.Vertical(*[
|
||||
rrb.Spatial3DView(
|
||||
name="3D",
|
||||
origin="/3D",
|
||||
time_ranges=rrb.VisibleTimeRange(
|
||||
"frame_nr",
|
||||
start=rrb.TimeRangeBoundary.infinite(),
|
||||
end=rrb.TimeRangeBoundary.infinite(),
|
||||
),
|
||||
)
|
||||
for _ in range(3)
|
||||
]),
|
||||
rrb.TextDocumentView(origin="readme"),
|
||||
grid_columns=4,
|
||||
)
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def log_text_logs() -> None:
|
||||
for t in range(100):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
rr.log("text", rr.TextLog("Something good happened", level=rr.TextLogLevel.INFO))
|
||||
rr.log("text", rr.TextLog("Something bad happened", level=rr.TextLogLevel.ERROR))
|
||||
|
||||
|
||||
def log_plots() -> None:
|
||||
from math import cos, sin, tau
|
||||
|
||||
rr.log("plots/sin", rr.SeriesLines(colors=[255, 0, 0], names="sin(0.01t)"), static=True)
|
||||
rr.log("plots/cos", rr.SeriesLines(colors=[0, 255, 0], names="cos(0.01t)"), static=True)
|
||||
|
||||
for t in range(int(tau * 2 * 10.0)):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
|
||||
sin_of_t = sin(float(t) / 10.0)
|
||||
rr.log("plots/sin", rr.Scalars(sin_of_t))
|
||||
|
||||
cos_of_t = cos(float(t) / 10.0)
|
||||
rr.log("plots/cos", rr.Scalars(cos_of_t))
|
||||
|
||||
|
||||
def log_spatial() -> None:
|
||||
for t in range(100):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
|
||||
positions3d = [
|
||||
[math.sin((i + t) * 0.2) * 5, math.cos((i + t) * 0.2) * 5 - 10.0, i * 0.4 - 5.0] for i in range(100)
|
||||
]
|
||||
|
||||
rr.log(
|
||||
"3D/points",
|
||||
rr.Points3D(
|
||||
np.array(positions3d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"3D/lines",
|
||||
rr.LineStrips3D(
|
||||
np.array(positions3d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"3D/arrows",
|
||||
rr.Arrows3D(
|
||||
vectors=np.array(positions3d),
|
||||
radii=0.1,
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"3D/boxes",
|
||||
rr.Boxes3D(
|
||||
half_sizes=np.array(positions3d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
|
||||
positions2d = [[math.sin(i * math.tau / 100.0) * t, math.cos(i * math.tau / 100.0) * t] for i in range(100)]
|
||||
|
||||
rr.log(
|
||||
"2D/points",
|
||||
rr.Points2D(
|
||||
np.array(positions2d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"2D/lines",
|
||||
rr.LineStrips2D(
|
||||
np.array(positions2d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"2D/arrows",
|
||||
rr.Arrows2D(
|
||||
vectors=np.array(positions2d),
|
||||
radii=0.1,
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
rr.log(
|
||||
"2D/boxes",
|
||||
rr.Boxes2D(
|
||||
half_sizes=np.array(positions2d),
|
||||
labels=[str(i) for i in range(t, t + 100)],
|
||||
colors=np.array([[random.randrange(255) for _ in range(3)] for _ in range(t, t + 100)]),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())
|
||||
rr.send_blueprint(blueprint(), make_active=True, make_default=True)
|
||||
|
||||
log_readme()
|
||||
log_text_logs()
|
||||
log_plots()
|
||||
log_spatial()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import rerun as rr
|
||||
|
||||
README = """\
|
||||
# Plot overrides
|
||||
|
||||
This checks whether one can override all properties in a plot.
|
||||
|
||||
### Component overrides
|
||||
|
||||
* Select `plots/cos`.
|
||||
* Under "Visualizer": Override all of its properties with arbitrary values.
|
||||
* Remove all these overrides.
|
||||
|
||||
### Visible time range overrides
|
||||
* Select the `plots` view and confirm it shows:
|
||||
* "Default" selected
|
||||
* Showing "Entire timeline".
|
||||
* Select the `plots/cos` entity and confirm it shows:
|
||||
* "Default" selected
|
||||
* Showing "Entire timeline".
|
||||
* Override the `plots` view Visible time range
|
||||
* Verify all 3 offset modes operate as expected
|
||||
* Override the `plots/cos` entity Visible time range
|
||||
* Verify all 3 offset modes operate as expected
|
||||
|
||||
### Overrides are cloned
|
||||
* After overriding things on both the view and the entity, clone the view.
|
||||
|
||||
If nothing weird happens, you can close this recording.
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def log_plots() -> None:
|
||||
from math import cos, sin, tau
|
||||
|
||||
rr.log("plots/sin", rr.SeriesLines(colors=[255, 0, 0], names="sin(0.01t)"), static=True)
|
||||
rr.log("plots/cos", rr.SeriesLines(colors=[0, 255, 0], names="cos(0.01t)"), static=True)
|
||||
|
||||
for t in range(int(tau * 2 * 10.0)):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
|
||||
sin_of_t = sin(float(t) / 10.0)
|
||||
rr.log("plots/sin", rr.Scalars(sin_of_t))
|
||||
|
||||
cos_of_t = cos(float(t) / 10.0)
|
||||
rr.log("plots/cos", rr.Scalars(cos_of_t))
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())
|
||||
|
||||
log_readme()
|
||||
log_plots()
|
||||
|
||||
rr.send_blueprint(rr.blueprint.Blueprint(auto_layout=True, auto_views=True), make_active=True, make_default=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from argparse import Namespace
|
||||
from uuid import uuid4
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
|
||||
README = """\
|
||||
# Blueprint imports
|
||||
|
||||
This checks that importing a blueprint into an application always applies it, regardless of its AppID.
|
||||
|
||||
You should be seeing a **dataframe view of a plot** on your left, instead of an _actual plot_.
|
||||
"""
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def log_external_blueprint() -> None:
|
||||
import tempfile
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".rbl") as tmp:
|
||||
rrb.Blueprint(
|
||||
rrb.Horizontal(
|
||||
rrb.DataframeView(
|
||||
origin="/",
|
||||
query=rrb.archetypes.DataframeQuery(
|
||||
timeline="frame_nr",
|
||||
apply_latest_at=True,
|
||||
),
|
||||
),
|
||||
rrb.TextDocumentView(origin="readme"),
|
||||
column_shares=[3, 2],
|
||||
),
|
||||
).save("some_unrelated_blueprint_app_id", tmp.name)
|
||||
|
||||
rr.log_file_from_path(tmp.name)
|
||||
|
||||
|
||||
def log_plots() -> None:
|
||||
from math import cos, sin, tau
|
||||
|
||||
def lerp(a, b, t): # type: ignore[no-untyped-def]
|
||||
return a + t * (b - a)
|
||||
|
||||
for t in range(int(tau * 2 * 100.0)):
|
||||
rr.set_time("frame_nr", sequence=t)
|
||||
|
||||
sin_of_t = sin(float(t) / 100.0)
|
||||
rr.log(
|
||||
"trig/sin",
|
||||
rr.Scalars(sin_of_t),
|
||||
rr.SeriesLines(
|
||||
widths=5, colors=lerp(np.array([1.0, 0, 0]), np.array([1.0, 1.0, 0]), (sin_of_t + 1.0) * 0.5)
|
||||
),
|
||||
)
|
||||
|
||||
cos_of_t = cos(float(t) / 100.0)
|
||||
rr.log(
|
||||
"trig/cos",
|
||||
rr.Scalars(cos_of_t),
|
||||
rr.SeriesLines(
|
||||
widths=5,
|
||||
colors=lerp(np.array([0.0, 1.0, 1.0]), np.array([0.0, 0.0, 1.0]), (cos_of_t + 1.0) * 0.5),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def run(args: Namespace) -> None:
|
||||
rr.script_setup(
|
||||
args,
|
||||
f"{os.path.basename(__file__)}",
|
||||
recording_id=uuid4(),
|
||||
)
|
||||
rr.send_blueprint(
|
||||
rrb.Blueprint(
|
||||
rrb.Horizontal(
|
||||
rrb.TimeSeriesView(origin="/"),
|
||||
rrb.TextDocumentView(origin="readme"),
|
||||
column_shares=[3, 2],
|
||||
),
|
||||
rrb.BlueprintPanel(state="collapsed"),
|
||||
rrb.SelectionPanel(state="collapsed"),
|
||||
rrb.TimePanel(state="collapsed"),
|
||||
),
|
||||
make_active=True,
|
||||
make_default=True,
|
||||
)
|
||||
|
||||
log_readme()
|
||||
log_plots()
|
||||
|
||||
log_external_blueprint()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import importlib
|
||||
from os.path import basename, dirname, isfile, join
|
||||
|
||||
import rerun as rr
|
||||
|
||||
|
||||
def log_checks(args: argparse.Namespace) -> None:
|
||||
modules = glob.glob(join(dirname(__file__), "*.py"))
|
||||
modules = [basename(f)[:-3] for f in modules if isfile(f) and basename(f).startswith("check_")]
|
||||
|
||||
for module in modules:
|
||||
m = importlib.import_module(module)
|
||||
m.run(args)
|
||||
|
||||
|
||||
def log_readme() -> None:
|
||||
with open(join(dirname(__file__), "README.md"), encoding="utf8") as f:
|
||||
rr.log("readme", rr.TextDocument(f.read(), media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Interactive release checklist")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
log_checks(args)
|
||||
|
||||
# Log instructions last so that's what people see first.
|
||||
rr.script_setup(args, "instructions")
|
||||
log_readme()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
-r test_api/requirements.txt
|
||||
-r nv12image/requirements.txt
|
||||
pytest-benchmark
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A test series for view coordinates."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import rerun as rr # pip install rerun-sdk
|
||||
|
||||
parser = argparse.ArgumentParser(description="Logs rich data using the Rerun SDK.")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
rr.script_setup(args, "rerun_example_view_coordinates")
|
||||
|
||||
# Log sphere of colored points to make it easier to orient ourselves.
|
||||
# See https://math.stackexchange.com/a/1586185
|
||||
num_points = 5000
|
||||
radius = 8
|
||||
lamd = np.arccos(2 * np.random.rand(num_points) - 1) - np.pi / 2
|
||||
phi = np.random.rand(num_points) * 2 * np.pi
|
||||
x = np.cos(lamd) * np.cos(phi)
|
||||
y = np.cos(lamd) * np.sin(phi)
|
||||
z = np.sin(lamd)
|
||||
unit_sphere_positions = np.transpose([x, y, z])
|
||||
rr.log("world/points", rr.Points3D(unit_sphere_positions * radius, colors=np.abs(unit_sphere_positions), radii=0.01))
|
||||
|
||||
# RGB image that indicates orientation:
|
||||
rgb = np.zeros((50, 100, 3))
|
||||
rgb[0:3, 0:3] = [255, 255, 255]
|
||||
rgb[3:25, 0:3] = [0, 255, 0]
|
||||
rgb[0:3, 3:25] = [255, 0, 0]
|
||||
|
||||
# Depth image for testing depth cloud:
|
||||
# depth = np.ones((50, 100)) * 0.5
|
||||
x, y = np.meshgrid(np.arange(0, 100), np.arange(0, 50))
|
||||
depth = 0.5 + 0.005 * x + 0.25 * np.sin(3.14 * y / 50 / 2)
|
||||
|
||||
|
||||
rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Z_UP)
|
||||
|
||||
|
||||
def log_camera(origin: npt.ArrayLike, label: str, xyz: rr.components.ViewCoordinates, forward: npt.ArrayLike) -> None:
|
||||
[height, width, _channels] = rgb.shape
|
||||
f_len = (height * width) ** 0.5
|
||||
cam_path = f"world/{label}"
|
||||
pinhole_path = f"{cam_path}/{label}"
|
||||
rr.log(f"{cam_path}/indicator", rr.Points3D([0, 0, 0], colors=[255, 255, 255], labels=label))
|
||||
rr.log(cam_path, rr.Transform3D(translation=origin))
|
||||
rr.log(cam_path + "/arrow", rr.Arrows3D(origins=[0, 0, 0], vectors=forward, colors=[255, 255, 255], radii=0.025))
|
||||
rr.log(
|
||||
pinhole_path,
|
||||
rr.Pinhole(
|
||||
width=width,
|
||||
height=height,
|
||||
focal_length=f_len,
|
||||
principal_point=[width * 3 / 4, height * 3 / 4], # test offset principal point
|
||||
camera_xyz=xyz,
|
||||
),
|
||||
)
|
||||
rr.log(f"{pinhole_path}/rgb", rr.Image(rgb))
|
||||
rr.log(f"{pinhole_path}/depth", rr.DepthImage(depth, meter=1.0))
|
||||
|
||||
|
||||
# Log a series of pinhole cameras only differing by their view coordinates and some offset.
|
||||
# Not all possible, but a fair sampling.
|
||||
|
||||
s = 3 # spacing
|
||||
|
||||
log_camera([0, 0, s], "RUB", rr.ViewCoordinates.RUB, forward=[0, 0, -1])
|
||||
|
||||
# All right-handed permutations of RDF:
|
||||
log_camera([s, -s, 0], "RDF", rr.ViewCoordinates.RDF, forward=[0, 0, 1])
|
||||
log_camera([s, 0, 0], "FRD", rr.ViewCoordinates.FRD, forward=[1, 0, 0])
|
||||
log_camera([s, s, 0], "DFR", rr.ViewCoordinates.DFR, forward=[0, 1, 0])
|
||||
|
||||
# All right-handed permutations of LUB:
|
||||
log_camera([0, -s, 0], "ULB", rr.ViewCoordinates.ULB, forward=[0, 0, -1])
|
||||
log_camera([0, 0, 0], "LBU", rr.ViewCoordinates.LBU, forward=[0, -1, 0])
|
||||
log_camera([0, s, 0], "BUL", rr.ViewCoordinates.BUL, forward=[-1, 0, 0])
|
||||
|
||||
# All permutations of LUF:
|
||||
log_camera([-s, -s, 0], "LUF", rr.ViewCoordinates.LUF, forward=[0, 0, 1])
|
||||
log_camera([-s, 0, 0], "FLU", rr.ViewCoordinates.FLU, forward=[1, 0, 0])
|
||||
log_camera([-s, s, 0], "UFL", rr.ViewCoordinates.UFL, forward=[0, 1, 0])
|
||||
|
||||
|
||||
rr.script_teardown(args)
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Playground to test the visible history feature."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
rr.script_setup(args, "rerun_example_visible_history_playground")
|
||||
|
||||
rr.log("bbox", rr.Boxes2D(centers=[50, 3.5], half_sizes=[50, 4.5], colors=[255, 0, 0]), static=True)
|
||||
rr.log("transform", rr.Transform3D(translation=[0, 0, 0]))
|
||||
rr.log("some/nested/pinhole", rr.Pinhole(focal_length=3, width=3, height=3), static=True)
|
||||
|
||||
rr.log("3dworld/depthimage/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), static=True)
|
||||
rr.log("3dworld/image", rr.Transform3D(translation=[0, 1, 0]), static=True)
|
||||
rr.log("3dworld/image/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), static=True)
|
||||
|
||||
date_offset = int(datetime.datetime(year=2023, month=1, day=1).timestamp())
|
||||
|
||||
for i in range(100):
|
||||
rr.set_time("temporal_100day_span", duration=i * 24 * 3600)
|
||||
rr.set_time("temporal_100s_span", duration=i)
|
||||
rr.set_time("temporal_100ms_span", duration=i / 1000)
|
||||
rr.set_time("temporal_100us_span", duration=i / 1000000)
|
||||
|
||||
rr.set_time("temporal_100day_span_date_offset", duration=date_offset + i * 24 * 3600)
|
||||
rr.set_time("temporal_100s_span_date_offset", duration=date_offset + i)
|
||||
rr.set_time("temporal_100ms_span_date_offset", duration=date_offset + i / 1000)
|
||||
rr.set_time("temporal_100us_span_date_offset", duration=date_offset + i / 1000000)
|
||||
|
||||
rr.set_time("temporal_100day_span_zero_centered", duration=(i - 50) * 24 * 3600)
|
||||
rr.set_time("temporal_100s_zero_centered", duration=i - 50)
|
||||
rr.set_time("temporal_100ms_zero_centered", duration=(i - 50) / 1000)
|
||||
rr.set_time("temporal_100us_zero_centered", duration=(i - 50) / 1000000)
|
||||
|
||||
rr.set_time("sequence", sequence=i)
|
||||
rr.set_time("sequence_zero_centered", sequence=(i - 50))
|
||||
rr.set_time("sequence_10k_offset", sequence=10000 + i)
|
||||
rr.set_time("sequence_10k_neg_offset", sequence=-10000 + i)
|
||||
|
||||
rr.log("world/data/nested/point", rr.Points2D([[i, 0], [i, 1]], radii=0.4))
|
||||
rr.log("world/data/nested/point2", rr.Points2D([i, 2], radii=0.4))
|
||||
rr.log("world/data/nested/box", rr.Boxes2D(centers=[i, 1], half_sizes=[0.5, 0.5]))
|
||||
rr.log("world/data/nested/arrow", rr.Arrows3D(origins=[i, 4, 0], vectors=[0, 1.7, 0]))
|
||||
rr.log(
|
||||
"world/data/nested/linestrip",
|
||||
rr.LineStrips2D([[[i - 0.4, 6], [i + 0.4, 6], [i - 0.4, 7], [i + 0.4, 7]], [[i - 0.2, 6.5], [i + 0.2, 6.5]]]),
|
||||
)
|
||||
|
||||
rr.log("world/data/nested/transformed", rr.Transform3D(translation=[i, 0, 0]))
|
||||
rr.log("world/data/nested/transformed/point", rr.Boxes2D(centers=[0, 3], half_sizes=[0.5, 0.5]))
|
||||
|
||||
rr.log("text_log", rr.TextLog(f"hello {i}"))
|
||||
rr.log("scalar", rr.Scalars(math.sin(i / 100 * 2 * math.pi)))
|
||||
|
||||
depth_image = 100 * np.ones((10, 100), dtype=np.float32)
|
||||
depth_image[:, i] = 50
|
||||
rr.log("3dworld/depthimage/pinhole/data", rr.DepthImage(depth_image, meter=100))
|
||||
|
||||
image = 100 * np.ones((10, 100, 3), dtype=np.uint8)
|
||||
image[:, i, :] = [255, 0, 0]
|
||||
rr.log("3dworld/image/pinhole/data", rr.Image(image))
|
||||
|
||||
x_coord = (i - 50) / 5
|
||||
rr.log(
|
||||
"3dworld/mesh",
|
||||
rr.Mesh3D(
|
||||
vertex_positions=[[x_coord, 2, 0], [x_coord, 2, 1], [x_coord, 3, 0]],
|
||||
vertex_colors=[[0, 0, 255], [0, 255, 0], [255, 0, 0]],
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user