chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:05:14 +08:00
commit 2a547be7fe
7904 changed files with 1000926 additions and 0 deletions
View File
@@ -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)
+53
View File
@@ -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))
+12
View File
@@ -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")
+12
View File
@@ -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")
+203
View File
@@ -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)
+53
View File
@@ -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]))
+79
View File
@@ -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
+54
View File
@@ -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()
+62
View File
@@ -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)
+247
View File
@@ -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/
+30
View File
@@ -0,0 +1,30 @@
![Rerun.io](https://static.rerun.io/d0f5443d4803cac65c73fcc064936c09f5e7f208_rerun_banner.png)
# 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)
+39
View File
@@ -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()
+3
View File
@@ -0,0 +1,3 @@
-r test_api/requirements.txt
-r nv12image/requirements.txt
pytest-benchmark
+91
View File
@@ -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]],
),
)