Skip to content

Commit

Permalink
Fix default thumbnail implementation, some documentation changes
Browse files Browse the repository at this point in the history
  • Loading branch information
mgeier committed Mar 12, 2023
1 parent 590a127 commit 9fd867f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 106 deletions.
39 changes: 28 additions & 11 deletions doc/gallery/default-thumbnail.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Default Thumbnails\n",
"# Default Thumbnail\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",
"By default,\n",
"the last image output of a notebook will be used as its thumbnail.\n",
"Without an image output, a placeholder will be used.\n",
"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`."
"However, if a thumbnail is explicitly assigned by\n",
"[Using Cell Metadata to Select a Thumbnail](cell-metadata.ipynb),\n",
"[Using a Cell Tag to Select a Thumbnail](cell-tag.ipynb) or\n",
"[Specifying a Thumbnail File](thumbnail-from-conf-py.ipynb),\n",
"these methods will take precedence."
]
},
{
Expand Down Expand Up @@ -45,14 +52,15 @@
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"x = np.linspace(-5, 5, 50)\n",
"ax.plot(x, np.sinc(x))"
"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."
"But the next cell is the last containing an image in the notebook,\n",
"so its last image output will be used as the thumbnail."
]
},
{
Expand All @@ -61,23 +69,32 @@
"metadata": {},
"outputs": [],
"source": [
"display(fig)\n",
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"x = np.linspace(-5, 5, 50)\n",
"ax.plot(x, -np.sinc(x), color='red')"
"ax.plot(x, -np.sinc(x), color='red');"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"orig_nbformat": 4
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
6 changes: 3 additions & 3 deletions doc/gallery/gallery-with-nested-documents.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
"Only links and the first section title are scanned,\n",
"everything else is ignored.\n",
"\n",
"* [Last Image Is Used by Default](default-thumbnail.ipynb)\n",
"* [Using a Cell Tag to Select a Thumbnail](cell-tag.ipynb)\n",
"* [Using Cell Metadata to Select a Thumbnail](cell-metadata.ipynb)\n",
"* [Using Cell Metadata to Select a Thumbnail and Provide a Tooltip](cell-metadata.ipynb)\n",
"* [Choosing from Multiple Outputs](multiple-outputs.ipynb)\n",
"* [Default Thumbnails](default-thumbnail.ipynb)\n",
"* [No Thumbnail Available](no-thumbnail.ipynb)\n",
"* [Specifying a Thumbnail File](thumbnail-from-conf-py.ipynb)\n",
"\n",
Expand All @@ -99,7 +99,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.11.2"
}
},
"nbformat": 4,
Expand Down
40 changes: 26 additions & 14 deletions doc/gallery/thumbnail-from-conf-py.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"source": [
"# Specifying Thumbnails in `conf.py`\n",
"\n",
"This notebook doesn't contain any thumbnail metadata.\n",
"\n",
"But in the file [conf.py](../conf.py),\n",
"This notebook doesn't contain a `nbsphinx-thumbnail`\n",
"[cell tag](cell-tag.ipynb) nor\n",
"[cell metadata](cell-metadata.ipynb).\n",
"Instead, in the file [conf.py](../conf.py),\n",
"a thumbnail is specified (via the\n",
"[nbsphinx_thumbnails](../configuration.ipynb#nbsphinx_thumbnails)\n",
"option),\n",
Expand Down Expand Up @@ -48,15 +49,6 @@
"we are creating an image file here:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib agg"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -74,7 +66,8 @@
"source": [
"fig, ax = plt.subplots()\n",
"ax.plot([4, 8, 15, 16, 23, 42])\n",
"fig.savefig('a-local-file.png')"
"fig.savefig('a-local-file.png')\n",
"plt.close() # avoid plotting the figure"
]
},
{
Expand All @@ -97,6 +90,25 @@
"\n",
"Please note that the notebook name does *not* contain the `.ipynb` suffix."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the following plot is *not* used as a thumbnail\n",
"because the `nbsphinx_thumbnails` setting overrides\n",
"[the default behavior](default-thumbnail.ipynb)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=[6, 3])\n",
"ax.plot([4, 9, 7, 20, 6, 33, 13, 23, 16, 62, 8], 'r:');"
]
}
],
"metadata": {
Expand All @@ -115,7 +127,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.11.2"
}
},
"nbformat": 4,
Expand Down
158 changes: 80 additions & 78 deletions src/nbsphinx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
'text/plain',
)

MIME_TYPE_SUFFIXES = {
THUMBNAIL_MIME_TYPES = {
'image/svg+xml': '.svg',
'image/png': '.png',
'image/jpeg': '.jpg',
Expand Down Expand Up @@ -410,106 +410,106 @@ def from_notebook_node(self, nb, resources=None, **kw):
resources['nbsphinx_widgets'] = True

thumbnail = {}
thumbnail_filename = None

def warning(msg, *args):
logger.warning(
'"nbsphinx-thumbnail": ' + msg, *args,
'"nbsphinx-thumbnail" in cell %s: ' + msg, cell_index, *args,
location=resources.get('nbsphinx_docname'),
type='nbsphinx', subtype='thumbnail')
thumbnail['filename'] = _BROKEN_THUMBNAIL

for cell_index, cell in enumerate(nb.cells):
# 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:
if 'nbsphinx-thumbnail' in cell.metadata:
data = cell.metadata['nbsphinx-thumbnail'].copy()
output_index = data.pop('output-index', -1)
output_index = data.pop('output-index', None)
tooltip = data.pop('tooltip', '')

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

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

else:
continue

if thumbnail and thumbnail_cell:
warning('Only allowed onced per notebook')
if cell.cell_type != 'code':
warning(
'Only allowed in code cells; wrong cell type: "%s"',
cell.cell_type)
break

if not cell.outputs:
if thumbnail_cell:
warning('No outputs in cell %s', cell_index)
break

continue
if thumbnail:
warning('Only allowed once per notebook')
break

if output_index == -1:
if output_index is None:
output_index = len(cell.outputs) - 1
elif output_index >= len(cell.outputs):
warning('Invalid "output-index" in cell %s: %s',
cell_index, output_index)
try:
suffix = _extract_thumbnail(cell, output_index)
except _ExtractThumbnailException as e:
warning(*e.args)
break

out = cell.outputs[output_index]

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

continue

for mime_type in DISPLAY_DATA_PRIORITY_HTML:
if mime_type not in out.data or mime_type not in MIME_TYPE_SUFFIXES:
continue

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

if not thumbnail:
# No explicit thumbnails were specified in the notebook.
# Now we are looking for the last output image in the notebook.
for cell_index, cell in reversed(list(enumerate(nb.cells))):
if cell.cell_type == 'code':
for output_index in reversed(range(len(cell.outputs))):
try:
suffix = _extract_thumbnail(cell, output_index)
except _ExtractThumbnailException:
continue
thumbnail['filename'] = '{}_{}_{}{}'.format(
resources['unique_key'],
cell_index,
output_index,
suffix,
)
# NB: we use this as marker for implicit thumbnail:
thumbnail['tooltip'] = None
break
else:
continue
break

continue
if thumbnail:
resources['nbsphinx_thumbnail'] = thumbnail
return rststr, resources

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

break
class _ExtractThumbnailException(Exception):
"""Internal exception thrown by _extract_thumbnail()."""

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
def _extract_thumbnail(cell, output_index):
if not cell.outputs:
raise _ExtractThumbnailException('No outputs')
if output_index not in range(len(cell.outputs)):
raise _ExtractThumbnailException(
'Invalid "output-index": %s', output_index)
out = cell.outputs[output_index]
if out.output_type not in {'display_data', 'execute_result'}:
raise _ExtractThumbnailException(
'Unsupported output type in output %s: "%s"',
output_index, out.output_type)
for mime_type in DISPLAY_DATA_PRIORITY_HTML:
if mime_type not in out.data or mime_type not in THUMBNAIL_MIME_TYPES:
continue
return THUMBNAIL_MIME_TYPES[mime_type]
else:
raise _ExtractThumbnailException(
'Unsupported MIME type(s) in output %s: %s',
output_index, set(out.data))


class NotebookParser(rst.Parser):
Expand Down Expand Up @@ -1735,24 +1735,26 @@ def has_wildcard(pattern):
conf_py_thumbnail = candidate

thumbnail = app.env.nbsphinx_thumbnails.get(doc, {})
# NB: "None" is used as marker for implicit thumbnail:
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
# 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 and not was_implicit_thumbnail:
elif filename and tooltip is not None:
# 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
# implicit thumbnail from the last image in the notebook
assert tooltip is None
tooltip = ''
filename = os.path.join(
base, app.builder.imagedir, filename)
else:
Expand Down

0 comments on commit 9fd867f

Please sign in to comment.