Skip to content

Commit f556a29

Browse files
ColCarrolljunpenglao
authored andcommitted
Add notebook gallery extension
1 parent 9476f5a commit f556a29

File tree

6 files changed

+239
-27
lines changed

6 files changed

+239
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.pyc
22
*.sw[op]
33
examples/*.png
4+
nb_examples/
45
build/*
56
dist/*
67
*.egg-info/

docs/source/conf.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# add these directories to sys.path here. If the directory is relative to the
2323
# documentation root, use os.path.abspath to make it absolute, like shown here.
2424
sys.path.insert(0, os.path.abspath(os.path.join('..', '..')))
25+
sys.path.insert(0, os.path.abspath('sphinxext'))
2526

2627
# -- General configuration ------------------------------------------------
2728

@@ -37,8 +38,10 @@
3738
'sphinx.ext.autosummary',
3839
'sphinx.ext.mathjax',
3940
'numpydoc',
41+
'nbsphinx',
4042
'IPython.sphinxext.ipython_console_highlighting',
4143
'sphinx.ext.autosectionlabel',
44+
'gallery_generator',
4245
]
4346

4447
# Don't auto-generate summary for class members.
@@ -142,10 +145,9 @@
142145

143146
html_theme_options = {
144147
"navbar_links": [
145-
("Home", "index"),
146148
("Quickstart", "intro"),
147149
("API", "api"),
148-
("Examples", "examples"),
150+
("Examples", "nb_examples/index"),
149151
("Learn", "learn"),
150152
],
151153
# "fixed_sidebar": "false",
@@ -174,7 +176,7 @@
174176
# Add any paths that contain custom static files (such as style sheets) here,
175177
# relative to this directory. They are copied after the builtin static files,
176178
# so a file named "default.css" will overwrite the builtin "default.css".
177-
html_static_path = ['_static']
179+
html_static_path = ['_static', 'nb_examples/_images']
178180

179181
# Add any extra paths that contain custom files (such as robots.txt or
180182
# .htaccess) here, relative to this directory. These files are copied

docs/source/index.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,19 @@
6868
</div>
6969
</div>
7070

71-
<div class="ui container">
72-
<h2 class="ui dividing header">Licence</h2>
71+
<div class="ui vertical segment">
72+
<h2 class="ui dividing header">License</h2>
7373
<p>PyMC3 is licensed <a href="https://github.com/pymc-devs/pymc3/blob/master/LICENSE">under the Apache License, V2.</a></p>
7474
</div>
7575

76-
<div class="ui container">
76+
<div class="ui vertical segment">
7777
<h2 class="ui dividing header">Citing PyMC3</h2>
7878
<p>Salvatier J., Wiecki T.V., Fonnesbeck C. (2016) Probabilistic programming in Python using PyMC3. PeerJ
7979
Computer Science 2:e55 <a href="https://doi.org/10.7717/peerj-cs.55">DOI: 10.7717/peerj-cs.55</a>.</p>
8080
<p>See <a href="https://scholar.google.de/scholar?oi=bibs&hl=en&authuser=1&cites=6936955228135731011">Google Scholar</a> for a continuously updated list of papers citing PyMC3.</p>
8181
</div>
8282

83-
<div class="ui segment">
83+
<div class="ui bottom attached segment">
8484
<h2 class="ui dividing header">Support and sponsors</h2>
8585
<p>PyMC3 is a non-profit project under NumFOCUS umbrella. If you want to support PyMC3 financially, you <a href="https://www.flipcause.com/widget/widget_home/MTE4OTc=">can donate here</a>.</p>
8686

docs/source/learn.rst

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@
3939

4040
</div>
4141

42+
<h2 class="ui header">...with a video!</h2>
43+
<div class="ui celled list">
44+
45+
<div class="item">
46+
<i class="large youtube middle aligned icon"></i>
47+
<div class="content">
48+
<a href="https://www.youtube.com/playlist?list=PL1Ma_1DBbE82OVW8Fz_6Ts1oOeyOAiovy" class="header"> YouTube Playlist</a>
49+
<div class="description">
50+
There is an actively curated playlist of PyMC3 talks on YouTube.
51+
</div>
52+
</div>
53+
</div>
54+
</div>
55+
4256
<h2 class="ui header">...with a book!</h2>
4357
<div class="ui link four stackable cards">
4458

@@ -144,28 +158,28 @@
144158

145159
<div class="card">
146160
<div class="image">
147-
<img src="https://images-na.ssl-images-amazon.com/images/I/51K33XI2I8L._SX330_BO1,204,203,200_.jpg">
161+
<img src="https://dz13w8afd47il.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/3804OS_4958_Bayesian%20Analysis%20with%20Python.png">
148162
</div>
149163
<div class="content">
150-
<div class="header">Bayesian Cognitive Modeling: A Practical Course</div>
151-
<div class="meta">Michael Lee and Eric-Jan Wagenmakers</div>
164+
<div class="header">Bayesian Analysis with Python</div>
165+
<div class="meta">Osvaldo Martin</div>
152166
<div class="description">
153-
Focused on using Bayesian statistics in cognitive modeling.
167+
A great introductory book written by a maintainer of PyMC3.
154168
</div>
155169
</div>
156170
<table class="ui table">
157171
<tbody>
158172
<tr>
159173
<td>
160-
<a href="https://bayesmodels.com/">
174+
<a href="https://www.packtpub.com/big-data-and-business-intelligence/bayesian-analysis-python">
161175
<i class="linkify icon"></i> Book website
162176
</a>
163177
</td>
164178
</tr>
165179
<tr>
166180
<td>
167-
<a href="https://github.com/pymc-devs/resources/tree/master/BCM">
168-
<i class="linkify icon"></i> PyMC3 implementations
181+
<a href="https://github.com/aloctavodia/BAP">
182+
<i class="linkify icon"></i> Code and errata in PyMC3
169183
</a>
170184
</td>
171185
</tr>
@@ -175,28 +189,28 @@
175189

176190
<div class="card">
177191
<div class="image">
178-
<img src="https://dz13w8afd47il.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/3804OS_4958_Bayesian%20Analysis%20with%20Python.png">
192+
<img src="https://images-na.ssl-images-amazon.com/images/I/51K33XI2I8L._SX330_BO1,204,203,200_.jpg">
179193
</div>
180194
<div class="content">
181-
<div class="header">Bayesian Analysis with Python</div>
182-
<div class="meta">Osvaldo Martin</div>
195+
<div class="header">Bayesian Cognitive Modeling: A Practical Course</div>
196+
<div class="meta">Michael Lee and Eric-Jan Wagenmakers</div>
183197
<div class="description">
184-
A great introductory book written by a maintainer of PyMC3.
198+
Focused on using Bayesian statistics in cognitive modeling.
185199
</div>
186200
</div>
187201
<table class="ui table">
188202
<tbody>
189203
<tr>
190204
<td>
191-
<a href="https://www.packtpub.com/big-data-and-business-intelligence/bayesian-analysis-python">
205+
<a href="https://bayesmodels.com/">
192206
<i class="linkify icon"></i> Book website
193207
</a>
194208
</td>
195209
</tr>
196210
<tr>
197211
<td>
198-
<a href="https://github.com/aloctavodia/BAP">
199-
<i class="linkify icon"></i> Code and errata in PyMC3
212+
<a href="https://github.com/pymc-devs/resources/tree/master/BCM">
213+
<i class="linkify icon"></i> PyMC3 implementations
200214
</a>
201215
</td>
202216
</tr>
@@ -236,7 +250,3 @@
236250
</div>
237251

238252
</div>
239-
240-
<h2 class="ui header">...with a video!</h2>
241-
242-
<p> There is an <a href="https://www.youtube.com/playlist?list=PL1Ma_1DBbE82OVW8Fz_6Ts1oOeyOAiovy">actively curated playlist of PyMC3 talks</a> on YouTube.

docs/source/semantic_sphinx/layout.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
<div class="ui container">
2727
<div class="ui large secondary pointing menu">
28-
<img class="ui bottom aligned tiny image item" src="https://cdn.rawgit.com/pymc-devs/pymc3/master/docs/logos/svg/PyMC3_banner.svg" />
28+
<a class="item" href="/">
29+
<img class="ui bottom aligned tiny image" src="https://cdn.rawgit.com/pymc-devs/pymc3/master/docs/logos/svg/PyMC3_banner.svg" />
30+
</a>
2931
{% if
3032
theme_navbar_links %} {%- for link in theme_navbar_links %} <a href="{{ pathto(*link[1:]) }}" class="item">{{
3133
link[0] }}</a>
@@ -54,7 +56,9 @@ <h2>Probabilistic Programming in Python</h2>
5456
</div>
5557

5658
<div class="ui container" role="main">
57-
{% block body %}{% endblock %}
59+
<div class="ui vertical segment">
60+
{% block body %}{% endblock %}
61+
</div>
5862
</div>
5963
{%- endblock %}
6064

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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 glob
10+
import shutil
11+
12+
import matplotlib
13+
14+
matplotlib.use("Agg")
15+
import matplotlib.pyplot as plt
16+
17+
from matplotlib import image
18+
19+
DOC_SRC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20+
21+
INDEX_TEMPLATE = """
22+
.. _{sphinx_tag}:
23+
24+
.. title:: example_notebooks
25+
26+
.. raw:: html
27+
28+
<h1 class="ui header">Example Notebooks</h1>
29+
<div class="ui link six stackable cards">
30+
{contents}
31+
</div>
32+
"""
33+
34+
35+
def create_thumbnail(infile, width=275, height=275, cx=0.5, cy=0.5, border=4):
36+
"""Overwrites `infile` with a new file of the given size"""
37+
im = image.imread(infile)
38+
rows, cols = im.shape[:2]
39+
size = min(rows, cols)
40+
if size == cols:
41+
xslice = slice(0, size)
42+
ymin = min(max(0, int(cx * rows - size // 2)), rows - size)
43+
yslice = slice(ymin, ymin + size)
44+
else:
45+
yslice = slice(0, size)
46+
xmin = min(max(0, int(cx * cols - size // 2)), cols - size)
47+
xslice = slice(xmin, xmin + size)
48+
thumb = im[yslice, xslice]
49+
thumb[:border, :, :3] = thumb[-border:, :, :3] = 0
50+
thumb[:, :border, :3] = thumb[:, -border:, :3] = 0
51+
52+
dpi = 100
53+
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
54+
55+
ax = fig.add_axes([0, 0, 1, 1], aspect="auto", frameon=False, xticks=[], yticks=[])
56+
ax.imshow(thumb, aspect="auto", resample=True, interpolation="bilinear")
57+
fig.savefig(infile, dpi=dpi)
58+
plt.close(fig)
59+
return fig
60+
61+
62+
class NotebookGenerator(object):
63+
"""Tools for generating an example page from a file"""
64+
65+
def __init__(self, filename, target_dir):
66+
self.basename = os.path.basename(filename)
67+
self.stripped_name = os.path.splitext(self.basename)[0]
68+
self.output_html = os.path.join(
69+
"..", "notebooks", "{}.html".format(self.stripped_name)
70+
)
71+
self.image_dir = os.path.join(target_dir, "_images")
72+
self.png_path = os.path.join(
73+
self.image_dir, "{}.png".format(self.stripped_name)
74+
)
75+
with open(filename, "r") as fid:
76+
self.json_source = json.load(fid)
77+
self.pagetitle = self.extract_title()
78+
self.default_image_loc = os.path.join(
79+
os.path.dirname(DOC_SRC), "logos", "PyMC3.png"
80+
)
81+
82+
# Only actually run it if the output RST file doesn't
83+
# exist or it was modified less recently than the example
84+
if not os.path.exists(self.output_html) or (
85+
os.path.getmtime(self.output_html) < os.path.getmtime(filename)
86+
):
87+
88+
self.gen_previews()
89+
else:
90+
print("skipping {0}".format(filename))
91+
92+
def extract_preview_pic(self):
93+
"""By default, just uses the last image in the notebook."""
94+
pic = None
95+
for cell in self.json_source["cells"]:
96+
for output in cell.get("outputs", []):
97+
if "image/png" in output.get("data", []):
98+
pic = output["data"]["image/png"]
99+
if pic is not None:
100+
return base64.b64decode(pic)
101+
return None
102+
103+
def extract_title(self):
104+
for cell in self.json_source['cells']:
105+
if cell["cell_type"] == "markdown":
106+
rows = [row.strip() for row in cell["source"] if row.strip()]
107+
for row in rows:
108+
if row.startswith("# "):
109+
return row[2:]
110+
return self.basename.replace('_', ' ')
111+
112+
def gen_previews(self):
113+
preview = self.extract_preview_pic()
114+
if preview is not None:
115+
with open(self.png_path, "wb") as buff:
116+
buff.write(preview)
117+
else:
118+
shutil.copy(self.default_image_loc, self.png_path)
119+
create_thumbnail(self.png_path)
120+
121+
def contents_entry(self):
122+
return """
123+
.. raw:: html
124+
125+
<a class='card' href='./{0}'>
126+
<div class="image">
127+
<img src=../_static/{1}>
128+
</div>
129+
<div class="content">
130+
<div class="header">{2}</div>
131+
</div>
132+
</a>
133+
134+
""".format(
135+
self.output_html, os.path.basename(self.png_path), self.pagetitle
136+
)
137+
138+
139+
def main(app):
140+
working_dir = os.getcwd()
141+
os.chdir(app.builder.srcdir)
142+
static_dir = os.path.join(app.builder.srcdir, "_static")
143+
target_dir = os.path.join(app.builder.srcdir, "nb_examples")
144+
image_dir = os.path.join(app.builder.srcdir, "nb_examples/_images")
145+
source_dir = os.path.abspath(
146+
os.path.join(os.path.dirname(os.path.dirname(app.builder.srcdir)), "notebooks")
147+
)
148+
149+
if not os.path.exists(static_dir):
150+
os.makedirs(static_dir)
151+
152+
if not os.path.exists(target_dir):
153+
os.makedirs(target_dir)
154+
155+
if not os.path.exists(image_dir):
156+
os.makedirs(image_dir)
157+
158+
if not os.path.exists(source_dir):
159+
os.makedirs(source_dir)
160+
161+
banner_data = []
162+
163+
contents = "\n\n"
164+
165+
# Write individual example files
166+
files = sorted(glob.glob(os.path.join(source_dir, "*.ipynb")))
167+
for filename in files:
168+
169+
ex = NotebookGenerator(filename, target_dir)
170+
171+
banner_data.append(
172+
{
173+
"title": ex.pagetitle,
174+
"url": os.path.join("examples", ex.output_html),
175+
"thumb": ex.png_path,
176+
}
177+
)
178+
179+
contents += ex.contents_entry()
180+
181+
if len(banner_data) < 10:
182+
banner_data = (4 * banner_data)[:10]
183+
184+
# write index file
185+
index_file = os.path.join(target_dir, "index.rst")
186+
with open(index_file, "w") as index:
187+
index.write(
188+
INDEX_TEMPLATE.format(sphinx_tag="notebook_gallery", contents=contents)
189+
)
190+
191+
os.chdir(working_dir)
192+
193+
194+
def setup(app):
195+
app.connect("builder-inited", main)

0 commit comments

Comments
 (0)