From 7a6ed9b8efff8733c716c092bcd8b225b5aadd3c Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 15 Nov 2024 16:20:36 +0100 Subject: [PATCH 1/3] WIP: AnyValue -> send_columns --- rerun_py/rerun_sdk/rerun/_send_columns.py | 8 +++++++- rerun_py/rerun_sdk/rerun/any_value.py | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/rerun_py/rerun_sdk/rerun/_send_columns.py b/rerun_py/rerun_sdk/rerun/_send_columns.py index 945c1e3d8349..9599ac12d718 100644 --- a/rerun_py/rerun_sdk/rerun/_send_columns.py +++ b/rerun_py/rerun_sdk/rerun/_send_columns.py @@ -7,6 +7,7 @@ from ._baseclasses import Archetype, ComponentBatchMixin, ComponentColumn from ._log import IndicatorComponentBatch +from .any_value import AnyBatchValue from .error_utils import catch_and_log_exceptions from .recording_stream import RecordingStream @@ -120,7 +121,7 @@ def as_arrow_array(self) -> pa.Array: def send_columns( entity_path: str, times: Iterable[TimeColumnLike], - components: Iterable[Union[ComponentBatchMixin, ComponentColumn]], + components: Iterable[Union[ComponentBatchMixin, ComponentColumn, AnyBatchValue]], recording: RecordingStream | None = None, strict: bool | None = None, ) -> None: @@ -227,6 +228,11 @@ def send_columns( component_column = c elif isinstance(c, ComponentBatchMixin): component_column = c.partition([1] * len(c)) # type: ignore[arg-type] + elif isinstance(c, AnyBatchValue): + array = c.as_arrow_array() + if array is None: + raise ValueError(f"Expected a non-null value for component: {component_name}") + component_column = c.partition([1] * len(c.as_arrow_array())) else: raise TypeError( f"Expected either a type that implements the `ComponentMixin` or a `ComponentColumn`, got: {type(c)}" diff --git a/rerun_py/rerun_sdk/rerun/any_value.py b/rerun_py/rerun_sdk/rerun/any_value.py index 9b45fd771139..d6833d318688 100644 --- a/rerun_py/rerun_sdk/rerun/any_value.py +++ b/rerun_py/rerun_sdk/rerun/any_value.py @@ -3,8 +3,10 @@ from typing import Any, Iterable import numpy as np +import numpy.typing as npt import pyarrow as pa +from . import ComponentColumn from ._log import AsComponents, ComponentBatchLike from .error_utils import catch_and_log_exceptions @@ -92,6 +94,25 @@ def component_name(self) -> str: def as_arrow_array(self) -> pa.Array | None: return self.pa_array + def partition(self, lengths: npt.ArrayLike) -> ComponentColumn: + """ + Partitions the component into multiple sub-batches. This wraps the inner arrow + array in a `pyarrow.ListArray` where the different lists have the lengths specified. + + Lengths must sum to the total length of the component batch. + + Parameters + ---------- + lengths : npt.ArrayLike + The offsets to partition the component at. + + Returns + ------- + The partitioned component. + + """ # noqa: D205 + return ComponentColumn(self, lengths) + class AnyValues(AsComponents): """ @@ -165,5 +186,5 @@ def __init__(self, drop_untyped_nones: bool = True, **kwargs: Any) -> None: if batch.is_valid(): self.component_batches.append(batch) - def as_component_batches(self) -> Iterable[ComponentBatchLike]: + def as_component_batches(self) -> Iterable[AnyBatchValue]: return self.component_batches From 98a9f6b67723bbf5ea306960805e9d7bb974600c Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 15 Nov 2024 17:05:17 +0100 Subject: [PATCH 2/3] Update doc & fix lints --- docs/content/howto/logging/send-columns.md | 13 +++++++++- .../all/howto/any_batch_value_send_columns.py | 24 +++++++++++++++++++ .../all/howto/any_values_send_columns.py | 22 +++++++++++++++++ docs/snippets/snippets.toml | 8 +++++++ rerun_py/docs/gen_common_index.py | 5 +++- rerun_py/rerun_sdk/rerun/__init__.py | 1 + rerun_py/rerun_sdk/rerun/_send_columns.py | 2 +- rerun_py/rerun_sdk/rerun/any_value.py | 2 +- 8 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 docs/snippets/all/howto/any_batch_value_send_columns.py create mode 100644 docs/snippets/all/howto/any_values_send_columns.py diff --git a/docs/content/howto/logging/send-columns.md b/docs/content/howto/logging/send-columns.md index cef7745b253a..07c6051e8c08 100644 --- a/docs/content/howto/logging/send-columns.md +++ b/docs/content/howto/logging/send-columns.md @@ -26,7 +26,18 @@ snippet: archetypes/image_send_columns ### Using `send_columns` for logging points -Each row the in the component column can be a batch of data, e.g. a batch of positions. +Each row in the component column can be a batch of data, e.g. a batch of positions. This lets you log the evolution of a point cloud over time efficiently. snippet: archetypes/points3d_send_columns.py + +### Using `send_columns` for logging custom components + +An entire batch of a custom component can be logged at once using [`rr.AnyBatchValue`](https://ref.rerun.io/docs/python/0.20.0/common/custom_data/#rerun.AnyBatchValue?speculative-link) along with `send_column`: + +snippet: howto/any_batch_value_send_columns + +The [`rr.AnyValues`](https://ref.rerun.io/docs/python/0.20.0/common/custom_data/#rerun.AnyValues) class can also be used to log multiple components at a time. +It does not support partitioning, so each component batch and the timeline must hold the same number of elements. + +snippet: howto/any_values_send_columns diff --git a/docs/snippets/all/howto/any_batch_value_send_columns.py b/docs/snippets/all/howto/any_batch_value_send_columns.py new file mode 100644 index 000000000000..5719eac82406 --- /dev/null +++ b/docs/snippets/all/howto/any_batch_value_send_columns.py @@ -0,0 +1,24 @@ +"""Use `AnyBatchValue` and `send_column` to send an entire column of custom data to Rerun.""" + +from __future__ import annotations + +import numpy as np +import rerun as rr + +rr.init("rerun_example_any_batch_value_send_columns", spawn=True) + +N = 64 +timestamps = np.arange(0, N) +one_per_timestamp = np.sin(timestamps / 10.0) +ten_per_timestamp = np.cos(np.arange(0, N * 10) / 100.0) + +rr.send_columns( + "/", + times=[rr.TimeSequenceColumn("step", timestamps)], + components=[ + # log one value per timestamp + rr.AnyBatchValue("custom_component_single", one_per_timestamp), + # log ten values per timestamp + rr.AnyBatchValue("custom_component_multi", ten_per_timestamp).partition([10] * N), + ], +) diff --git a/docs/snippets/all/howto/any_values_send_columns.py b/docs/snippets/all/howto/any_values_send_columns.py new file mode 100644 index 000000000000..5be47bc1ae82 --- /dev/null +++ b/docs/snippets/all/howto/any_values_send_columns.py @@ -0,0 +1,22 @@ +"""Use `AnyValues` and `send_column` to send entire columns of custom data to Rerun.""" + +from __future__ import annotations + +import numpy as np +import rerun as rr + +rr.init("rerun_example_any_values_send_columns", spawn=True) + +timestamps = np.arange(0, 64) + +# Log two components, named "sin" and "cos", with the corresponding values +values = rr.AnyValues( + sin=np.sin(timestamps / 10.0), + cos=np.cos(timestamps / 10.0), +) + +rr.send_columns( + "/", + times=[rr.TimeSequenceColumn("step", timestamps)], + components=values.as_component_batches(), +) diff --git a/docs/snippets/snippets.toml b/docs/snippets/snippets.toml index 3561ae49e49e..6b2a26de0147 100644 --- a/docs/snippets/snippets.toml +++ b/docs/snippets/snippets.toml @@ -62,6 +62,14 @@ views = [ "archetypes/points3d_send_columns" = [ "rust", # Doesn't support partitioned component batches yet. ] +"howto/any_batch_value_send_columns" = [ + "cpp", # Not implemented + "rust", # Not implemented +] +"howto/any_values_send_columns" = [ + "cpp", # Not implemented + "rust", # Not implemented +] "migration/log_line" = [ # Not a complete example -- just a single log line "cpp", "rust", diff --git a/rerun_py/docs/gen_common_index.py b/rerun_py/docs/gen_common_index.py index 63279c8c8a19..a5cff5a1a706 100755 --- a/rerun_py/docs/gen_common_index.py +++ b/rerun_py/docs/gen_common_index.py @@ -145,7 +145,10 @@ class Section: ), Section( title="Custom Data", - class_list=["AnyValues"], + class_list=[ + "AnyValues", + "AnyBatchValues", + ], ), ################################################################################ # These are tables but don't need their own pages since they refer to types that diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index af0722816e44..bd1ebe50f2a6 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -57,6 +57,7 @@ send_columns as send_columns, ) from .any_value import ( + AnyBatchValue as AnyBatchValue, AnyValues as AnyValues, ) from .archetypes import ( diff --git a/rerun_py/rerun_sdk/rerun/_send_columns.py b/rerun_py/rerun_sdk/rerun/_send_columns.py index 9599ac12d718..b96a960d8f03 100644 --- a/rerun_py/rerun_sdk/rerun/_send_columns.py +++ b/rerun_py/rerun_sdk/rerun/_send_columns.py @@ -232,7 +232,7 @@ def send_columns( array = c.as_arrow_array() if array is None: raise ValueError(f"Expected a non-null value for component: {component_name}") - component_column = c.partition([1] * len(c.as_arrow_array())) + component_column = c.partition([1] * len(c.as_arrow_array())) # type: ignore[arg-type] else: raise TypeError( f"Expected either a type that implements the `ComponentMixin` or a `ComponentColumn`, got: {type(c)}" diff --git a/rerun_py/rerun_sdk/rerun/any_value.py b/rerun_py/rerun_sdk/rerun/any_value.py index d6833d318688..f8bcf7d57477 100644 --- a/rerun_py/rerun_sdk/rerun/any_value.py +++ b/rerun_py/rerun_sdk/rerun/any_value.py @@ -186,5 +186,5 @@ def __init__(self, drop_untyped_nones: bool = True, **kwargs: Any) -> None: if batch.is_valid(): self.component_batches.append(batch) - def as_component_batches(self) -> Iterable[AnyBatchValue]: + def as_component_batches(self) -> Iterable[ComponentBatchLike]: return self.component_batches From 51571cefeb4b390c52868041891f73b88b9105f8 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 15 Nov 2024 17:18:12 +0100 Subject: [PATCH 3/3] typo --- rerun_py/docs/gen_common_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rerun_py/docs/gen_common_index.py b/rerun_py/docs/gen_common_index.py index a5cff5a1a706..8b8f8b863c8c 100755 --- a/rerun_py/docs/gen_common_index.py +++ b/rerun_py/docs/gen_common_index.py @@ -147,7 +147,7 @@ class Section: title="Custom Data", class_list=[ "AnyValues", - "AnyBatchValues", + "AnyBatchValue", ], ), ################################################################################