Skip to content

Commit aa187b5

Browse files
twieckiOriolAbril
andauthored
Get thumbnails working for gallery (#340)
* Initial version of gallery using nbsphinx with a hack to make it work with myst_nb. * Add gallery. * Run black. * Add all dirs to gallery. * WIP: Trying to get thumbnails to work. * Remove sphinxext stuff * Remove print and debug stuff. * Remove unused method. * fix readthedocs config * sort inputs and requirements Co-authored-by: Oriol (ZBook) <[email protected]>
1 parent 2cf0ccf commit aa187b5

File tree

5 files changed

+111
-58
lines changed

5 files changed

+111
-58
lines changed

.readthedocs.yml

-2
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ python:
77
version: "3.8"
88
install:
99
- requirements: requirements-docs.txt
10-
- method: pip
11-
path: .

examples/conf.py

+15-55
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
import os, sys
22
from sphinx.application import Sphinx
33

44
# -- Project information -----------------------------------------------------
@@ -8,6 +8,8 @@
88

99
# -- General configuration ---------------------------------------------------
1010

11+
sys.path.insert(0, os.path.abspath("../sphinxext"))
12+
1113
# Add any Sphinx extension module names here, as strings. They can be
1214
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
1315
# ones.
@@ -23,6 +25,7 @@
2325
"sphinx_codeautolink",
2426
"notfound.extension",
2527
"sphinx_gallery.load_style",
28+
"gallery_generator",
2629
]
2730

2831
# List of patterns, relative to source directory, that match files and
@@ -46,22 +49,19 @@ def hack_nbsphinx(app: Sphinx) -> None:
4649
GalleryNode,
4750
NbGallery,
4851
patched_toctree_resolve,
49-
NotebookParser,
50-
NbInput,
51-
NbOutput,
52-
NbInfo,
53-
NbWarning,
54-
CodeAreaNode,
55-
depart_codearea_html,
56-
visit_codearea_latex,
57-
depart_codearea_latex,
58-
GetSizeFromImages,
5952
)
6053
from sphinx.environment.adapters import toctree
6154

62-
nbsphinx_thumbnails = {
63-
"case_studies/stochastic_volatility": "_static/stochastic_volatility.png",
64-
}
55+
from glob import glob
56+
57+
nb_paths = glob("examples/*/*.ipynb")
58+
nbsphinx_thumbnails = {}
59+
for nb_path in nb_paths:
60+
png_file = os.path.join("_static", os.path.splitext(os.path.split(nb_path)[-1])[0] + ".png")
61+
nb_path_rel = os.path.splitext(
62+
os.path.join(*os.path.normpath(nb_path).split(os.path.sep)[1:])
63+
)[0]
64+
nbsphinx_thumbnails[nb_path_rel] = png_file
6565

6666
def builder_inited(app: Sphinx):
6767
if not hasattr(app.env, "nbsphinx_thumbnails"):
@@ -70,46 +70,6 @@ def builder_inited(app: Sphinx):
7070
def do_nothing(*node):
7171
pass
7272

73-
app.add_source_parser(NotebookParser)
74-
# app.add_config_value('nbsphinx_execute', 'auto', rebuild='env')
75-
app.add_config_value("nbsphinx_kernel_name", "", rebuild="env")
76-
app.add_config_value("nbsphinx_execute_arguments", [], rebuild="env")
77-
app.add_config_value("nbsphinx_allow_errors", False, rebuild="")
78-
app.add_config_value("nbsphinx_timeout", None, rebuild="")
79-
app.add_config_value("nbsphinx_codecell_lexer", "none", rebuild="env")
80-
app.add_config_value("nbsphinx_prompt_width", "4.5ex", rebuild="html")
81-
app.add_config_value("nbsphinx_responsive_width", "540px", rebuild="html")
82-
app.add_config_value("nbsphinx_prolog", None, rebuild="env")
83-
app.add_config_value("nbsphinx_epilog", None, rebuild="env")
84-
app.add_config_value("nbsphinx_input_prompt", "[%s]:", rebuild="env")
85-
app.add_config_value("nbsphinx_output_prompt", "[%s]:", rebuild="env")
86-
app.add_config_value("nbsphinx_custom_formats", {}, rebuild="env")
87-
# Default value is set in config_inited():
88-
app.add_config_value("nbsphinx_requirejs_path", None, rebuild="html")
89-
# Default value is set in config_inited():
90-
app.add_config_value("nbsphinx_requirejs_options", None, rebuild="html")
91-
# This will be updated in env_updated():
92-
app.add_config_value("nbsphinx_widgets_path", None, rebuild="html")
93-
app.add_config_value("nbsphinx_widgets_options", {}, rebuild="html")
94-
# app.add_config_value('nbsphinx_thumbnails', {}, rebuild='html')
95-
app.add_config_value("nbsphinx_assume_equations", True, rebuild="env")
96-
97-
app.add_directive("nbinput", NbInput)
98-
app.add_directive("nboutput", NbOutput)
99-
app.add_directive("nbinfo", NbInfo)
100-
app.add_directive("nbwarning", NbWarning)
101-
app.add_directive("nbgallery", NbGallery)
102-
app.add_node(
103-
CodeAreaNode,
104-
html=(do_nothing, depart_codearea_html),
105-
latex=(visit_codearea_latex, depart_codearea_latex),
106-
text=(do_nothing, do_nothing),
107-
)
108-
app.connect("builder-inited", builder_inited)
109-
app.connect("doctree-resolved", doctree_resolved)
110-
app.add_post_transform(GetSizeFromImages)
111-
112-
app.add_config_value("nbsphinx_execute", "auto", rebuild="env")
11373
app.add_config_value("nbsphinx_thumbnails", nbsphinx_thumbnails, rebuild="html")
11474
app.add_directive("nbgallery", NbGallery)
11575
app.add_node(
@@ -185,7 +145,7 @@ def setup(app: Sphinx):
185145
# Add any paths that contain custom static files (such as style sheets) here,
186146
# relative to this directory. They are copied after the builtin static files,
187147
# so a file named "default.css" will overwrite the builtin "default.css".
188-
html_static_path = ["../_static", "../thumbnails"]
148+
html_static_path = ["../_static", "../_images", "../_templates"]
189149
html_css_files = ["custom.css"]
190150
templates_path = ["../_templates"]
191151
html_sidebars = {

examples/gallery.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,4 @@ Variational Inference
112112
:glob:
113113
:reversed:
114114

115-
variational_inference/*
115+
variational_inference/*

requirements-docs.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ sphinx-codeautolink
1010
sphinx-notfound-page
1111
nbsphinx
1212
sphinx-gallery
13+
matplotlib

sphinxext/gallery_generator.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
Sphinx plugin to run generate a gallery for notebooks
3+
4+
Modified from the seaborn project, which modified the mpld3 project.
5+
"""
6+
import base64
7+
import json
8+
import os
9+
import shutil
10+
11+
import matplotlib
12+
13+
matplotlib.use("Agg")
14+
import matplotlib.pyplot as plt
15+
16+
from matplotlib import image
17+
18+
DOC_SRC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19+
DEFAULT_IMG_LOC = os.path.join(os.path.dirname(DOC_SRC), "pymc-examples/_static", "PyMC.png")
20+
21+
22+
def create_thumbnail(infile, width=275, height=275, cx=0.5, cy=0.5, border=4):
23+
"""Overwrites `infile` with a new file of the given size"""
24+
im = image.imread(infile)
25+
rows, cols = im.shape[:2]
26+
size = min(rows, cols)
27+
if size == cols:
28+
xslice = slice(0, size)
29+
ymin = min(max(0, int(cx * rows - size // 2)), rows - size)
30+
yslice = slice(ymin, ymin + size)
31+
else:
32+
yslice = slice(0, size)
33+
xmin = min(max(0, int(cx * cols - size // 2)), cols - size)
34+
xslice = slice(xmin, xmin + size)
35+
thumb = im[yslice, xslice]
36+
thumb[:border, :, :3] = thumb[-border:, :, :3] = 0
37+
thumb[:, :border, :3] = thumb[:, -border:, :3] = 0
38+
39+
dpi = 100
40+
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
41+
42+
ax = fig.add_axes([0, 0, 1, 1], aspect="auto", frameon=False, xticks=[], yticks=[])
43+
ax.imshow(thumb, aspect="auto", resample=True, interpolation="bilinear")
44+
fig.savefig(infile, dpi=dpi)
45+
plt.close(fig)
46+
return fig
47+
48+
49+
class NotebookGenerator:
50+
"""Tools for generating an example page from a file"""
51+
52+
def __init__(self, filename, target_dir):
53+
self.basename = os.path.basename(filename)
54+
stripped_name = os.path.splitext(self.basename)[0]
55+
self.image_dir = os.path.join(target_dir, "_static")
56+
self.png_path = os.path.join(self.image_dir, f"{stripped_name}.png")
57+
with open(filename) as fid:
58+
self.json_source = json.load(fid)
59+
self.default_image_loc = DEFAULT_IMG_LOC
60+
61+
def extract_preview_pic(self):
62+
"""By default, just uses the last image in the notebook."""
63+
pic = None
64+
for cell in self.json_source["cells"]:
65+
for output in cell.get("outputs", []):
66+
if "image/png" in output.get("data", []):
67+
pic = output["data"]["image/png"]
68+
if pic is not None:
69+
return base64.b64decode(pic)
70+
return None
71+
72+
def gen_previews(self):
73+
preview = self.extract_preview_pic()
74+
if preview is not None:
75+
with open(self.png_path, "wb") as buff:
76+
buff.write(preview)
77+
else:
78+
print(f"didn't find for {self.png_path}")
79+
shutil.copy(self.default_image_loc, self.png_path)
80+
create_thumbnail(self.png_path)
81+
82+
83+
def main(app):
84+
from glob import glob
85+
86+
nb_paths = glob("examples/*/*.ipynb")
87+
88+
for nb_path in nb_paths:
89+
nbg = NotebookGenerator(nb_path, "")
90+
nbg.gen_previews()
91+
92+
93+
def setup(app):
94+
app.connect("builder-inited", main)

0 commit comments

Comments
 (0)