Migrating from 0.21 to 0.22
Major changes to the logging APIs major-changes-to-the-logging-apis
Partial updates partial-updates
These new APIs make it possible to send partial updates of your data over time, i.e. you can think of this as a sort of diffs or delta encodings.
This was already possible before, but only by relying on semi-private APIs that were not without their lot of issues.In particular, these APIs had no way of keeping track of the surrounding context in which these logging calls were made (e.g. which archetype?), which created a lot of data modeling related issues.Internally, these new APIs make it possible to implement many long awaited Rerun features, in the long term.
The following snippets give a succinct before/after picture; for more information about partial updates, please refer to the dedicated documentation.
Python
Before:
positions = [[i, 0, 0] for i in range(0, 10)]
rr.set_time_sequence("frame", 0)
rr.log("points", rr.Points3D(positions))
for i in range(0, 10):
colors = [[20, 200, 20] if n < i else [200, 20, 20] for n in range(0, 10)]
radii = [0.6 if n < i else 0.2 for n in range(0, 10)]
# Update only the colors and radii, leaving everything else as-is.
rr.set_time_sequence("frame", i)
rr.log("points", [rr.components.ColorBatch(colors), rr.components.RadiusBatch(radii)])
# Update the positions and radii, and clear everything else in the process.
rr.set_time_sequence("frame", 20)
rr.log("points", rr.Clear.flat())
rr.log("points", [rr.components.Position3DBatch(positions), rr.components.RadiusBatch(0.3)])
After:
positions = [[i, 0, 0] for i in range(0, 10)]
rr.set_time_sequence("frame", 0)
rr.log("points", rr.Points3D(positions))
for i in range(0, 10):
colors = [[20, 200, 20] if n < i else [200, 20, 20] for n in range(0, 10)]
radii = [0.6 if n < i else 0.2 for n in range(0, 10)]
# Update only the colors and radii, leaving everything else as-is.
rr.set_time_sequence("frame", i)
rr.log("points", rr.Points3D.from_fields(radii=radii, colors=colors))
# Update only the colors and radii, leaving everything else as-is.
rr.set_time_sequence("frame", 20)
rr.log("points", rr.Points3D.from_fields(clear_unset=True, positions=positions, radii=0.3))
See also:
Rust
Before:
let positions = || (0..10).map(|i| (i as f32, 0.0, 0.0)).map(Into::into).collect::<Vec<rerun::components::Position3D>>();
rec.set_time_sequence("frame", 0);
rec.log("points", &rerun::Points3D::new(positions()))?;
for i in 0..10 {
let colors: Vec<rerun::components::Color> = (0..10)
.map(|n| { if n < i { rerun::Color::from_rgb(20, 200, 20) } else { rerun::Color::from_rgb(200, 20, 20) } })
.collect();
let radii: Vec<rerun::components::Radius> = (0..10)
.map(|n| if n < i { 0.6 } else { 0.2 })
.map(Into::into)
.collect();
// Update only the colors and radii, leaving everything else as-is.
rec.set_time_sequence("frame", i);
rec.log("points", &[&radii as &dyn rerun::ComponentBatch, &colors])?;
}
// Update the positions and radii, and clear everything else in the process.
let radii: Vec<rerun::components::Radius> = vec![0.3.into()];
rec.set_time_sequence("frame", 20);
rec.log("points", &rerun::Clear::flat())?;
rec.log("points", &[&positions() as &dyn rerun::ComponentBatch, &radii])?;
After:
let positions = || (0..10).map(|i| (i as f32, 0.0, 0.0));
rec.set_time_sequence("frame", 0);
rec.log("points", &rerun::Points3D::new(positions()))?;
for i in 0..10 {
let colors = (0..10).map(|n| { if n < i { rerun::Color::from_rgb(20, 200, 20) } else { rerun::Color::from_rgb(200, 20, 20) } });
let radii = (0..10).map(|n| if n < i { 0.6 } else { 0.2 });
// Update only the colors and radii, leaving everything else as-is.
rec.set_time_sequence("frame", i);
rec.log("points", &rerun::Points3D::update_fields().with_radii(radii).with_colors(colors))?;
}
// Update the positions and radii, and clear everything else in the process.
rec.set_time_sequence("frame", 20);
rec.log("points", &rerun::Points3D::clear_fields().with_positions(positions()).with_radii([0.3]))?;
See also:
C++
Before:
std::vector<rerun::Position3D> positions;
for (int i = 0; i < 10; ++i) {
positions.emplace_back(static_cast<float>(i), 0.0f, 0.0f);
}
rec.set_time_sequence("frame", 0);
rec.log("points", rerun::Points3D(positions));
for (int i = 0; i < 10; ++i) {
std::vector<rerun::Color> colors;
for (int n = 0; n < 10; ++n) {
if (n < i) { colors.emplace_back(rerun::Color(20, 200, 20)); } else { colors.emplace_back(rerun::Color(200, 20, 20)); }
}
std::vector<rerun::Radius> radii;
for (int n = 0; n < 10; ++n) {
if (n < i) { radii.emplace_back(rerun::Radius(0.6f)); } else { radii.emplace_back(rerun::Radius(0.2f)); }
}
// Update only the colors and radii, leaving everything else as-is.
rec.set_time_sequence("frame", i);
rec.log("points", colors, radii);
}
std::vector<rerun::Radius> radii;
radii.emplace_back(0.3f);
// Update the positions and radii, and clear everything else in the process.
rec.set_time_sequence("frame", 20);
rec.log("points", rerun::Clear::FLAT);
rec.log("points", positions, radii);
After:
std::vector<rerun::Position3D> positions;
for (int i = 0; i < 10; ++i) positions.emplace_back(static_cast<float>(i), 0.0f, 0.0f);
rec.set_time_sequence("frame", 0);
rec.log("points", rerun::Points3D(positions));
for (int i = 0; i < 10; ++i) {
std::vector<rerun::Color> colors;
for (int n = 0; n < 10; ++n) {
if (n < i) { colors.emplace_back(rerun::Color(20, 200, 20)); } else { colors.emplace_back(rerun::Color(200, 20, 20)); }
}
std::vector<rerun::Radius> radii;
for (int n = 0; n < 10; ++n) {
if (n < i) { radii.emplace_back(rerun::Radius(0.6f)); } else { radii.emplace_back(rerun::Radius(0.2f)); }
}
// Update only the colors and radii, leaving everything else as-is.
rec.set_time_sequence("frame", i);
rec.log("points", rerun::Points3D::update_fields().with_radii(radii).with_colors(colors));
}
std::vector<rerun::Radius> radii;
radii.emplace_back(0.3f);
// Update the positions and radii, and clear everything else in the process.
rec.set_time_sequence("frame", 20);
rec.log("points", rerun::Points3D::clear_fields().with_positions(positions).with_radii(radii));
See also:
Columnar updates columnar-updates
These new APIs make it possible to send partial updates of your data over time, i.e. you can think of this as a sort of diffs or delta encodings.
This was already possible before, although with pretty severe limitations.In particular, these APIs had no way of keeping track of the surrounding context in which these logging calls were made (e.g. which archetype?), which created a lot of data modeling related issues.Internally, these new APIs make it possible to implement many long awaited Rerun features, in the long term.
The following snippets give a succinct before/after picture; for more information about partial updates, please refer to the dedicated documentation.
See also the API reference:
Python
Before:
# Prepare a point cloud that evolves over 5 timesteps, changing the number of points in the process.
times = np.arange(10, 15, 1.0)
positions = [
[[1.0, 0.0, 1.0], [0.5, 0.5, 2.0]],
[[1.5, -0.5, 1.5], [1.0, 1.0, 2.5], [-0.5, 1.5, 1.0], [-1.5, 0.0, 2.0]],
[[2.0, 0.0, 2.0], [1.5, -1.5, 3.0], [0.0, -2.0, 2.5], [1.0, -1.0, 3.5]],
[[-2.0, 0.0, 2.0], [-1.5, 1.5, 3.0], [-1.0, 1.0, 3.5]],
[[1.0, -1.0, 1.0], [2.0, -2.0, 2.0], [3.0, -1.0, 3.0], [2.0, 0.0, 4.0]],
]
positions_arr = np.concatenate(positions)
# At each timestep, all points in the cloud share the same but changing color and radius.
colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 0x00FFFFFF]
rr.send_columns(
"points",
times=[rr.TimeSecondsColumn("time", times)],
components=[
rr.Points3D.indicator(),
rr.components.Position3DBatch(positions_arr).partition([len(row) for row in positions]),
rr.components.ColorBatch(colors),
],
)
After:
# Prepare a point cloud that evolves over 5 timesteps, changing the number of points in the process.
times = np.arange(10, 15, 1.0)
# fmt: off
positions = [
[1.0, 0.0, 1.0], [0.5, 0.5, 2.0],
[1.5, -0.5, 1.5], [1.0, 1.0, 2.5], [-0.5, 1.5, 1.0], [-1.5, 0.0, 2.0],
[2.0, 0.0, 2.0], [1.5, -1.5, 3.0], [0.0, -2.0, 2.5], [1.0, -1.0, 3.5],
[-2.0, 0.0, 2.0], [-1.5, 1.5, 3.0], [-1.0, 1.0, 3.5],
[1.0, -1.0, 1.0], [2.0, -2.0, 2.0], [3.0, -1.0, 3.0], [2.0, 0.0, 4.0],
]
# fmt: on
# At each timestep, all points in the cloud share the same but changing color and radius.
colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 0x00FFFFFF]
radii = [0.05, 0.01, 0.2, 0.1, 0.3]
rr.send_columns(
"points",
indexes=[rr.TimeSecondsColumn("time", times)],
columns=[
*rr.Points3D.columns(positions=positions).partition(lengths=[2, 4, 4, 3, 4]),
*rr.Points3D.columns(colors=colors, radii=radii),
],
)
See also:
- Example: Columnar updates of a
Scalar
archetype - Example: Columnar updates of a
Transform3D
archetype - Example: Columnar updates of an
Image
archetype
Rust
Before: N/A. This was not previously possible using the Rust API.
After:
let times = rerun::TimeColumn::new_seconds("time", 10..15);
// Prepare a point cloud that evolves over 5 timesteps, changing the number of points in the process.
#[rustfmt::skip]
let positions = [
[1.0, 0.0, 1.0], [0.5, 0.5, 2.0],
[1.5, -0.5, 1.5], [1.0, 1.0, 2.5], [-0.5, 1.5, 1.0], [-1.5, 0.0, 2.0],
[2.0, 0.0, 2.0], [1.5, -1.5, 3.0], [0.0, -2.0, 2.5], [1.0, -1.0, 3.5],
[-2.0, 0.0, 2.0], [-1.5, 1.5, 3.0], [-1.0, 1.0, 3.5],
[1.0, -1.0, 1.0], [2.0, -2.0, 2.0], [3.0, -1.0, 3.0], [2.0, 0.0, 4.0],
];
// At each timestep, all points in the cloud share the same but changing color and radius.
let colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 0x00FFFFFF];
let radii = [0.05, 0.01, 0.2, 0.1, 0.3];
// Partition our data as expected across the 5 timesteps.
let position = rerun::Points3D::update_fields().with_positions(positions).columns([2, 4, 4, 3, 4])?;
let color_and_radius = rerun::Points3D::update_fields().with_colors(colors).with_radii(radii).columns_of_unit_batches()?;
rec.send_columns("points", [times], position.chain(color_and_radius))?;
See also:
- Example: Columnar updates of a
Scalar
archetype - Example: Columnar updates of a
Transform3D
archetype - Example: Columnar updates of an
Image
archetype
C++
Before:
// Prepare a point cloud that evolves over 5 timesteps, changing the number of points in the process.
std::vector<std::array<float, 3>> positions = {
// clang-format off
{1.0, 0.0, 1.0}, {0.5, 0.5, 2.0},
{1.5, -0.5, 1.5}, {1.0, 1.0, 2.5}, {-0.5, 1.5, 1.0}, {-1.5, 0.0, 2.0},
{2.0, 0.0, 2.0}, {1.5, -1.5, 3.0}, {0.0, -2.0, 2.5}, {1.0, -1.0, 3.5},
{-2.0, 0.0, 2.0}, {-1.5, 1.5, 3.0}, {-1.0, 1.0, 3.5},
{1.0, -1.0, 1.0}, {2.0, -2.0, 2.0}, {3.0, -1.0, 3.0}, {2.0, 0.0, 4.0},
// clang-format on
};
// At each timestep, all points in the cloud share the same but changing color and radius.
std::vector<uint32_t> colors = {0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 0x00FFFFFF};
// Log at seconds 10-14
auto times = rerun::Collection{10s, 11s, 12s, 13s, 14s};
auto time_column = rerun::TimeColumn::from_times("time", std::move(times));
// Partition our data as expected across the 5 timesteps.
auto indicator_batch = rerun::ComponentColumn::from_indicators<rerun::Points3D>(5);
auto position_batch = rerun::ComponentColumn::from_loggable_with_lengths(
rerun::Collection<rerun::components::Position3D>(std::move(positions)),
{2, 4, 4, 3, 4}
);
auto color_batch = rerun::ComponentColumn::from_loggable(
rerun::Collection<rerun::components::Color>(std::move(colors))
);
rec.send_columns(
"points",
time_column,
{
indicator_batch.value_or_throw(),
position_batch.value_or_throw(),
color_batch.value_or_throw(),
}
);
After:
// Prepare a point cloud that evolves over 5 timesteps, changing the number of points in the process.
std::vector<std::array<float, 3>> positions = {
// clang-format off
{1.0, 0.0, 1.0}, {0.5, 0.5, 2.0},
{1.5, -0.5, 1.5}, {1.0, 1.0, 2.5}, {-0.5, 1.5, 1.0}, {-1.5, 0.0, 2.0},
{2.0, 0.0, 2.0}, {1.5, -1.5, 3.0}, {0.0, -2.0, 2.5}, {1.0, -1.0, 3.5},
{-2.0, 0.0, 2.0}, {-1.5, 1.5, 3.0}, {-1.0, 1.0, 3.5},
{1.0, -1.0, 1.0}, {2.0, -2.0, 2.0}, {3.0, -1.0, 3.0}, {2.0, 0.0, 4.0},
// clang-format on
};
// At each timestep, all points in the cloud share the same but changing color and radius.
std::vector<uint32_t> colors = {0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 0x00FFFFFF};
std::vector<float> radii = {0.05f, 0.01f, 0.2f, 0.1f, 0.3f};
// Log at seconds 10-14
auto times = rerun::Collection{10s, 11s, 12s, 13s, 14s};
auto time_column = rerun::TimeColumn::from_times("time", std::move(times));
// Partition our data as expected across the 5 timesteps.
auto position = rerun::Points3D().with_positions(positions).columns({2, 4, 4, 3, 4});
auto color_and_radius = rerun::Points3D().with_colors(colors).with_radii(radii).columns();
rec.send_columns("points", time_column, position, color_and_radius);
See also:
- Example: Columnar updates of a
Scalar
archetype - Example: Columnar updates of a
Transform3D
archetype - Example: Columnar updates of an
Image
archetype
Rust API changes rust-api-changes
ViewCoordinates
archetype now has static methods instead of constants viewcoordinates-archetype-now-has-static-methods-instead-of-constants
As part of the switch to "eager archetype serialization" (serialization of archetype components now occurs at time of archetype instantiation rather than logging), we can no longer offer constants for the ViewCoordinates
archetype like ViewCoordinates::RUB
.
Instead, there's now methods with the same name, i.e. ViewCoordinates::RUB()
.
Tensor
archetype can no longer access tensor data as ndarray
view directly tensor-archetype-can-no-longer-access-tensor-data-as-ndarray-view-directly
As part of the switch to "eager archetype serialization" (serialization of archetype components now occurs at time of archetype instantiation rather than logging), we can no longer offer exposing the Tensor
archetype as ndarray::ArrayView
directly.
However, it is still possible to do so with the TensorData
component.
C++ API changes c-api-changes
RecordingStream::log
/send_column
no longer takes raw component collections recordingstreamlogsendcolumn-no-longer-takes-raw-component-collections
Previously, both RecordingStream::log
and RecordingStream::send_column
were able to
handle raw component collections which then would be serialized to arrow on the fly.
log
Under the hood we allow any type that implements the AsComponents
trait.
However, AsComponents
is no longer implemented for collections of components / implementers of Loggable
.
Instead, you're encouraged to use archetypes for cases where you'd previously use loose collections of components. This is made easier by the fact that archetypes can now be created without specifying required components. For example, colors of a point cloud can be logged without position data:
rec.log("points", rerun::Points3D().with_colors(colors));
Custom implementations of AsComponents
still work as before.
send_column
Only rerun::ComponentColumn
and anything else from which
a Collection<ComponentColumn>
can be constructed is accepted.
The preferred way to create rerun::ComponentColumn
s is to use the new columns
method on archetypes.
For instance in order to send a column of scalars, you can now do this.
rec.send_columns("scalars", time_column,
rerun::Scalar().with_many_scalar(scalar_data).columns()
);
All example snippets have been updated accordingly.
AsComponents::serialize
is now called AsComponents::as_batches
and returns rerun::Collection<ComponentBatch>
ascomponentsserialize-is-now-called-ascomponentsasbatches-and-returns-reruncollectioncomponentbatch
The AsComponents
's serialize
method has been renamed to as_batches
and now returns a rerun::Collection<ComponentBatch>
instead of a std::vector<ComponentBatch>
.
// Old
template <>
struct AsComponents<CustomArchetype> {
static Result<std::vector<ComponentBatch>> serialize(const CustomArchetype& archetype);
};
// New
template <>
struct AsComponents<CustomArchetype> {
static Result<rerun::Collection<ComponentBatch>> operator()(const CustomArchetype& archetype);
};
Python API changes python-api-changes
rr.log_components()
is now deprecated & no longer has a num_instances
keyword argument rrlogcomponents-is-now-deprecated--no-longer-has-a-numinstances-keyword-argument
For historical reasons, the rr.log_components()
function of the Python SDK accepts an optional, keyword-only argument num_instances
.
It was no longer used for several releases, so we removed it.
Although rr.log_components()
was technically a public API, it was undocumented and we now deprecated its use.
For logging custom components, use rr.AnyValue
and rr.AnyBatchValue
.
Other other
Previously deprecated DisconnectedSpace
archetype/component have been removed previously-deprecated-disconnectedspace-archetypecomponent-have-been-removed
The deprecated DisconnectedSpace
archetype and DisconnectedSpace
component have been removed.
To achieve the same effect, you can log any of the following "invalid" transforms:
- zeroed 3x3 matrix
- zero scale
- zeroed quaternion
- zero axis on axis-angle rotation
Previously, the DisconnectedSpace
archetype played a double role by governing view spawn heuristics & being used as a transform placeholder.
This led to a lot of complexity and often broke or caused confusion (see https://github.com/rerun-io/rerun/issues/6817, https://github.com/rerun-io/rerun/issues/4465, https://github.com/rerun-io/rerun/issues/4221).
By now, explicit blueprints offer a better way to express which views should be spawned and what content they should query.
(you can learn more about blueprints here).