Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the last image in a notebook as the default thumbnail #707

Closed
wants to merge 3 commits into from
Closed
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: 2 additions & 1 deletion doc/configuration.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,8 @@
"(i.e. source file without suffix but with subdirectories)\n",
"-- optionally containing wildcards --\n",
"to a thumbnail path to be used in a\n",
"[thumbnail gallery](subdir/gallery.ipynb).\n",
"[thumbnail gallery](subdir/gallery.ipynb). Thumbnails specified\n",
"in notebooks will override those provided in this dictionary.\n",
"\n",
"See [Specifying Thumbnails](gallery/thumbnail-from-conf-py.ipynb)."
]
Expand Down
19 changes: 18 additions & 1 deletion doc/gallery/cell-tag.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The following cell has the `nbsphinx-thumbnail` tag:"
"The following cell has the `nbsphinx-thumbnail` tag, which will take precedence over the default of the last image in the notebook:"
]
},
{
Expand All @@ -55,6 +55,23 @@
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Although the next cell has an image, it won't be used as the thumbnail, due to the tag on the one above."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"ax.scatter(range(10), [0, 8, 9, 1, -8, -10, -3, 7, 10, 4])"
]
}
],
"metadata": {
Expand Down
83 changes: 83 additions & 0 deletions doc/gallery/default-thumbnail.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"nbsphinx": "hidden"
},
"source": [
"This notebook is part of the `nbsphinx` documentation: https://nbsphinx.readthedocs.io/."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Default Thumbnails\n",
"\n",
"By default, a notebook with an image output will use the last of these as its thumbnail. Without an image output, a placeholder will be used. See [a notebook with no thumbnail](no-thumbnail.ipynb) for an example.\n",
"\n",
"However, if a thumbnail is explicitly assigned by [Using Cell Metadata to Select a Thumbnail](cell-metadata.ipynb), [Using a Cell Tag to Select a Thumbnail](cell-tag.ipynb) or [Specifying Thumbnails in `conf.py`](thumbnail-from-conf-py.ipynb), these methods will take precedence: cell tags and metadata are higher priority than in `conf.py`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Although the next cell contains an image (a plot), it won't be used as the thumbnail because it's not the last in the notebook, and we haven't explicitly tagged it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"x = np.linspace(-5, 5, 50)\n",
"ax.plot(x, np.sinc(x))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But the next cell is the last containing an image in the notebook, so it will be used as the thumbnail."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"x = np.linspace(-5, 5, 50)\n",
"ax.plot(x, -np.sinc(x), color='red')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
1 change: 1 addition & 0 deletions doc/gallery/gallery-with-nested-documents.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"* [Using a Cell Tag to Select a Thumbnail](cell-tag.ipynb)\n",
"* [Using Cell Metadata to Select a Thumbnail](cell-metadata.ipynb)\n",
"* [Choosing from Multiple Outputs](multiple-outputs.ipynb)\n",
"* [Default Thumbnails](default-thumbnail.ipynb)\n",
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't be the default behavior the first in this list?

"* [No Thumbnail Available](no-thumbnail.ipynb)\n",
"* [Specifying a Thumbnail File](thumbnail-from-conf-py.ipynb)\n",
"\n",
Expand Down
102 changes: 74 additions & 28 deletions src/nbsphinx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
'text/plain',
)

MIME_TYPE_SUFFIXES = {
'image/svg+xml': '.svg',
'image/png': '.png',
'image/jpeg': '.jpg',
}

# The default rst template name is changing in nbconvert 6, so we substitute
# it in to the *extends* directive.
RST_TEMPLATE = """
Expand Down Expand Up @@ -600,6 +606,7 @@ def from_notebook_node(self, nb, resources=None, **kw):
resources['nbsphinx_widgets'] = True

thumbnail = {}
thumbnail_filename = None

def warning(msg, *args):
logger.warning(
Expand All @@ -609,64 +616,94 @@ def warning(msg, *args):
thumbnail['filename'] = _BROKEN_THUMBNAIL

for cell_index, cell in enumerate(nb.cells):
if 'nbsphinx-thumbnail' in cell.metadata:
# figure out if this cell is explicitly tagged
# but if it's not, we'll default to the last figure in the notebook
# if one exists
metadata_cell = 'nbsphinx-thumbnail' in cell.metadata
tagged_cell = 'nbsphinx_thubnail' in cell.metadata.get('tags',[])
thumbnail_cell = metadata_cell or tagged_cell

if metadata_cell:
data = cell.metadata['nbsphinx-thumbnail'].copy()
output_index = data.pop('output-index', -1)
tooltip = data.pop('tooltip', '')

if data:
warning('Invalid key(s): %s', set(data))
break
elif 'nbsphinx-thumbnail' in cell.metadata.get('tags', []):
else:
output_index = -1
tooltip = ''
else:
continue

if cell.cell_type != 'code':
warning('Only allowed in code cells; cell %s has type "%s"',
if thumbnail_cell:
warning('Only allowed in code cells; cell %s has type "%s"',
cell_index, cell.cell_type)
break

continue

if thumbnail and thumbnail_cell:
warning('Only allowed onced per notebook')
break
if thumbnail:
warning('Only allowed once per notebook')
break

if not cell.outputs:
warning('No outputs in cell %s', cell_index)
break
if tooltip:
thumbnail['tooltip'] = tooltip
if thumbnail_cell:
warning('No outputs in cell %s', cell_index)
break

continue

if output_index == -1:
output_index = len(cell.outputs) - 1
elif output_index >= len(cell.outputs):
warning('Invalid "output-index" in cell %s: %s',
cell_index, output_index)
cell_index, output_index)
break

out = cell.outputs[output_index]

if out.output_type not in {'display_data', 'execute_result'}:
warning('Unsupported output type in cell %s/output %s: "%s"',
if thumbnail_cell:
warning('Unsupported output type in cell %s/output %s: "%s"',
cell_index, output_index, out.output_type)
break
break

continue

for mime_type in DISPLAY_DATA_PRIORITY_HTML:
if mime_type not in out.data:
continue
if mime_type == 'image/svg+xml':
suffix = '.svg'
elif mime_type == 'image/png':
suffix = '.png'
elif mime_type == 'image/jpeg':
suffix = '.jpg'
else:
if mime_type not in out.data or mime_type not in MIME_TYPE_SUFFIXES:
continue
thumbnail['filename'] = '{}_{}_{}{}'.format(

thumbnail_filename = '{}_{}_{}{}'.format(
resources['unique_key'],
cell_index,
output_index,
suffix,
MIME_TYPE_SUFFIXES[mime_type],
)
break
else:
warning('Unsupported MIME type(s) in cell %s/output %s: %s',
if thumbnail_cell:
warning('Unsupported MIME type(s) in cell %s/output %s: %s',
cell_index, output_index, set(out.data))
break

continue

if thumbnail_cell:
thumbnail['filename'] = thumbnail_filename
thumbnail['implicit'] = False
if tooltip:
thumbnail['tooltip'] = tooltip

break

else:
# default to the last figure in the notebook, if it's a valid thumbnail
if thumbnail_filename:
thumbnail['filename'] = thumbnail_filename
thumbnail['implicit'] = True

resources['nbsphinx_thumbnail'] = thumbnail
return rststr, resources

Expand Down Expand Up @@ -1890,15 +1927,24 @@ def has_wildcard(pattern):
thumbnail = app.env.nbsphinx_thumbnails.get(doc, {})
tooltip = thumbnail.get('tooltip', '')
filename = thumbnail.get('filename', '')
was_implicit_thumbnail = thumbnail.get('implicit', True)

# thumbnail priority: broken, explicit in notebook, from conf.py
# implicit in notebook, default
if filename is _BROKEN_THUMBNAIL:
filename = os.path.join(
base, '_static', 'nbsphinx-broken-thumbnail.svg')
elif filename:
elif filename and not was_implicit_thumbnail:
# thumbnail from tagged cell or metadata
filename = os.path.join(
base, app.builder.imagedir, filename)
elif conf_py_thumbnail:
# NB: Settings from conf.py can be overwritten in notebook
filename = os.path.join(base, conf_py_thumbnail)
elif filename:
# implicit thumbnail from an image in the notebook
filename = os.path.join(
base, app.builder.imagedir, filename)
else:
filename = os.path.join(
base, '_static', 'nbsphinx-no-thumbnail.svg')
Expand Down