Marimo notebooks for demos

Recently I had to give a live demonstration of my declarative geospatial for Python, Yirgacheffe, to an audience of other programmers at the PROPL 2025 workshop. This presented a couple of challenges, not least of all because whilst Yirgacheffe spends a lot of time processing image files (GeoTIFFs), that work is done in large data-science pipelines where it is rare you visualise the data directly. Even if you did, the files themselves can be up to hundreds of gigabytes, so don't make for a very interactive demo when trying to load them up in a geospatial visualisation tool like QGIS.

For a successful demo, my criteria were:

People must see basic code using Yirgacheffe to show how simple and succinct its API is

People must see the code being either written live or at least modified to ensure a sense of liveness for the demo

The outputs must be images and not text on screen - its much harder to keep an audience engaged when you show them console output vs image output

Whilst in theory I could do this using Python in the console and saving images to disk and then opening them in QGIS, I ruled this out: it puts up a gap between the code executing and the visualisation that slows down the presentation, has the chance to go wrong as I juggle windows, and just adds a mental leap required of the audience that the files I'm opening are the ones related to the code I just executed.

My next though was to do the same, but have Python render and display the results using matplotlib, similar to how other Python geospatial libraries do (e.g., rasterio). To enable this required a little work on extending Yirgacheffe to make it work with matplotlib, and that did work, but I found that under macOS Tahoe I had a weird issue where after I closed the plot window I couldn't interact with the terminal unless I tabbed away and back (possibly related to the ghost windows bug people have complained about in Tahoe). So whilst I could work around it, it was not something I wanted to deal with in a live demo.

At this point I opted to abandon the console, and try Jupyter notebooks. These notebooks let you mix text and code, and there is even a presentation plugin, called RISE, that lets turn your notebook into slides. This did not make me happy for several reasons. Firstly, despite Jupyter being very popular, I really struggled with editing the notebooks. Perhaps it's just because I'm using Safari as my browser rather than the more ubiquitous Chrome, but I stuggled when adding new blocks and re-arranging blocks was next to impossible for me. Even allowing for that being a setup issue, I still didn't quite get what I wanted: RISE will execute your notebook before it runs, so you can't show incremental workflow as I needed. The best I could do was go into editor mode, and then hit shift-enter to execute each block. Finally, and this is entirely subjective, I just think the notebooks don't look that good in edit mode, and I struggled to get code and images on screen in a way that looked good.

At this point I'd exhausted my ideas, so I asked for advice on the Nordic-RSE zulip chat, as I know that a few people over there have been using notebooks to teach people coding. There Luca Ferranti pointed me towards Marimo, which is a more modern take on Python notebooks.

The main differentiator for Marimo from Jupyter is that it is designed to be reactive, and so as you change one bit of code, the code that depends on those values will be re-evaluated, and so you can tweak things and have graphs and other visualisations update on the fly. You can also have UI elements appear that set variables in your code, and that code will be re-evaluated as you interact with the UI elements, and that then ripples down etc. Basically it keeps a dependancy graph between the blocks for this sort of thing. They have a large example gallery so I recommend you have a look and see what people have done with it.

To cut a long story short, I went with Marimo for my talk:

It wasn't a perfect fit, so here are some of the pros and cons I found trying to use Marimo in this way.

Firstly, it's less visually cluttered to my mind than Jupyter, so made for a nicer presentation experience. Subjective and superficial, but for communication of ideas I think this is important.

Secondly, editing the notebook is so much easier with Marimo than Jupyter. Not only was editing the notebook in browser much easier, with nice big buttons for adding different block types, but also the notebook that Marimo stores is decorated Python, and so you can just write the notebook in a text editor if you want like you would a regular Python program. This also helps with things like revision control and collaboration as PRs will make a lot more sense.

@app.cell(hide_code=True)
def _():
mo.md(
r"""
# Demo 1: Sentinel-2 cloud removal

Here we'll remove cloud pixels from a Sentinel-2 layer. First we'll load a visual band, and the
Scene Classification Layer:
"""
)
return

@app.cell
def _():
veg_layer = yg.read_raster("data/T37NCG_20250909T073609_B06_20m.jp2")
scl_layer = yg.read_raster("data/T37NCG_20250909T073609_SCL_20m.jp2")
return scl_layer, veg_layer

@app.cell(hide_code=True)
def _():
mo.md(r"""Let's take a look at the layers, we can see it has cloud in it:""")
return

@app.cell(hide_code=True)
def _(scl, veg_layer):
plt.figure(figsize=(16, 10))

ax1 = plt.subplot(1, 2, 1)
ax1.set_title("Vegetation Red Edge 2")
veg_layer.show(ax1, cmap="turbo")

ax2 = plt.subplot(1, 2, 2)
ax2.set_title("Scene Classification Layer")
scl_layer.show(ax2, cmap="grey")

plt.show()
return

Thirdly, like Jupyter, it does have support for matplotlib built in, so my visualisations just worked. They didn't work as well as many of the Marimo demos, as matplotlib itself isn't reactive, so you don't get any pretty redrawing animations that many of the demos have, but that's more down to my using matplotlib than it being Marimo's fault.

The presentation mode was similarly useless for me, as it executes your notebook to the end in that mode. So again I had to run in edit mode rather than the built in presenter mode.

There is a lazy evaluation mode that you need to opt into, as by default it's trying to do the clever live updates thing, and I really wanted to incrementally show I was running the code. Not a great advance on how I was with Jupyter really, but I did for my final demo of the talk leverage the live updates by having a UI widget with many species in it and let me change with AOH I was calculating and have it live update.

You can collapse code blocks, so in my demo I could hide a bunch of the boiler plate code so it didn't clutter the screen, allowing me to more readily focus on the narrative I was trying to tell when presenting. You still need to run it in lazy mode by clicking on the header of the collapsed block, but that's much nicer than filling the screen with things that aren't necessary for the point I was trying to illustrate.

If you want to try out my talk, the netbooks are on github and I've included the data for the first demo, so you should be able to just run that. Unfortunately the other demos require data that is either too large or I can't distribute due to licensing terms. But they're simple enough that you should get the idea of how easy it was to throw the demo together.