Quarto: Notebooks for IPOL demos

Quentin Bammey

3rd of April, 2022

What is an IPOL demo?

https://ipolcore.ipol.im/demo/clientApp/demo.html?id=5555531082037

Today’s plan

What you will learn today:

  • Render a Jupyter Notebook into a beautiful web page
  • Parameterize the notebook to execute and render it with different parameters

At the end of this talk, you will know how to make and preview a local demo.

Tomorrow: Jeremy and Miguel will show you how to put the demo online, and ensure its reproducibility with Docker

Warning

We are not making interactive notebooks where the user modifies and runs the code himself.

Much simpler:

  1. The user provides inputs and parameters
  2. The notebook is run with thoses values…
  3. And is then rendered as a web page

I am not using Python, what to do?

  • Jupyter notebooks can be used with plenty of languages, even compiled ones such as C++
  • For R users, Quarto also supports RStudio
  • You can even use enhanced markdown files!
  • You can also make a demo without notebooks

Getting started

What you will need:

  • A jupyter notebook environment: jupyter notebook or jupyter lab (not vscode which is incompatible with parameterization features)
  • For parameterization, papermill: pip install --user papermill or conda install -c conda-forge papermill
  • Quarto

An existing notebook

import imageio
from matplotlib import pyplot as plt
img_path = "input_0.png"
img = imageio.imread(img_path)
plt.imshow(img)
plt.axis('off')
plt.show()

n_crop = 100
img = img[n_crop:]
plt.imshow(img)
plt.axis('off')
plt.show()

Add the YAML front matter

First cell of the notebook, as a Raw cell theme_ipol.scss: get it from the source code in the last slide and place it in the same directory as the notebook (ensures font and size consistency with IPOL).

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true

---
import imageio
from matplotlib import pyplot as plt
img_path = "input_0.png"
img = imageio.imread(img_path)
plt.imshow(img)
plt.axis('off')
plt.show()

n_crop = 100
img = img[n_crop:]
plt.imshow(img)
plt.axis('off')
plt.show()

Save the outputs separately

(will be important for archiving the demo)

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from matplotlib import pyplot as plt
img_path = "input_0.png"
img = imageio.imread(img_path)
plt.imshow(img)
plt.axis('off')
plt.show()

n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
plt.imshow(img)
plt.axis('off')
plt.show()

Display the data correctly

Use IPython.display to show images, videos, audio, not a plotting library such as matplotlib. Why?

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image
img_path = "input_0.png"
img = imageio.imread(img_path)
display(Image(img_path))

n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

Use markdown cells to display text

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image

Input image

img_path = "input_0.png"
img = imageio.imread(img_path)
display(Image(img_path))

Cropped image (output)

n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

Render the demo locally

quarto render main.ipynb -o main.html

To execute the notebook while rendering:

quarto render main.ipynb -o main.html --execute

Preview mode (re-render as the notebook is saved):

quarto preview main.ipynb -o main.html

Et voilà! Your first demo

Input image

Cropped image (output)

Better layout: tabs

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image
::: {.panel-tabset}
### Input image
img_path = "input_0.png"
img = imageio.imread(img_path)
display(Image(img_path))

### Cropped image (output)
n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

:::

Better layout: tabs (render)

Better layout: columns

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image
:::: {.columns}
::: {.column width="50%"}
### Input image
img_path = "input_0.png"
img = imageio.imread(img_path)
display(Image(img_path))

:::

::: {.column width="50%"}
### Cropped image (output)
n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

:::
::::

Better layout: columns (render)

Input image

Cropped image (output)

Plotting and visualizing results

  • Input and output files can be displayed with IPython.display: prefer this to plotting libraries
  • Static plots: matplotlib, seaborn
  • Interactive plots: plotly, altair, bokeh
  • Focus first on the demo, only then on pretty visualizations (especially if you are new to IPOL) (exceptions: 3D data, etc)
  • ⇒ Start with what you know (usually matplotlib and seaborn)

Parameterization

Inputs and Parameters

You want to be able to run your code with different inputs and parameters

  • Inputs: IPOL inputs have a standardized name (input_0.png, input_1.png, etc)
  • Parameters: let’s see now!

Parameters

Simplified version of then notebook above

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image
img_path = "input_0.png"
img = imageio.imread(img_path)
n_crop = 100
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

Move the parameters declaration to a specific cell

(with their default value)

---
execute:
    echo: false
    warning: false
    
format:
    html:
        theme: "theme_ipol.scss"
        self-contained: true
---
import imageio
from IPython.display import display, Image
img_path = "input_0.png"
img = imageio.imread(img_path)
img = img[n_crop:]
imageio.imsave("output.png", img)
display(Image("output.png"))

Tag the parameters cell

With the tag parameters See here to add tags.

Execute and render with parameters

quarto render main.ipynb --execute -P n_crop:10

Execute and render with parameters

quarto render main.ipynb --execute -P n_crop:100

Execute and render with parameters

quarto render main.ipynb --execute -P n_crop:200

Parameters

If you have more than one parameter: prefix each with -P, e.g. quarto render main.ipynb --execute -P n_crop:10 -P foo:5 -P bar:8

Warning: For parameters with free text, enclose the parameters in double quotes: -P "param:something else". This will still cause a few problems with IPOL: ask us for help.

Getting ready for uploading the demo

A few points to fix first

SRCDIR

On IPOL, the notebook is moved before execution. To be able to import code or load data (e.g. model weights), use the SRCDIR parameter, with a default value of '.'

SRCDIR = '.'
n_crop = 100

And add SRCDIR to the list of import directories to be able to import local code:

import sys
sys.path.append(SRCDIR)  # Will enable local code imports

Load your model relative to SRCDIR:

torch.load(os.path.join(SRCDIR, "src/foo/bar/model_weights.pt"))

A complete example

See here for a complete demo made using a notebook, and here for the source code (and the notebook).

A beautiful notebook demo

Tweaking the notebook Layout / Interactive visualizations with plotly

Tweaking Quarto: tabsets

::: {.panel-tabset}
### An image
display(Image("p1.jpg"))
### A video
display(Video("video.mov"))
### A sound
display(Audio("strange_sound.mp3"))
:::

Tweaking Quarto: tabsets

Tweaking Quarto: Columns

:::: {.columns}

::: {.column width="25%"}
display(Image("p1.jpg"))
:::

::: {.column width="25%"}
display(Image("p2.jpg"))
:::

::: {.column width="25%"}
display(Image("p3.jpg"))
:::

::: {.column width="25%"}
display(Image("p4.jpg"))
:::

::::

Tweaking Quarto: Columns

Tweaking Quarto: display size

Y, X, _  = imageio.imread("p1.jpg").shape
Y, X
display(Image("p1.jpg", width=max(300, X), height=max(300, Y)))

Tweaking Quarto: Body size

If you need a wider content size…

Interactive visualization with Plotly

Should I use plotly?

You should use plotly (or bokeh, altair)

  • Interactive output (vs. matplotlib and seaborn: static output)
  • Almost necessary for decent visualization in some cases (e.g. time series if zooming ability needed, 3D data)
  • Still useful and usually more beautiful than default matplotlib plots in most other cases
  • Very quick to use in most standard cases: pandas data, and if what you want to do is natively supported

Should I use plotly?

But

  • If not necessary for your demo, start with what you know and can do quickly (usually matplotlib/seaborn)
  • Make a demo that works well, write at least a draft of the article
  • Then you can spend more time on beautifying your demo
  • I’m almost always in the lab so I can still help you later with that

IMPORTANT

Put this with your imports if you use plotly

import plotly.io as pio
pio.renderers.default = 'notebook_connected'

Entry point: plotly express

  • If you have (or can make) pandas data
  • If what you want to do is “standard” (in a very, very large sense)
  • Use premade functions that do all the difficult work for you

Entry point: plotly express

from plotly import express as px
df = px.data.tips()
fig = px.histogram(df, x="total_bill", color="sex", marginal="rug", hover_data=df.columns)
fig.show()
from plotly import express as px
df = px.data.gapminder()
fig = px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])
fig.show()

Not enough? Graph objects

  • Start with an empty figure or use plotly express as an entry point
  • Then add your own traces to the figure
  • Only do it if needed: express is much faster, easier to use. It is enough in most cases

Alternative: Bokeh

  • Interesting for non-standard plots: lower-level, you define what you plot (like matplotlib) rather than the data.
  • Will take more time and lines than plotly if what you want already exists in plotly
  • We already have a demo with bokeh (WIP)
  • Important:
from bokeh.io import output_notebook
output_notebook(resources=bkh.resources.Resources(mode='cdn'), hide_banner=True)

Show pandas data

  • Set the float print precision for legibility! (idem if you show numpy data, etc)
  • Plenty of display options, but if you already use pandas you probably know them better than I do.
pd.options.display.precision = 2
df
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
... ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2

244 rows × 7 columns

Useful references