From a5821a01a20d257ea92099049c446f2f05604460 Mon Sep 17 00:00:00 2001 From: Caroline Pascal Date: Mon, 29 Jun 2026 17:28:06 +0200 Subject: [PATCH] feat(dependencies): bump rerun-sdk to `<0.34.0` (#3763) * Update upper bound to latest rerun-sdk * chore(updae): update rerun logging to use the latest features * chore(format): formatting code * feat(features names and color): improving features names and display colors when replaying an episode * feat(blueprints): switching to blueprints for backwards (and forward) compatibiltiy * feat(blueprints): switching to blueprints for backwards (and forward) compatibiltiy * feat(grid): Leveraging rerun's automatic grid arangement for improved layout * test(update): update tests * chore(colors): removing unreliable colors * chore(simplification): removing no longer needed reshape * chore(imports): cleaning up imports * fix(claude): claude reviews * chore(dependecies): update rerun ceil version * chore(scripts): recover comments * chore(utils): add guard for blueprint * fix(test): style check * fix(deps): typo bound --------- Signed-off-by: Steven Palma Co-authored-by: ntjohnson1 <24689722+ntjohnson1@users.noreply.github.com> Co-authored-by: Steven Palma Co-authored-by: Steven Palma --- pyproject.toml | 2 +- src/lerobot/scripts/lerobot_dataset_viz.py | 65 ++++++++-- src/lerobot/utils/visualization_utils.py | 69 +++++++++-- tests/utils/test_visualization_utils.py | 137 ++++++++++++++++----- uv.lock | 14 +-- 5 files changed, 221 insertions(+), 66 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3961ded19..28a8948b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ hardware = [ "lerobot[deepdiff-dep]", ] viz = [ - "rerun-sdk>=0.24.0,<0.27.0", + "rerun-sdk>=0.24.0,<0.34.0", ] # ── User-facing composite extras (map to CLI scripts) ───── # lerobot-record, lerobot-replay, lerobot-calibrate, lerobot-teleoperate, etc. diff --git a/src/lerobot/scripts/lerobot_dataset_viz.py b/src/lerobot/scripts/lerobot_dataset_viz.py index 21ae1ac9d..22a7208d4 100644 --- a/src/lerobot/scripts/lerobot_dataset_viz.py +++ b/src/lerobot/scripts/lerobot_dataset_viz.py @@ -77,6 +77,21 @@ from lerobot.utils.constants import ACTION, DONE, OBS_STATE, REWARD from lerobot.utils.utils import init_logging +def get_feature_names(dataset: LeRobotDataset, key: str) -> list[str]: + """Return per-dimension names for a feature from the dataset metadata. + + Only flat-list ``names`` metadata is used. Dict-style ``names`` and missing names fall back to ``{key}_{i}`` indices. + """ + feature = dataset.features[key] + dim = feature["shape"][-1] + + names = feature.get("names") + if isinstance(names, list) and len(names) == dim: + return [str(name) for name in names] + + return [f"{key}_{d}" for d in range(dim)] + + def check_chw_float32(frame: torch.Tensor) -> None: """ Check if a frame is a channel-first, float32 tensor. @@ -93,6 +108,31 @@ def to_hwc_uint8_numpy(chw_float32_torch: torch.Tensor) -> np.ndarray: return hwc_uint8_numpy +def build_blueprint_from_dataset(dataset: LeRobotDataset): + """Build a Rerun blueprint laying out camera images and time series for the given dataset. + + Camera images and scalar signals (action, state, reward, done, success) are arranged in a grid. + The per-dimension series names for ``action`` and ``state`` are applied directly + via blueprint overrides. + """ + import rerun as rr + import rerun.blueprint as rrb + + views = [rrb.Spatial2DView(origin=key, name=key) for key in dataset.meta.camera_keys] + + # Style multi-dimensional signals (action, state) with per-dimension names. + for origin, key in ((ACTION, ACTION), ("state", OBS_STATE)): + if key in dataset.features: + names = get_feature_names(dataset, key) + styling = rr.SeriesLines(names=names) + views.append(rrb.TimeSeriesView(origin=origin, name=origin, overrides={origin: styling})) + for key in (DONE, REWARD, "next.success"): + if key in dataset.features: + views.append(rrb.TimeSeriesView(origin=key, name=key)) + + return rrb.Blueprint(rrb.Grid(*views)) + + def to_hwc_uint16_numpy(chw_float32_torch: torch.Tensor) -> np.ndarray: check_chw_float32(chw_float32_torch) hwc_uint16_numpy = chw_float32_torch.round().type(torch.uint16).permute(1, 2, 0).numpy() @@ -137,7 +177,8 @@ def visualize_dataset( import rerun as rr spawn_local_viewer = mode == "local" and not save - rr.init(f"{repo_id}/episode_{episode_index}", spawn=spawn_local_viewer) + blueprint = build_blueprint_from_dataset(dataset) + rr.init(f"{repo_id}/episode_{episode_index}", spawn=spawn_local_viewer, default_blueprint=blueprint) # Manually call python garbage collector after `rr.init` to avoid hanging in a blocking flush # when iterating on a dataloader with `num_workers` > 0 @@ -163,12 +204,13 @@ def visualize_dataset( for batch in tqdm.tqdm(dataloader, total=len(dataloader)): if first_index is None: first_index = batch["index"][0].item() + # iterate over the batch for i in range(len(batch["index"])): rr.set_time("frame_index", sequence=batch["index"][i].item() - first_index) rr.set_time("timestamp", timestamp=batch["timestamp"][i].item()) - # display each camera image + # display each camera image (or depth map) for key in dataset.meta.camera_keys: if key in dataset.meta.depth_keys: depth = to_hwc_uint16_numpy(batch[key][i]) @@ -183,15 +225,13 @@ def visualize_dataset( img_entity = rr.Image(img).compress() if display_compressed_images else rr.Image(img) rr.log(key, entity=img_entity) - # display each dimension of action space (e.g. actuators command) + # display the action space (e.g. actuators command) if ACTION in batch: - for dim_idx, val in enumerate(batch[ACTION][i]): - rr.log(f"{ACTION}/{dim_idx}", rr.Scalars(val.item())) + rr.log(ACTION, rr.Scalars(batch[ACTION][i].numpy())) - # display each dimension of observed state space (e.g. agent position in joint space) + # display the observed state space (e.g. agent position in joint space) if OBS_STATE in batch: - for dim_idx, val in enumerate(batch[OBS_STATE][i]): - rr.log(f"state/{dim_idx}", rr.Scalars(val.item())) + rr.log("state", rr.Scalars(batch[OBS_STATE][i].numpy())) if DONE in batch: rr.log(DONE, rr.Scalars(batch[DONE][i].item())) @@ -202,9 +242,8 @@ def visualize_dataset( if "next.success" in batch: rr.log("next.success", rr.Scalars(batch["next.success"][i].item())) + # save .rrd locally if mode == "local" and save: - # save .rrd locally - output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) repo_id_str = repo_id.replace("/", "_") rrd_path = output_dir / f"{repo_id_str}_episode_{episode_index}.rrd" @@ -212,7 +251,7 @@ def visualize_dataset( return rrd_path elif mode == "distant": - # stop the process from exiting since it is serving the websocket connection + # Keep the process alive while it serves the gRPC/web connection. try: while True: time.sleep(1) @@ -327,12 +366,14 @@ def main(): ) logging.warning("Setting grpc_port to ws_port value.") kwargs["grpc_port"] = kwargs.pop("ws_port") + else: + kwargs.pop("ws_port") # Always remove ws_port from kwargs init_logging() logging.info("Loading dataset") dataset = LeRobotDataset(repo_id, episodes=[args.episode_index], root=root, tolerance_s=tolerance_s) - visualize_dataset(dataset, **vars(args)) + visualize_dataset(dataset, **kwargs) if __name__ == "__main__": diff --git a/src/lerobot/utils/visualization_utils.py b/src/lerobot/utils/visualization_utils.py index e039f7b33..a0f07f0c7 100644 --- a/src/lerobot/utils/visualization_utils.py +++ b/src/lerobot/utils/visualization_utils.py @@ -38,6 +38,8 @@ def init_rerun( require_package("rerun-sdk", extra="viz", import_name="rerun") import rerun as rr + log_rerun_data.blueprint = None # Reset blueprint cache for new session + batch_size = os.getenv("RERUN_FLUSH_NUM_BYTES", "8000") os.environ["RERUN_FLUSH_NUM_BYTES"] = batch_size rr.init(session_name) @@ -63,6 +65,41 @@ def _is_scalar(x): ) +def _build_blueprint(observation_paths: set[str], action_paths: set[str], image_paths: set[str]): + """Build a Rerun blueprint laying out camera images, observation and action scalars in separate views. + + Camera images, observation and action scalars are arranged in a grid. + """ + + # Safe + zero-overhead: `log_rerun_data` already ran the `require_package` guard and imported rerun. + import rerun.blueprint as rrb + + views = [rrb.Spatial2DView(origin=path, name=path) for path in sorted(image_paths)] + + if observation_paths: + views.append(rrb.TimeSeriesView(name="observation", contents=sorted(observation_paths))) + if action_paths: + views.append(rrb.TimeSeriesView(name="action", contents=sorted(action_paths))) + + return rrb.Blueprint(rrb.Grid(*views)) + + +def _ensure_blueprint(observation_paths: set[str], action_paths: set[str], image_paths: set[str]) -> None: + """Build and send the blueprint once, from the first observation and action data.""" + if getattr(log_rerun_data, "blueprint", None) is not None: + return + + if not (observation_paths or action_paths or image_paths): + return + + # Safe + zero-overhead: `log_rerun_data` already ran the `require_package` guard and imported rerun. + import rerun as rr + + blueprint = _build_blueprint(observation_paths, action_paths, image_paths) + log_rerun_data.blueprint = blueprint + rr.send_blueprint(blueprint) + + def log_rerun_data( observation: RobotObservation | None = None, action: RobotAction | None = None, @@ -76,11 +113,15 @@ def log_rerun_data( - Scalars values (floats, ints) are logged as `rr.Scalars`. - 3D NumPy arrays that resemble images (e.g., with 1, 3, or 4 channels first) are transposed from CHW to HWC format, (optionally) compressed to JPEG and logged as `rr.Image` or `rr.EncodedImage`. - - 1D NumPy arrays are logged as a series of individual scalars, with each element indexed. - - Other multi-dimensional arrays are flattened and logged as individual scalars. + - 1D NumPy arrays are logged as a single `rr.Scalars` batch under one entity path, so that every + dimension shares the same view instead of being split across one view per element. + - Multi-dimensional **action** arrays are flattened and logged as a single `rr.Scalars` batch. Keys are automatically namespaced with "observation." or "action." if not already present. + On the first call, a blueprint is built and sent so observation and action scalars get separate + time-series views and each image gets its own spatial view. + Args: observation: An optional dictionary containing observation data to log. action: An optional dictionary containing action data to log. @@ -90,6 +131,10 @@ def log_rerun_data( require_package("rerun-sdk", extra="viz", import_name="rerun") import rerun as rr + observation_paths: set[str] = set() + action_paths: set[str] = set() + image_paths: set[str] = set() + if observation: for k, v in observation.items(): if v is None: @@ -98,20 +143,22 @@ def log_rerun_data( if _is_scalar(v): rr.log(key, rr.Scalars(float(v))) + observation_paths.add(key) elif isinstance(v, np.ndarray): arr = v # Convert CHW -> HWC when needed if arr.ndim == 3 and arr.shape[0] in (1, 3, 4) and arr.shape[-1] not in (1, 3, 4): arr = np.transpose(arr, (1, 2, 0)) if arr.ndim == 1: - for i, vi in enumerate(arr): - rr.log(f"{key}_{i}", rr.Scalars(float(vi))) + rr.log(key, rr.Scalars(arr.astype(float))) + observation_paths.add(key) else: if arr.shape[-1] == 1: img_entity = rr.DepthImage(arr, colormap=rr.components.Colormap.Viridis) else: img_entity = rr.Image(arr).compress() if compress_images else rr.Image(arr) rr.log(key, entity=img_entity, static=True) + image_paths.add(key) if action: for k, v in action.items(): @@ -121,12 +168,10 @@ def log_rerun_data( if _is_scalar(v): rr.log(key, rr.Scalars(float(v))) + action_paths.add(key) elif isinstance(v, np.ndarray): - if v.ndim == 1: - for i, vi in enumerate(v): - rr.log(f"{key}_{i}", rr.Scalars(float(vi))) - else: - # Fall back to flattening higher-dimensional arrays - flat = v.flatten() - for i, vi in enumerate(flat): - rr.log(f"{key}_{i}", rr.Scalars(float(vi))) + # Flatten any (incl. higher-dimensional) array into a single batched Scalars + rr.log(key, rr.Scalars(v.reshape(-1).astype(float))) + action_paths.add(key) + + _ensure_blueprint(observation_paths, action_paths, image_paths) diff --git a/tests/utils/test_visualization_utils.py b/tests/utils/test_visualization_utils.py index 5bd1552db..f62a697cd 100644 --- a/tests/utils/test_visualization_utils.py +++ b/tests/utils/test_visualization_utils.py @@ -30,19 +30,25 @@ from lerobot.utils.constants import OBS_STATE @pytest.fixture def mock_rerun(monkeypatch): """ - Provide a mock `rerun` module so tests don't depend on the real library. - Also reload the module-under-test so it binds to this mock `rr`. + Provide a mock `rerun` module (and `rerun.blueprint` submodule) so tests don't + depend on the real library. Also reload the module-under-test so it binds to + this mock `rr`. """ calls = [] + blueprints = [] class DummyScalar: def __init__(self, value): - self.value = float(value) + # Scalars may be built from a single float or from a 1D array batch. + self.value = value class DummyImage: def __init__(self, arr): self.arr = arr + def compress(self, *a, **k): + return self + class DummyDepthImage: def __init__(self, arr, colormap=None): self.arr = arr @@ -54,6 +60,21 @@ def mock_rerun(monkeypatch): obj = kwargs.pop("entity") calls.append((key, obj, kwargs)) + def dummy_send_blueprint(blueprint, *a, **k): + blueprints.append(blueprint) + + # Mock the `rerun.blueprint` submodule used to build the layout. + dummy_rrb = SimpleNamespace( + Spatial2DView=lambda origin=None, name=None: SimpleNamespace( + kind="Spatial2DView", origin=origin, name=name + ), + TimeSeriesView=lambda name=None, contents=None: SimpleNamespace( + kind="TimeSeriesView", name=name, contents=contents + ), + Grid=lambda *views: SimpleNamespace(kind="Grid", views=list(views)), + Blueprint=lambda root: SimpleNamespace(kind="Blueprint", root=root), + ) + dummy_rr = SimpleNamespace( __name__="rerun", __package__="rerun", @@ -63,20 +84,23 @@ def mock_rerun(monkeypatch): DepthImage=DummyDepthImage, components=SimpleNamespace(Colormap=SimpleNamespace(Viridis="viridis")), log=dummy_log, + send_blueprint=dummy_send_blueprint, init=lambda *a, **k: None, spawn=lambda *a, **k: None, + blueprint=dummy_rrb, ) - # Inject fake module into sys.modules + # Inject fake modules into sys.modules (both `rerun` and `rerun.blueprint`). monkeypatch.setitem(sys.modules, "rerun", dummy_rr) + monkeypatch.setitem(sys.modules, "rerun.blueprint", dummy_rrb) # Now import and reload the module under test, to bind to our rerun mock import lerobot.utils.visualization_utils as vu importlib.reload(vu) - # Expose both the reloaded module and the call recorder - yield vu, calls + # Expose the reloaded module, the call recorder and the captured blueprints + yield vu, calls, blueprints def _keys(calls): @@ -99,8 +123,13 @@ def _kwargs_for(calls, key): raise KeyError(f"Key {key} not found in calls: {calls}") +def _views_by_kind(blueprint, kind): + """Return the views of a given kind from the (single) blueprint's grid.""" + return [v for v in blueprint.root.views if v.kind == kind] + + def test_log_rerun_data_envtransition_scalars_and_image(mock_rerun): - vu, calls = mock_rerun + vu, calls, blueprints = mock_rerun # Build EnvTransition dict obs = { @@ -110,7 +139,7 @@ def test_log_rerun_data_envtransition_scalars_and_image(mock_rerun): } act = { "action.throttle": 0.7, - # 1D array should log individual Scalars with suffix _i + # 1D array should be logged as a single Scalars batch under one entity path "action.vector": np.array([1.0, 2.0], dtype=np.float32), } transition = { @@ -127,31 +156,28 @@ def test_log_rerun_data_envtransition_scalars_and_image(mock_rerun): # - observation.state.temperature -> Scalars # - observation.camera -> Image (HWC) with static=True # - action.throttle -> Scalars - # - action.vector_0, action.vector_1 -> Scalars + # - action.vector -> single Scalars batch (no per-element suffix) expected_keys = { f"{OBS_STATE}.temperature", "observation.camera", "action.throttle", - "action.vector_0", - "action.vector_1", + "action.vector", } assert set(_keys(calls)) == expected_keys # Check scalar types and values temp_obj = _obj_for(calls, f"{OBS_STATE}.temperature") assert type(temp_obj).__name__ == "DummyScalar" - assert temp_obj.value == pytest.approx(25.0) + assert float(temp_obj.value) == pytest.approx(25.0) throttle_obj = _obj_for(calls, "action.throttle") assert type(throttle_obj).__name__ == "DummyScalar" - assert throttle_obj.value == pytest.approx(0.7) + assert float(throttle_obj.value) == pytest.approx(0.7) - v0 = _obj_for(calls, "action.vector_0") - v1 = _obj_for(calls, "action.vector_1") - assert type(v0).__name__ == "DummyScalar" - assert type(v1).__name__ == "DummyScalar" - assert v0.value == pytest.approx(1.0) - assert v1.value == pytest.approx(2.0) + # 1D vector logged as a single batched Scalars under one entity path + vec = _obj_for(calls, "action.vector") + assert type(vec).__name__ == "DummyScalar" + np.testing.assert_allclose(np.asarray(vec.value), [1.0, 2.0]) # Check image handling: CHW -> HWC img_obj = _obj_for(calls, "observation.camera") @@ -159,9 +185,24 @@ def test_log_rerun_data_envtransition_scalars_and_image(mock_rerun): assert img_obj.arr.shape == (10, 20, 3) # transposed assert _kwargs_for(calls, "observation.camera").get("static", False) is True # static=True for images + # A blueprint should have been built and sent exactly once, and cached on the function. + assert len(blueprints) == 1 + assert vu.log_rerun_data.blueprint is blueprints[0] + + bp = blueprints[0] + # One spatial view per image path + spatial_views = _views_by_kind(bp, "Spatial2DView") + assert {v.origin for v in spatial_views} == {"observation.camera"} + + # One time-series view each for observation and action scalars + ts_views = {v.name: v for v in _views_by_kind(bp, "TimeSeriesView")} + assert set(ts_views) == {"observation", "action"} + assert ts_views["observation"].contents == [f"{OBS_STATE}.temperature"] + assert ts_views["action"].contents == ["action.throttle", "action.vector"] + def test_log_rerun_data_plain_list_ordering_and_prefixes(mock_rerun): - vu, calls = mock_rerun + vu, calls, blueprints = mock_rerun # First dict without prefixes treated as observation # Second dict without prefixes treated as action @@ -180,14 +221,12 @@ def test_log_rerun_data_plain_list_ordering_and_prefixes(mock_rerun): # First dict was treated as observation, second as action vu.log_rerun_data(observation=obs_plain, action=act_plain) - # Expected keys with auto-prefixes + # Expected keys with auto-prefixes. The 1D vector is a single batched Scalars. expected = { "observation.temp", "observation.img", "action.throttle", - "action.vec_0", - "action.vec_1", - "action.vec_2", + "action.vec", } logged = set(_keys(calls)) assert logged == expected @@ -195,11 +234,11 @@ def test_log_rerun_data_plain_list_ordering_and_prefixes(mock_rerun): # Scalars t = _obj_for(calls, "observation.temp") assert type(t).__name__ == "DummyScalar" - assert t.value == pytest.approx(1.5) + assert float(t.value) == pytest.approx(1.5) throttle = _obj_for(calls, "action.throttle") assert type(throttle).__name__ == "DummyScalar" - assert throttle.value == pytest.approx(0.3) + assert float(throttle.value) == pytest.approx(0.3) # Image stays HWC img = _obj_for(calls, "observation.img") @@ -207,15 +246,23 @@ def test_log_rerun_data_plain_list_ordering_and_prefixes(mock_rerun): assert img.arr.shape == (5, 6, 3) assert _kwargs_for(calls, "observation.img").get("static", False) is True - # Vectors - for i, val in enumerate([9, 8, 7]): - o = _obj_for(calls, f"action.vec_{i}") - assert type(o).__name__ == "DummyScalar" - assert o.value == pytest.approx(val) + # Vector logged as a single batched Scalars under one entity path + vec = _obj_for(calls, "action.vec") + assert type(vec).__name__ == "DummyScalar" + np.testing.assert_allclose(np.asarray(vec.value), [9, 8, 7]) + + # Blueprint sent once with the expected view layout + assert len(blueprints) == 1 + bp = blueprints[0] + spatial_views = _views_by_kind(bp, "Spatial2DView") + assert {v.origin for v in spatial_views} == {"observation.img"} + ts_views = {v.name: v for v in _views_by_kind(bp, "TimeSeriesView")} + assert ts_views["observation"].contents == ["observation.temp"] + assert ts_views["action"].contents == ["action.throttle", "action.vec"] def test_log_rerun_data_kwargs_only(mock_rerun): - vu, calls = mock_rerun + vu, calls, blueprints = mock_rerun vu.log_rerun_data( observation={"observation.temp": 10.0, "observation.gray": np.zeros((8, 8, 1), dtype=np.uint8)}, @@ -229,7 +276,7 @@ def test_log_rerun_data_kwargs_only(mock_rerun): temp = _obj_for(calls, "observation.temp") assert type(temp).__name__ == "DummyScalar" - assert temp.value == pytest.approx(10.0) + assert float(temp.value) == pytest.approx(10.0) img = _obj_for(calls, "observation.gray") assert type(img).__name__ == "DummyDepthImage" # single-channel -> DepthImage @@ -238,4 +285,26 @@ def test_log_rerun_data_kwargs_only(mock_rerun): a = _obj_for(calls, "action.a") assert type(a).__name__ == "DummyScalar" - assert a.value == pytest.approx(1.0) + assert float(a.value) == pytest.approx(1.0) + + # Blueprint sent once, with a spatial view for the image and time-series views for scalars + assert len(blueprints) == 1 + bp = blueprints[0] + assert {v.origin for v in _views_by_kind(bp, "Spatial2DView")} == {"observation.gray"} + ts_views = {v.name: v for v in _views_by_kind(bp, "TimeSeriesView")} + assert ts_views["observation"].contents == ["observation.temp"] + assert ts_views["action"].contents == ["action.a"] + + +def test_log_rerun_data_blueprint_sent_only_once(mock_rerun): + """The blueprint is built from the first call and not resent on subsequent calls.""" + vu, calls, blueprints = mock_rerun + + vu.log_rerun_data(observation={"temp": 1.0}, action={"a": 2.0}) + assert len(blueprints) == 1 + first_blueprint = vu.log_rerun_data.blueprint + + vu.log_rerun_data(observation={"temp": 3.0}, action={"a": 4.0}) + # Still only one blueprint, and the cached one is unchanged. + assert len(blueprints) == 1 + assert vu.log_rerun_data.blueprint is first_blueprint diff --git a/uv.lock b/uv.lock index e24b1d884..5a76fcbf8 100644 --- a/uv.lock +++ b/uv.lock @@ -3395,7 +3395,7 @@ requires-dist = [ { name = "qwen-vl-utils", marker = "extra == 'qwen-vl-utils-dep'", specifier = ">=0.0.11,<0.1.0" }, { name = "reachy2-sdk", marker = "extra == 'reachy2'", specifier = ">=1.0.15,<1.1.0" }, { name = "requests", specifier = ">=2.32.0,<3.0.0" }, - { name = "rerun-sdk", marker = "extra == 'viz'", specifier = ">=0.24.0,<0.27.0" }, + { name = "rerun-sdk", marker = "extra == 'viz'", specifier = ">=0.24.0,<0.34.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.1" }, { name = "safetensors", specifier = ">=0.4.3,<1.0.0" }, { name = "scikit-image", marker = "extra == 'video-benchmark'", specifier = ">=0.23.2,<0.26.0" }, @@ -5803,21 +5803,21 @@ wheels = [ [[package]] name = "rerun-sdk" -version = "0.26.2" +version = "0.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "numpy" }, { name = "pillow" }, + { name = "psutil" }, { name = "pyarrow" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/4a/767c20e1529d74d9be5b5e55c6c26b63a6918ef3c1709fc422d08a460114/rerun_sdk-0.26.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3d4151c9a3484e112b53d1df90c8fa07397dc7b8bfbb420f09e011eff20f1ef2", size = 93349439, upload-time = "2025-10-27T11:34:10.745Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3d/d8dd0af9c287a85d51ec99d69406cc4b94a9feb1d6f192d3bbcaac9f0b81/rerun_sdk-0.26.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:03977d2aba4966d9a70b682eca196123fda11408fecd733441ede9916c6341e2", size = 86323042, upload-time = "2025-10-27T11:34:17.995Z" }, - { url = "https://files.pythonhosted.org/packages/13/29/53d8d98799ab32418fd4ba6834d6a5749c31f56160d3c87f52a7219887e9/rerun_sdk-0.26.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b6128c3c4f014cae5be18e4d37657c5932d1bcdb2ce5e9d4b488a6eed47f7437", size = 92677274, upload-time = "2025-10-27T11:34:22.601Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/0b9c8f56398b4fc85f8e99279907c258413a297e5603f8f2537fe5806e51/rerun_sdk-0.26.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a6f97b60aaa7d4e8c6124a3f6b97ce9dbd09520050955f0e0bdacb72b0eb106a", size = 98768129, upload-time = "2025-10-27T11:34:27.36Z" }, - { url = "https://files.pythonhosted.org/packages/be/e7/99fc91c0f99f69d7d43e1db0a6f6cb8273ffc02111539bfc1fee43749bad/rerun_sdk-0.26.2-cp39-abi3-win_amd64.whl", hash = "sha256:a493ad6c8357022cba2ca6f8954a81d0faf984b0b22154eb1d976bfc7649df63", size = 84267089, upload-time = "2025-10-27T11:34:32.023Z" }, + { url = "https://files.pythonhosted.org/packages/16/07/380198590b194f1c17052d672865aa4a56e606eae47665f66edfb391999d/rerun_sdk-0.33.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c0115b710289022587bd2e9ecb715f98d1e87dd7d8ac48e053324131d4addc89", size = 125706253, upload-time = "2026-06-22T09:04:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/f8/eb/6741bbf6868175ab126aff58d372066241c6cd2fc1c4f82ed64069728e73/rerun_sdk-0.33.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:14451d31bc7bd0f7c6bcc9c1213ed679ab81b65ecd8b36eae99f738219897dc5", size = 135278374, upload-time = "2026-06-22T09:04:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/fd3b832652900fe3415739e7411c8af8af4c44e9e1a9d55d79e37f7f9094/rerun_sdk-0.33.1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0f87da7a270614074aca37846350f9e257f65081345474748f578c7da64fdeba", size = 139565470, upload-time = "2026-06-22T09:04:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/19/41/339920f5a6734054c07bcae543365a7ef3368ceee3eb67906e2e38bd1d67/rerun_sdk-0.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:b2c2af67f3c2a85b282669d97b52596593fcfdd19bf57c423f18827c837a6e49", size = 120411717, upload-time = "2026-06-22T09:04:42.643Z" }, ] [[package]]