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](https://gopherize.me).",
"alt": "Jupyter Gopher",
"full_size": False, # Default=False
"primary": True
})
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,

Falsifiable includes it in the image src without additional processing,
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,

/files
included Jupyter Gopher
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 (p.falsifiable.page), 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
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<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" />
</svg>
If you edit the cell metadata so that the format
is -%svg
, as in,
-%svg
falsifaible elides the first line,
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<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" />
</svg>
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
,
output-generator
tag.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
%%plantuml
@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@enduml
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")
sns.despine();
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.