Skip to content

Commit d770e43

Browse files
authored
Add send_columns examples for images, fix rust send_columns handling of listarrays (#7172)
### What * New `send_columns` snippets for image, demonstrating to send several images at once efficiently * C++ convenience methods for image format component * Rust slicing of blobs * Rust `send_columns` no longer assumes that an encountered `ListArray` is already the list array for the column * this broke any `send_columns` call for any component batch that's internally a list array Unfortunately, the snippet isn't fully deterministic and can't be compared cross language since the due to the batcher on `log` calls, log may arrive before or after `send_columns` https://github.com/user-attachments/assets/c9af069e-cd64-40c5-b543-54055bea3e42 ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using examples from latest `main` build: [rerun.io/viewer](https://rerun.io/viewer/pr/7172?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [rerun.io/viewer](https://rerun.io/viewer/pr/7172?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG * [x] If applicable, add a new check to the [release checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)! * [x] If have noted any breaking changes to the log API in `CHANGELOG.md` and the migration guide - [PR Build Summary](https://build.rerun.io/pr/7172) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) To run all checks from `main`, comment on the PR with `@rerun-bot full-check`.
1 parent 78d974f commit d770e43

File tree

22 files changed

+467
-29
lines changed

22 files changed

+467
-29
lines changed

crates/store/re_types/definitions/rerun/archetypes/image.fbs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace rerun.archetypes;
2020
/// \cpp If needed, this "borrow-behavior" can be extended by defining your own `rerun::CollectionAdapter`.
2121
///
2222
/// \example archetypes/image_simple image="https://static.rerun.io/image_simple/06ba7f8582acc1ffb42a7fd0006fad7816f3e4e4/1200w.png"
23+
/// \example archetypes/image_send_columns title= image="Advanced usage of `send_columns` to send multiple images at once" image="https://static.rerun.io/image_send_columns/321455161d79e2c45d6f5a6f175d6f765f418897/1200w.png"
2324
table Image (
2425
"attr.rust.derive": "PartialEq",
2526
"attr.cpp.no_field_ctors",

crates/store/re_types/definitions/rerun/components/blob.fbs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace rerun.components;
33
// ---
44

55
/// A binary blob of data.
6+
/// \rs Ref-counted internally and therefore cheap to clone.
67
table Blob (
78
"attr.arrow.transparent",
89
"attr.python.aliases": "bytes, npt.NDArray[np.uint8]",

crates/store/re_types/definitions/rerun/datatypes/blob.fbs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace rerun.datatypes;
33
// ---
44

55
/// A binary blob of data.
6+
/// \rs Ref-counted internally and therefore cheap to clone.
67
table Blob (
78
"attr.docs.unreleased",
89
"attr.arrow.transparent",

crates/store/re_types/src/archetypes/image.rs

+57-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/store/re_types/src/components/blob.rs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use super::ImageFormat;
2+
use crate::datatypes::{self, ChannelDatatype, ColorModel, PixelFormat};
3+
4+
impl ImageFormat {
5+
/// Create a new depth image format with the given resolution and datatype.
6+
#[inline]
7+
pub fn depth([width, height]: [u32; 2], datatype: ChannelDatatype) -> Self {
8+
datatypes::ImageFormat::depth([width, height], datatype).into()
9+
}
10+
11+
/// Create a new segmentation image format with the given resolution and datatype.
12+
#[inline]
13+
pub fn segmentation([width, height]: [u32; 2], datatype: ChannelDatatype) -> Self {
14+
datatypes::ImageFormat::segmentation([width, height], datatype).into()
15+
}
16+
17+
/// Create a new rgb image format with 8 bit per channel with the given resolution.
18+
#[inline]
19+
pub fn rgb8([width, height]: [u32; 2]) -> Self {
20+
datatypes::ImageFormat::rgb8([width, height]).into()
21+
}
22+
23+
/// Create a new rgba image format with 8 bit per channel with the given resolution.
24+
#[inline]
25+
pub fn rgba8([width, height]: [u32; 2]) -> Self {
26+
datatypes::ImageFormat::rgba8([width, height]).into()
27+
}
28+
29+
/// From a speicifc pixel format.
30+
#[inline]
31+
pub fn from_pixel_format([width, height]: [u32; 2], pixel_format: PixelFormat) -> Self {
32+
datatypes::ImageFormat::from_pixel_format([width, height], pixel_format).into()
33+
}
34+
35+
/// Determine if the image format has an alpha channel.
36+
#[inline]
37+
pub fn has_alpha(&self) -> bool {
38+
self.0.has_alpha()
39+
}
40+
41+
/// Determine if the image format represents floating point data.
42+
#[inline]
43+
pub fn is_float(&self) -> bool {
44+
self.0.is_float()
45+
}
46+
47+
/// Number of bytes for the whole image.
48+
#[inline]
49+
pub fn num_bytes(&self) -> usize {
50+
self.0.num_bytes()
51+
}
52+
53+
/// The color model represented by this image format.
54+
#[inline]
55+
pub fn color_model(&self) -> ColorModel {
56+
self.0.color_model()
57+
}
58+
59+
/// The datatype represented by this image format.
60+
#[inline]
61+
pub fn datatype(&self) -> ChannelDatatype {
62+
self.0.datatype()
63+
}
64+
}

crates/store/re_types/src/components/mod.rs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/store/re_types/src/datatypes/blob.rs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/store/re_types/src/datatypes/blob_ext.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
use super::Blob;
22

3+
impl Blob {
4+
/// Returns a new [`Blob`] that is a slice of this buffer starting at `offset`.
5+
///
6+
/// Doing so allows the same memory region to be shared between buffers.
7+
/// Note that you can beforehand `clone` the [`Blob`] to get a new buffer that shares the same memory.
8+
///
9+
/// # Panics
10+
/// Panics iff `offset + length` is larger than `len`.
11+
#[inline]
12+
pub fn sliced(self, range: std::ops::Range<usize>) -> Self {
13+
self.0.sliced(range).into()
14+
}
15+
}
16+
317
impl From<Vec<u8>> for Blob {
418
fn from(bytes: Vec<u8>) -> Self {
519
Self(bytes.into())

crates/store/re_types/src/datatypes/image_format_ext.rs

+24
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ impl ImageFormat {
2525
}
2626
}
2727

28+
/// Create a new rgb image format with 8 bit per channel with the given resolution.
29+
#[inline]
30+
pub fn rgb8([width, height]: [u32; 2]) -> Self {
31+
Self {
32+
width,
33+
height,
34+
pixel_format: None,
35+
channel_datatype: Some(ChannelDatatype::U8),
36+
color_model: Some(ColorModel::RGB),
37+
}
38+
}
39+
40+
/// Create a new rgba image format with 8 bit per channel with the given resolution.
41+
#[inline]
42+
pub fn rgba8([width, height]: [u32; 2]) -> Self {
43+
Self {
44+
width,
45+
height,
46+
pixel_format: None,
47+
channel_datatype: Some(ChannelDatatype::U8),
48+
color_model: Some(ColorModel::RGBA),
49+
}
50+
}
51+
2852
/// From a speicifc pixel format.
2953
#[inline]
3054
pub fn from_pixel_format([width, height]: [u32; 2], pixel_format: PixelFormat) -> Self {

crates/store/re_types_core/src/arrow_buffer.rs

+11
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ impl<T> ArrowBuffer<T> {
5454
pub fn into_inner(self) -> Buffer<T> {
5555
self.0
5656
}
57+
58+
/// Returns a new [`Buffer`] that is a slice of this buffer starting at `offset`.
59+
///
60+
/// Doing so allows the same memory region to be shared between buffers.
61+
///
62+
/// # Panics
63+
/// Panics iff `offset + length` is larger than `len`.
64+
#[inline]
65+
pub fn sliced(self, range: std::ops::Range<usize>) -> Self {
66+
Self(self.0.sliced(range.start, range.len()))
67+
}
5768
}
5869

5970
impl<T: bytemuck::Pod> ArrowBuffer<T> {

crates/top/re_sdk/src/recording_stream.rs

+19-21
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,17 @@ impl RecordingStream {
894894
/// Lower-level logging API to provide data spanning multiple timepoints.
895895
///
896896
/// Unlike the regular `log` API, which is row-oriented, this API lets you submit the data
897-
/// in a columnar form. The lengths of all of the [`TimeColumn`] and the [`ArrowListArray`]s
897+
/// in a columnar form. The lengths of all of the [`TimeColumn`] and the component batches
898898
/// must match. All data that occurs at the same index across the different time and components
899899
/// arrays will act as a single logical row.
900900
///
901901
/// Note that this API ignores any stateful time set on the log stream via the
902902
/// [`Self::set_timepoint`]/[`Self::set_time_nanos`]/etc. APIs.
903903
/// Furthermore, this will _not_ inject the default timelines `log_tick` and `log_time` timeline columns.
904+
///
905+
/// TODO(#7167): Unlike Python and C++, this API does not yet support arbitrary partitions of the incoming
906+
/// component arrays. Each component will be individually associated with a single timepoint, rather
907+
/// than offering how big the component arrays are that are associated with each timepoint.
904908
#[inline]
905909
pub fn send_columns<'a>(
906910
&self,
@@ -920,27 +924,21 @@ impl RecordingStream {
920924
.map(|batch| {
921925
let array = batch.to_arrow()?;
922926

923-
let array = if let Some(array) =
924-
array.as_any().downcast_ref::<ArrowListArray<i32>>()
925-
{
926-
array.clone()
927-
} else {
928-
let offsets = Offsets::try_from_lengths(std::iter::repeat(1).take(array.len()))
929-
.map_err(|err| ChunkError::Malformed {
930-
reason: format!("Failed to create offsets: {err}"),
931-
})?;
932-
let data_type =
933-
ArrowListArray::<i32>::default_datatype(array.data_type().clone());
934-
ArrowListArray::<i32>::try_new(
935-
data_type,
936-
offsets.into(),
937-
array.to_boxed(),
938-
None,
939-
)
927+
let offsets = Offsets::try_from_lengths(std::iter::repeat(1).take(array.len()))
940928
.map_err(|err| ChunkError::Malformed {
941-
reason: format!("Failed to wrap in List array: {err}"),
942-
})?
943-
};
929+
reason: format!("Failed to create offsets: {err}"),
930+
})?;
931+
let data_type = ArrowListArray::<i32>::default_datatype(array.data_type().clone());
932+
933+
let array = ArrowListArray::<i32>::try_new(
934+
data_type,
935+
offsets.into(),
936+
array.to_boxed(),
937+
None,
938+
)
939+
.map_err(|err| ChunkError::Malformed {
940+
reason: format!("Failed to wrap in List array: {err}"),
941+
})?;
944942

945943
Ok((batch.name(), array))
946944
})

docs/content/reference/types/archetypes/image.md

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Send multiple images at once using `send_columns`.
2+
3+
#include <numeric>
4+
#include <rerun.hpp>
5+
6+
int main() {
7+
auto rec = rerun::RecordingStream("rerun_example_image_send_columns");
8+
rec.spawn().exit_on_failure();
9+
10+
// Timeline on which the images are distributed.
11+
std::vector<int64_t> times(20);
12+
std::iota(times.begin(), times.end(), 0);
13+
14+
// Create a batch of images with a moving rectangle.
15+
const size_t width = 300, height = 200;
16+
std::vector<uint8_t> images(times.size() * height * width * 3, 0);
17+
for (size_t t = 0; t < times.size(); ++t) {
18+
for (size_t y = 0; y < height; ++y) {
19+
for (size_t x = 0; x < width; ++x) {
20+
size_t idx = (t * height * width + y * width + x) * 3;
21+
images[idx + 2] = 255; // Blue background
22+
if (y >= 50 && y < 150 && x >= t * 10 && x < t * 10 + 100) {
23+
images[idx + 1] = 255; // Turquoise rectangle
24+
}
25+
}
26+
}
27+
}
28+
29+
// Log the ImageFormat and indicator once, as static.
30+
auto format = rerun::components::ImageFormat(
31+
{width, height},
32+
rerun::ColorModel::RGB,
33+
rerun::ChannelDatatype::U8
34+
);
35+
rec.log_static("images", rerun::borrow(&format), rerun::Image::IndicatorComponent());
36+
37+
// Split up the image data into several components referencing the underlying data.
38+
const size_t image_size_in_bytes = width * height * 3;
39+
std::vector<rerun::components::ImageBuffer> image_data(times.size());
40+
for (size_t i = 0; i < times.size(); ++i) {
41+
image_data[i] = rerun::borrow(images.data() + i * image_size_in_bytes, image_size_in_bytes);
42+
}
43+
44+
// Send all images at once.
45+
rec.send_columns(
46+
"images",
47+
rerun::TimeColumn::from_sequence_points("step", std::move(times)),
48+
rerun::borrow(image_data)
49+
);
50+
}

0 commit comments

Comments
 (0)