Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .utils/README_template.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Below is a list of all tutorials available in this repository:
{%- if tutorial.visualize_url %}
- 📊 [Visualize]({{ tutorial.visualize_url }})
{%- endif %}
{%- if tutorial.colab_url %}
- 📓 [Open in Colab]({{ tutorial.colab_url }})
{%- endif %}
{%- endfor %}
{% endfor %}
Join the Foxglove [Discord](https://discord.gg/UEuytgVkks) and follow [our blog](https://foxglove.dev/blog) for more ideas on how to integrate Foxglove into your robotics development workflows.
1 change: 1 addition & 0 deletions .utils/generate_readme.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def scan_tutorials():
"blog_post_url": metadata.get("blog_post_url", ""),
"video_url": metadata.get("video_url", ""),
"visualize_url": metadata.get("visualize_url", ""),
"colab_url": metadata.get("colab_url", ""),
})
return sorted(tutorials, key=lambda t: t["path"])

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,8 @@ Below is a list of all tutorials available in this repository:
### [Analyze Your Robotics Data with Jupyter Notebooks](jupyter_notebooks/data_platform/README.md)
- 📝 Load and analyze data in Jupyter Notebooks using Foxglove Data Management
- 🔗 [Related Blog Post](https://foxglove.dev/blog/analyze-your-robotics-data-with-jupyter-notebooks)
### [Get Started with the Foxglove Notebook Integration](jupyter_notebooks/getting_started/README.md)
- 📝 Embed the Foxglove viewer in a Jupyter / Colab notebook and drive it with programmatic layouts
- 📓 [Open in Colab](https://colab.research.google.com/github/foxglove/tutorials/blob/main/jupyter_notebooks/getting_started/GettingStarted.ipynb)

Join the Foxglove [Discord](https://discord.gg/UEuytgVkks) and follow [our blog](https://foxglove.dev/blog) for more ideas on how to integrate Foxglove into your robotics development workflows.
334 changes: 334 additions & 0 deletions jupyter_notebooks/getting_started/GettingStarted.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3cb6f824",
"metadata": {},
"source": [
"# Get Started with the Foxglove Notebook Integration\n",
"\n",
"[Foxglove](https://foxglove.dev/) is a multimodal robotics observability platform. The [Python SDK](https://docs.foxglove.dev/docs/getting-started/python) ships an optional **notebook integration** that lets you embed a fully-featured Foxglove viewer directly inside Jupyter, JupyterLab, Google Colab, or VS Code.\n",
"\n",
"This notebook is a runnable companion to the [Foxglove notebook integration docs](https://docs.foxglove.dev/docs/notebook). It walks through:\n",
"\n",
"1. Installing the integration\n",
"2. Creating a notebook buffer and logging messages\n",
"3. Building layouts programmatically with the `foxglove.layouts` API\n"
]
},
{
"cell_type": "markdown",
"id": "f09dc6bc",
"metadata": {},
"source": [
"## Install the integration\n",
"\n",
"The notebook integration is shipped as the optional `notebook` extra of the [`foxglove-sdk`](https://pypi.org/project/foxglove-sdk/) package. In Colab or any other fresh kernel, install it like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b47856b0",
"metadata": {},
"outputs": [],
"source": [
"%pip install -q \"foxglove-sdk[notebook]\""
]
},
{
"cell_type": "markdown",
"id": "b56daf55",
"metadata": {},
"source": [
"## Using the integration\n",
"\n",
"Once the integration is installed, create a notebook buffer with [`foxglove.init_notebook_buffer()`](https://docs.foxglove.dev/docs/notebook#using-the-integration). The buffer collects every message you log to the SDK's default context. When you're ready to visualize the data, call `nb_buffer.show()` — **it must be the last expression in the cell** so Jupyter renders the embedded widget.\n",
"\n",
"> ⚠️ **Always set `log_time` explicitly** when calling `foxglove.log(...)`. If you don't, the SDK uses the wall-clock time at the moment each message is logged, which can lead to surprising playback behavior in the viewer.\n",
"\n",
"The example below logs 300 `count` messages on the `/hello` topic, spaced 33 ms apart (roughly 10 seconds of buffered data), then displays the embedded Foxglove viewer."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4860aa2",
"metadata": {},
"outputs": [],
"source": [
"import foxglove\n",
"from foxglove.layouts import Layout, RawMessagesConfig, RawMessagesPanel\n",
"\n",
"# Create a notebook buffer to collect messages.\n",
"nb_buffer = foxglove.init_notebook_buffer()\n",
"\n",
"# Log 300 messages spaced 33ms apart (~10 seconds of data).\n",
"for t in range(10 * 30):\n",
" timestamp = t * 0.033\n",
" foxglove.log(\"/hello\", {\"count\": t}, log_time=int(timestamp * 1e9))\n",
"\n",
"# Pre-configure a Raw Messages panel pointed at /hello so the embedded\n",
"# viewer shows the buffered data immediately instead of an empty default layout.\n",
"layout = Layout(\n",
" content=RawMessagesPanel(\n",
" title=\"/hello\",\n",
" config=RawMessagesConfig(topic_path=\"/hello\"),\n",
" ),\n",
")\n",
"\n",
"# Display the data in the embedded Foxglove viewer.\n",
"# This must be the last expression in the cell for the widget to render.\n",
"nb_buffer.show(layout=layout)"
]
},
{
"cell_type": "markdown",
"id": "435a2b05",
"metadata": {},
"source": [
"### Logging richer data\n",
"\n",
"The SDK ships typed channels for the [Foxglove schemas](https://docs.foxglove.dev/docs/visualization/message-schemas/introduction) so you can log 3D scenes, images, point clouds, and more — and they all render in the embedded viewer just like they would in the desktop app.\n",
"\n",
"Here we log an animated cube on the `/scene` topic. We'll reuse this data later when we build a custom layout."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e59a45e",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"import foxglove\n",
"from foxglove.channels import SceneUpdateChannel\n",
"from foxglove.layouts import (\n",
" BaseRendererGridLayerSettings,\n",
" BaseRendererSceneUpdateTopicSettings,\n",
" Layout,\n",
" ThreeDeeConfig,\n",
" ThreeDeePanel,\n",
")\n",
"from foxglove.messages import (\n",
" Color,\n",
" CubePrimitive,\n",
" SceneEntity,\n",
" SceneUpdate,\n",
" Vector3,\n",
")\n",
"\n",
"# Start with a fresh buffer so this cell stands on its own.\n",
"nb_buffer = foxglove.init_notebook_buffer()\n",
"\n",
"scene_channel = SceneUpdateChannel(\"/scene\")\n",
"\n",
"# Animate a cube whose size pulses over ~10 seconds.\n",
"for t in range(10 * 30):\n",
" timestamp = t * 0.033\n",
" size = abs(math.sin(timestamp)) + 1.0\n",
"\n",
" # Also log a scalar so we have something to plot later.\n",
" foxglove.log(\"/hello\", {\"count\": t}, log_time=int(timestamp * 1e9))\n",
"\n",
" scene_channel.log(\n",
" SceneUpdate(\n",
" entities=[\n",
" SceneEntity(\n",
" cubes=[\n",
" CubePrimitive(\n",
" size=Vector3(x=size, y=size, z=size),\n",
" color=Color(r=1.0, g=0.8, b=0.0, a=1.0),\n",
" ),\n",
" ],\n",
" ),\n",
" ],\n",
" ),\n",
" log_time=int(timestamp * 1e9),\n",
" )\n",
"\n",
"# Pre-configure a 3D panel subscribed to /scene so the embedded viewer\n",
"# immediately renders the animated cube instead of an empty default layout.\n",
"layout = Layout(\n",
" content=ThreeDeePanel(\n",
" title=\"Scene\",\n",
" config=ThreeDeeConfig(\n",
" layers={\"grid\": BaseRendererGridLayerSettings()},\n",
" topics={\"/scene\": BaseRendererSceneUpdateTopicSettings(visible=True)},\n",
" ),\n",
" ),\n",
")\n",
"\n",
"nb_buffer.show(layout=layout)"
]
},
{
"cell_type": "markdown",
"id": "11460217",
"metadata": {},
"source": [
"## Layout management\n",
"\n",
"A [layout](https://docs.foxglove.dev/docs/visualization/layouts/) is the arrangement of panels — 3D scenes, plots, images, raw messages, etc. — that turns raw data into an opinionated view of your robot. The SDK ships a Python API in [`foxglove.layouts`](https://docs.foxglove.dev/docs/notebook/layouts) for building layouts programmatically and passing them to the embedded viewer via `nb_buffer.show(layout=...)`.\n",
"\n",
"The simplest layout is a single panel. Below we build one with a `MarkdownPanel` plus a global variable, mirroring the example from the docs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a874a71",
"metadata": {},
"outputs": [],
"source": [
"import foxglove\n",
"from foxglove.layouts import Layout, MarkdownConfig, MarkdownPanel\n",
"\n",
"layout = Layout(\n",
" content=MarkdownPanel(\n",
" config=MarkdownConfig(markdown=\"Hello, world!\"),\n",
" ),\n",
" variables={\"my_variable\": 1},\n",
")\n",
"\n",
"nb_buffer = foxglove.init_notebook_buffer()\n",
"nb_buffer.show(layout=layout)"
]
},
{
"cell_type": "markdown",
"id": "cb40cd1d",
"metadata": {},
"source": [
"### Composing panels with containers\n",
"\n",
"Real layouts usually have multiple panels arranged side-by-side or in tabs. The `foxglove.layouts` module ships three containers for that:\n",
"\n",
"- [`SplitContainer`](https://docs.foxglove.dev/docs/notebook/layouts) — arranges children side-by-side in a `\"row\"` or `\"column\"`.\n",
"- [`TabContainer`](https://docs.foxglove.dev/docs/notebook/layouts) — arranges children in tabs, with one visible at a time.\n",
"- [`StackContainer`](https://docs.foxglove.dev/docs/notebook/layouts) — arranges children in a vertically-scrolling list.\n",
"\n",
"Below we re-log the cube and counter from the previous cells, then build a layout that puts the 3D scene and a plot of `/hello.count` side-by-side inside a tab. This gives us a small but realistic example of nested containers driving real data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "821f6f93",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"import foxglove\n",
"from foxglove.channels import SceneUpdateChannel\n",
"from foxglove.layouts import (\n",
" BaseRendererGridLayerSettings,\n",
" BaseRendererSceneUpdateTopicSettings,\n",
" Layout,\n",
" PlotConfig,\n",
" PlotPanel,\n",
" PlotSeries,\n",
" SplitContainer,\n",
" SplitItem,\n",
" TabContainer,\n",
" TabItem,\n",
" ThreeDeeConfig,\n",
" ThreeDeePanel,\n",
")\n",
"from foxglove.messages import (\n",
" Color,\n",
" CubePrimitive,\n",
" SceneEntity,\n",
" SceneUpdate,\n",
" Vector3,\n",
")\n",
"\n",
"# Fresh buffer + re-log the data so this cell is self-contained.\n",
"nb_buffer = foxglove.init_notebook_buffer()\n",
"scene_channel = SceneUpdateChannel(\"/scene\")\n",
"\n",
"for t in range(10 * 30):\n",
" timestamp = t * 0.033\n",
" size = abs(math.sin(timestamp)) + 1.0\n",
"\n",
" foxglove.log(\"/hello\", {\"count\": t}, log_time=int(timestamp * 1e9))\n",
"\n",
" scene_channel.log(\n",
" SceneUpdate(\n",
" entities=[\n",
" SceneEntity(\n",
" cubes=[\n",
" CubePrimitive(\n",
" size=Vector3(x=size, y=size, z=size),\n",
" color=Color(r=0.1, g=0.7, b=1.0, a=1.0),\n",
" ),\n",
" ],\n",
" ),\n",
" ],\n",
" ),\n",
" log_time=int(timestamp * 1e9),\n",
" )\n",
"\n",
"# A 3D panel showing the cube on /scene, with a base grid layer for context.\n",
"scene_panel = ThreeDeePanel(\n",
" title=\"Scene\",\n",
" config=ThreeDeeConfig(\n",
" layers={\"grid\": BaseRendererGridLayerSettings()},\n",
" topics={\"/scene\": BaseRendererSceneUpdateTopicSettings(visible=True)},\n",
" ),\n",
")\n",
"\n",
"# A plot panel showing /hello.count over time.\n",
"plot_panel = PlotPanel(\n",
" title=\"Counter\",\n",
" config=PlotConfig(\n",
" paths=[PlotSeries(value=\"/hello.count\", enabled=True)],\n",
" show_legend=True,\n",
" ),\n",
")\n",
"\n",
"# Side-by-side row, wrapped in a single-tab TabContainer to demonstrate both APIs.\n",
"split = SplitContainer(\n",
" direction=\"row\",\n",
" items=[\n",
" SplitItem(content=scene_panel),\n",
" SplitItem(content=plot_panel),\n",
" ],\n",
")\n",
"tabs = TabContainer(tabs=[TabItem(title=\"Overview\", content=split)])\n",
"\n",
"layout = Layout(content=tabs)\n",
"nb_buffer.show(layout=layout)"
]
},
{
"cell_type": "markdown",
"id": "191d273b",
"metadata": {},
"source": [
"## Where to next\n",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on adding a link to the Playground?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 For now I'll defer. We don't promote playground and its a very different experience than notebooks (no external libraries).

"\n",
"- 📖 [Notebook integration docs](https://docs.foxglove.dev/docs/notebook) — the full reference, including buffer management and updating an existing viewer with `viewer.refresh()`.\n",
"- 🧱 [Layouts API reference](https://docs.foxglove.dev/docs/notebook/layouts) — every panel and container the `foxglove.layouts` module ships.\n",
"- 🐍 [Python SDK getting started](https://docs.foxglove.dev/docs/getting-started/python) — using the SDK outside of notebooks.\n",
"- 📊 [`data_platform` notebook](../data_platform/README.md) — a more advanced example that pulls data from Foxglove Data Management for offline analysis.\n",
"- 💬 [Foxglove Discord](https://foxglove.dev/community) — questions, feedback, and what we're working on next.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
34 changes: 34 additions & 0 deletions jupyter_notebooks/getting_started/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: "Get Started with the Foxglove Notebook Integration"
short_description: "Embed the Foxglove viewer in a Jupyter / Colab notebook and drive it with programmatic layouts"
colab_url: "https://colab.research.google.com/github/foxglove/tutorials/blob/main/jupyter_notebooks/getting_started/GettingStarted.ipynb"
---
# Foxglove Notebook Integration — Getting Started

A minimal, runnable companion to the [Foxglove notebook integration docs](https://docs.foxglove.dev/docs/notebook).

This notebook walks through:

- Installing `foxglove-sdk[notebook]`
- Creating a notebook buffer with `foxglove.init_notebook_buffer()`
- Logging messages and rendering them with the embedded Foxglove viewer
- Building layouts programmatically with the [`foxglove.layouts`](https://docs.foxglove.dev/docs/notebook/layouts) API, including nested `SplitContainer` and `TabContainer` examples driving real data

## Run in Colab

Launch the sample notebook directly in Colab.

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/foxglove/tutorials/blob/main/jupyter_notebooks/getting_started/GettingStarted.ipynb)

## Run locally

```bash
pip install "foxglove-sdk[notebook]" jupyterlab
jupyter lab GettingStarted.ipynb
```

The notebook works in JupyterLab, classic Jupyter, VS Code, and Google Colab — anywhere [`anywidget`](https://anywidget.dev/) is supported.

## Stay in touch

Join our [Discord](https://foxglove.dev/community) to ask questions, share feedback, and stay up to date on what our team is working on.
Loading