Building blueprints programmatically
For maximum control and automation, you can define Blueprints in code using the Python Blueprint API. This is ideal for:
- Creating layouts dynamically based on your data
- Ensuring consistent views for specific debugging scenarios
- Generating complex layouts that would be tedious to build manually
- Sending different blueprints based on runtime conditions
Getting started example getting-started-example
This walkthrough demonstrates the Blueprint API using stock market data. We'll start simple and progressively build more complex layouts.
Setup
First, create a virtual environment and install dependencies:
Linux/Mac:
python -m venv venv
source venv/bin/activate
pip install rerun-sdk humanize yfinanceWindows:
python -m venv venv
.\venv\Scripts\activate
pip install rerun-sdk humanize yfinanceBasic script
Create stocks.py with the necessary imports:
#!/usr/bin/env python3
import datetime as dt
import humanize
import pytz
import yfinance as yf
from typing import Any
import rerun as rr
import rerun.blueprint as rrbAdd helper functions for styling:
brand_colors = {
"AAPL": 0xA2AAADFF,
"AMZN": 0xFF9900FF,
"GOOGL": 0x34A853FF,
"META": 0x0081FBFF,
"MSFT": 0xF14F21FF,
}
def style_plot(symbol: str) -> rr.SeriesLine:
return rr.SeriesLine(
color=brand_colors[symbol],
name=symbol,
)
def style_peak(symbol: str) -> rr.SeriesPoint:
return rr.SeriesPoint(
color=0xFF0000FF,
name=f"{symbol} (peak)",
marker="Up",
)
def info_card(
shortName: str,
industry: str,
marketCap: int,
totalRevenue: int,
**args: dict[str, Any],
) -> rr.TextDocument:
markdown = f"""
- **Name**: {shortName}
- **Industry**: {industry}
- **Market cap**: ${humanize.intword(marketCap)}
- **Total Revenue**: ${humanize.intword(totalRevenue)}
"""
return rr.TextDocument(markdown, media_type=rr.MediaType.MARKDOWN)Add the main function that logs data:
def main() -> None:
symbols = ["AAPL", "AMZN", "GOOGL", "META", "MSFT"]
# Use eastern time for market hours
et_timezone = pytz.timezone("America/New_York")
start_date = dt.date(2024, 3, 18)
dates = [start_date + dt.timedelta(days=i) for i in range(5)]
# Initialize Rerun and spawn a new viewer
rr.init("rerun_example_blueprint_stocks", spawn=True)
# This is where we will edit the blueprint
blueprint = None
#rr.send_blueprint(blueprint)
# Log the stock data for each symbol and date
for symbol in symbols:
stock = yf.Ticker(symbol)
# Log the stock info document as static
rr.log(f"stocks/{symbol}/info", info_card(**stock.info), static=True)
for day in dates:
# Log the styling data as static
rr.log(f"stocks/{symbol}/{day}", style_plot(symbol), static=True)
rr.log(f"stocks/{symbol}/peaks/{day}", style_peak(symbol), static=True)
# Query the stock data during market hours
open_time = dt.datetime.combine(day, dt.time(9, 30), et_timezone)
close_time = dt.datetime.combine(day, dt.time(16, 00), et_timezone)
hist = stock.history(start=open_time, end=close_time, interval="5m")
# Offset the index to be in seconds since the market open
hist.index = hist.index - open_time
peak = hist.High.idxmax()
# Log the stock state over the course of the day
for row in hist.itertuples():
rr.set_time("time", duration=row.Index)
rr.log(f"stocks/{symbol}/{day}", rr.Scalars(row.High))
if row.Index == peak:
rr.log(f"stocks/{symbol}/peaks/{day}", rr.Scalars(row.High))
if __name__ == "__main__":
main()Run the script:
python stocks.pyWithout a blueprint, the heuristic layout may not be ideal:
Creating a simple view creating-a-simple-view
Replace the blueprint section with:
# Create a single chart for all the AAPL data:
blueprint = rrb.Blueprint(
rrb.TimeSeriesView(name="AAPL", origin="/stocks/AAPL"),
)
rr.send_blueprint(blueprint)The origin parameter scopes the view to a specific subtree. Now you'll see just the AAPL data:
Controlling panel state controlling-panel-state
You can control which panels are visible:
# Create a single chart and collapse the selection and time panels:
blueprint = rrb.Blueprint(
rrb.TimeSeriesView(name="AAPL", origin="/stocks/AAPL"),
rrb.BlueprintPanel(state="expanded"),
rrb.SelectionPanel(state="collapsed"),
rrb.TimePanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
Combining multiple views combining-multiple-views
Use containers to combine multiple views. The Vertical container stacks views, and row_shares controls relative sizing:
# Create a vertical layout of an info document and a time series chart
blueprint = rrb.Blueprint(
rrb.Vertical(
rrb.TextDocumentView(name="Info", origin="/stocks/AAPL/info"),
rrb.TimeSeriesView(name="Chart", origin="/stocks/AAPL"),
row_shares=[1, 4],
),
rrb.BlueprintPanel(state="expanded"),
rrb.SelectionPanel(state="collapsed"),
rrb.TimePanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
Specifying view contents specifying-view-contents
The contents parameter provides fine-grained control over what appears in a view. You can include data from multiple sources:
# Create a view with two stock time series
blueprint = rrb.Blueprint(
rrb.TimeSeriesView(
name="META vs MSFT",
contents=[
"+ /stocks/META/2024-03-19",
"+ /stocks/MSFT/2024-03-19",
],
),
rrb.BlueprintPanel(state="expanded"),
rrb.SelectionPanel(state="collapsed"),
rrb.TimePanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
Filtering with expressions filtering-with-expressions
Content expressions can include or exclude subtrees using wildcards. They can reference $origin and use /** to match entire subtrees:
# Create a chart for AAPL and filter out the peaks:
blueprint = rrb.Blueprint(
rrb.TimeSeriesView(
name="AAPL",
origin="/stocks/AAPL",
contents=[
"+ $origin/**",
"- $origin/peaks/**",
],
),
rrb.BlueprintPanel(state="expanded"),
rrb.SelectionPanel(state="collapsed"),
rrb.TimePanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
See Entity Queries for complete expression syntax.
Programmatic layout generation programmatic-layout-generation
Since blueprints are Python code, you can generate them dynamically. This example creates a grid with one row per stock symbol:
# Iterate over all symbols and days to create a comprehensive grid
blueprint = rrb.Blueprint(
rrb.Vertical(
contents=[
rrb.Horizontal(
contents=[
rrb.TextDocumentView(
name=f"{symbol}",
origin=f"/stocks/{symbol}/info",
),
]
+ [
rrb.TimeSeriesView(
name=f"{day}",
origin=f"/stocks/{symbol}/{day}",
)
for day in dates
],
name=symbol,
)
for symbol in symbols
]
),
rrb.BlueprintPanel(state="expanded"),
rrb.SelectionPanel(state="collapsed"),
rrb.TimePanel(state="collapsed"),
)
rr.send_blueprint(blueprint)
Saving blueprints from code saving-blueprints-from-code
You can save programmatically-created blueprints to .rbl files:
blueprint = rrb.Blueprint(
rrb.TimeSeriesView(name="AAPL", origin="/stocks/AAPL"),
)
# Save to a file
blueprint.save("rerun_example_blueprint_stocks", "my_blueprint.rbl")
# Later, load it in any language
rr.log_file_from_path("my_blueprint.rbl")Loading blueprints from any language
The programmatic way works by calling log_file_from_path:
This method allows you to log any file that contains data that Rerun understands (in this case, blueprint data) as part of your current recording.
This enables reusing blueprints across different programming languages. See the Blueprint API Reference for complete details.
Advanced customization advanced-customization
Blueprints support deep customization of view properties. For example:
# Configure a 3D view with custom camera settings
rrb.Spatial3DView(
name="Robot view",
origin="/world/robot",
background=[100, 149, 237], # Light blue
eye_controls=rrb.EyeControls3D(
kind=rrb.Eye3DKind.FirstPerson,
speed=20.0,
),
)
# Configure a time series view with custom axis and time ranges
rrb.TimeSeriesView(
name="Sensor Data",
origin="/sensors",
axis_y=rrb.ScalarAxis(range=(-10.0, 10.0), zoom_lock=True),
plot_legend=rrb.PlotLegend(visible=False),
time_ranges=[
rrb.VisibleTimeRange(
"time",
start=rrb.TimeRangeBoundary.cursor_relative(seq=-100),
end=rrb.TimeRangeBoundary.cursor_relative(),
),
],
)See Visualizers and Overrides for information on overriding component values and controlling visualizers from code.
Youtube overview youtube-overview
While some people might want to read through the documentation on this page, others might prefer to watch a video! If you would like to follow along with the Youtube video, you can find the code used in the video below.
from __future__ import annotations
import math
import numpy as np
import rerun as rr
import rerun.blueprint as rrb
from numpy.random import default_rng
rr.init("rerun_blueprint_example", spawn=True)
rr.set_time("time", sequence=0)
rr.log("log/status", rr.TextLog("Application started.", level=rr.TextLogLevel.INFO))
rr.set_time("time", sequence=5)
rr.log("log/other", rr.TextLog("A warning.", level=rr.TextLogLevel.WARN))
for i in range(10):
rr.set_time("time", sequence=i)
rr.log(
"log/status", rr.TextLog(f"Processing item {i}.", level=rr.TextLogLevel.INFO)
)
# Create a text view that displays all logs.
blueprint = rrb.Blueprint(
rrb.TextLogView(origin="/log", name="Text Logs"),
rrb.SelectionPanel(state="expanded"),
collapse_panels=True,
)
rr.send_blueprint(blueprint)
input("Press Enter to continue…")
# Create a spiral of points:
n = 150
angle = np.linspace(0, 10 * np.pi, n)
spiral_radius = np.linspace(0.0, 3.0, n) ** 2
positions = np.column_stack(
(np.cos(angle) * spiral_radius, np.sin(angle) * spiral_radius)
)
colors = np.dstack(
(np.linspace(255, 255, n), np.linspace(255, 0, n), np.linspace(0, 255, n))
)[0].astype(int)
radii = np.linspace(0.01, 0.7, n)
rr.log("points", rr.Points2D(positions, colors=colors, radii=radii))
# Create a Spatial2D view to display the points.
blueprint = rrb.Blueprint(
rrb.Spatial2DView(
origin="/",
name="2D Scene",
# Set the background color
background=[105, 20, 105],
# Note that this range is smaller than the range of the points,
# so some points will not be visible.
visual_bounds=rrb.VisualBounds2D(x_range=[-5, 5], y_range=[-5, 5]),
),
collapse_panels=True,
)
rr.send_blueprint(blueprint)
input("Press Enter to continue…")
rr.log(
"points",
rr.GeoPoints(
lat_lon=[[47.6344, 19.1397], [47.6334, 19.1399]],
radii=rr.Radius.ui_points(20.0),
),
)
# Create a map view to display the chart.
blueprint = rrb.Blueprint(
rrb.MapView(
origin="points",
name="MapView",
zoom=16.0,
background=rrb.MapProvider.OpenStreetMap,
),
collapse_panels=True,
)
rr.send_blueprint(blueprint)
input("Press Enter to continue…")
blueprint = rrb.Blueprint(
rrb.Grid(
rrb.MapView(
origin="points",
name="MapView",
zoom=16.0,
background=rrb.MapProvider.OpenStreetMap,
),
rrb.Spatial2DView(
origin="/",
name="2D Scene",
# Set the background color
background=[105, 20, 105],
# Note that this range is smaller than the range of the points,
# so some points will not be visible.
visual_bounds=rrb.VisualBounds2D(x_range=[-5, 5], y_range=[-5, 5]),
),
rrb.TextLogView(origin="/log", name="Text Logs"),
),
rrb.TimePanel(state="expanded"),
rrb.BlueprintPanel(state="expanded"),
collapse_panels=True,
)
rr.send_blueprint(blueprint)
blueprint.save("my_favorite_blueprint", "data/blueprint.rbl")
input("Press Enter to continue…")
rr.log("bar_chart", rr.BarChart([8, 4, 0, 9, 1, 4, 1, 6, 9, 0]))
rng = default_rng(12345)
positions = rng.uniform(-5, 5, size=[50, 3])
colors = rng.uniform(0, 255, size=[50, 3])
radii = rng.uniform(0.1, 0.5, size=[50])
rr.log("3dpoints", rr.Points3D(positions, colors=colors, radii=radii))
tensor = np.random.randint(0, 256, (32, 240, 320, 3), dtype=np.uint8)
rr.log("tensor", rr.Tensor(tensor, dim_names=("batch", "x", "y", "channel")))
rr.log(
"markdown",
rr.TextDocument(
"""
# Hello Markdown!
[Click here to see the raw text](recording://markdown:Text).
"""
),
)
rr.log("trig/sin", rr.SeriesLines(colors=[255, 0, 0], names="sin(0.01t)"), static=True)
for t in range(int(math.pi * 4 * 100.0)):
rr.set_time("time", sequence=t)
rr.set_time("timeline1", duration=t)
rr.log("trig/sin", rr.Scalars(math.sin(float(t) / 100.0)))
blueprint = rrb.Blueprint(
rrb.Grid(
rrb.MapView(
origin="points",
name="MapView",
zoom=16.0,
background=rrb.MapProvider.OpenStreetMap,
),
rrb.Spatial2DView(
origin="/",
name="2D Scene",
# Set the background color
background=[105, 20, 105],
# Note that this range is smaller than the range of the points,
# so some points will not be visible.
visual_bounds=rrb.VisualBounds2D(x_range=[-5, 5], y_range=[-5, 5]),
),
rrb.TextLogView(origin="/log", name="Text Logs"),
rrb.BarChartView(origin="bar_chart", name="Bar Chart"),
rrb.Spatial3DView(
origin="/3dpoints",
name="3D Scene",
# Set the background color to light blue.
background=[100, 149, 237],
# Configure the eye controls.
eye_controls=rrb.EyeControls3D(
kind=rrb.Eye3DKind.FirstPerson,
speed=20.0,
),
),
rrb.TensorView(
origin="tensor",
name="Tensor",
# Explicitly pick which dimensions to show.
slice_selection=rrb.TensorSliceSelection(
# Use the first dimension as width.
width=1,
# Use the second dimension as height and invert it.
height=rr.TensorDimensionSelection(dimension=2, invert=True),
# Set which indices to show for the other dimensions.
indices=[
rr.TensorDimensionIndexSelection(dimension=2, index=4),
rr.TensorDimensionIndexSelection(dimension=3, index=5),
],
# Show a slider for dimension 2 only. If not specified, all dimensions in `indices` will have sliders.
slider=[2],
),
# Set a scalar mapping with a custom colormap, gamma and magnification filter.
scalar_mapping=rrb.TensorScalarMapping(
colormap="turbo", gamma=1.5, mag_filter="linear"
),
# Fill the view, ignoring aspect ratio.
view_fit="fill",
),
rrb.TextDocumentView(origin="markdown", name="Markdown example"),
rrb.TimeSeriesView(
origin="/trig",
# Set a custom Y axis.
axis_y=rrb.ScalarAxis(range=(-1.0, 1.0), zoom_lock=True),
# Configure the legend.
plot_legend=rrb.PlotLegend(visible=False),
# Set time different time ranges for different timelines.
time_ranges=[
# Sliding window depending on the time cursor for the first timeline.
rrb.VisibleTimeRange(
"time",
start=rrb.TimeRangeBoundary.cursor_relative(seq=-100),
end=rrb.TimeRangeBoundary.cursor_relative(),
),
# Time range from some point to the end of the timeline for the second timeline.
rrb.VisibleTimeRange(
"timeline1",
start=rrb.TimeRangeBoundary.absolute(seconds=300.0),
end=rrb.TimeRangeBoundary.infinite(),
),
],
),
),
collapse_panels=True,
)
rr.send_blueprint(blueprint)