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")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)