dev / notes

Using Images with Falsifiable

IPython.display.Image (Preferred Method)

The easiest way to embed an image is to use the Image class from Ipython.display. It’s useful because you can set properties like the caption, alt text, whether or not the image is the primary image for the document (used in things like twitter cards), and whether or not the image should get full size treatment.

from IPython.display import Image

Image("imgs/jupyter_gopher_smallest.gif", metadata={
    "caption": "A Jupyter Gopher made with [gopherize](",
    "alt": "Jupyter Gopher",
    "full_size": False, # Default=False
    "primary": True
A Jupyter Gopher made with gopherize.Jupyter Gopher

If you emit an image with Ipython.display.Image, Jupyter embeds it via a data url. This is useful. It means that when you send an .ipynb file the recipient has everything necessary to render it exactly. Consequently, this method is preferred because it isn’t intrusive and it makes replication easier.

Markdown linking

Linking images via markdown complicates things.

If you create a markdown cell with a fully qualified URL such as,

![Gopher]( "Directly linked Jupyter Gopher")

Falsifiable includes it in the image src without additional processing,

Directly linked Jupyter GopherGopher

However, this means that, if the image is moved remotely, the notebook will fail to reflect the change and the render will have missing information.Moreover, in the future, Falsifiable may offer a setting that disallows external images as they afford the opportunity for tracker injection.

Alternatively, Jupyter allows relative (to the directory jupyter started in) file linking via the files prefix. For example, you may embed an image with,

![gopher](/files/notes/imgs/jupyter_gopher_smallest.gif "`/files` included Jupyter Gopher")

/files included Jupyter Gophergopher

The first problem with this is that the location of the image is now dependent upon the directory you ran jupyter lab in. If you change that, the /files/ path maps to a different root. The second problem is that, again, anyone who has a copy of the .ipynb file does not also necessarily have the correct associated file.

Furthermore, both cases – relative and absolute – introduce another problem for falsifaible: how do you create a consistent signature? The .ipynb is no longer self-contained. Two notebooks that render differently could now have the same hash because the image data is not part of the signature.

In the case of relative image include, falsifiable opts for a solution that is intrusive, but minimally so. At the time of uploading or local rendering, it rewrites the notebook using data URL attachment embedding. But, the source notebook does not change. As a result, anyone who downloads the notebook from falsifiable, has a full self-contained copy.

This is intrusive in one important way. If you were to manually compute the hash of your local file and the downloaded one, they would no longer match.This is why IPython.display.Image is still preferable. However, if you were to use abreka nb hash your.ipynb, they would, as the tool performs this additional check.

SVG Rendering

This previous post explains why SVGs are hard to include for user-generated content. To make it possible, Falsifiable keeps them on a different domain (, and only includes them via the src attribute in an img tag. If you are using IPython, you can use the svg cell magic to write the document directly in your notebook,

<svg width="100" height="100" xmlns="">
  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="4" fill="yellow" />
  <circle cx="40" cy="35" r="4" stroke="none" fill="black" />
  <circle cx="70" cy="35" r="4" stroke="none" fill="black" />
  <circle cx="60" cy="60" r="8" strokec="none" fill="black" />

If you edit the cell metadata so that the format is -%svg, as in,

Cell metadata edited so that the format is -%svgjupyter lab cell metadata editor

falsifaible elides the first line,

<svg width="100" height="100" xmlns="">
  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="4" fill="yellow" />
  <circle cx="40" cy="35" r="4" stroke="none" fill="black" />
  <circle cx="70" cy="35" r="4" stroke="none" fill="black" />
  <circle cx="60" cy="60" r="8" strokec="none" fill="black" />

Cell Eliding

The image above showing how to edit the cell metadata to remove the cell magic prefix and set the code highlighter to SVG used IPython.display.Image for rendering. But, you’ll notice the code didn’t show up. That’s because one of the cell tags was set to output-generator,

Cell metadata edited to include output-generator tag.jupyter lab cell metadata editor

You can use tags to remove the entire cell (remove_cell, private, setup), just the code portion (remove_input, output-generator), or the just the output (remove_output, assertion).

Generated Content

Most of the time, you’re probably only interested in including your plots. This happens automatically. For example, if you were using my iplantuml cell magic,

import iplantuml

Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response

Or, my itikz cell magic,

%load_ext itikz
%%itikz --temp-dir --implicit-pic --file-prefix conway-
\draw[help lines] grid (5, 5);
\draw[fill=black] (1, 1) rectangle (2, 2);
\draw[fill=black] (2, 1) rectangle (3, 2);
\draw[fill=black] (3, 1) rectangle (4, 2);
\draw[fill=black] (3, 2) rectangle (4, 3);
\draw[fill=black] (2, 3) rectangle (3, 4);

And finally, your basic every day plotting,

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

t = np.linspace(-10, 10, 101)
y = 1 / (1 + np.exp(-t))
plt.plot(t, y)
plt.xlabel("Time, $t$")
plt.ylabel("Proportion Project Complete")

The one caveat with user generated plots is that I have not come up with a good unobtrusive solution for adding captions and alt tags yet. Matplotlib doesn’t have a means of injecting metadata like IPython.display.Image. I’m going back and forth between editing the cell metadata directly (kinda kludgy) or using a small context manager that grabs the current figure, saves it to memory, then uses IPython.display internally.

I’ll update this section once one feels better.