mirror of
https://github.com/wassname/scikit-image.git
synced 2026-09-09 11:33:41 +08:00
Merge pull request #190 from tonysyu/plot2rst
ENH: Replace `gen_rst` Sphinx extension with `plot2rst`.
This commit is contained in:
@@ -3,57 +3,40 @@
|
||||
Comparing edge-based segmentation and region-based segmentation
|
||||
===============================================================
|
||||
|
||||
In this example, we will see how to segment objects from a background.
|
||||
We use the ``coins`` image from ``skimage.data``. This image shows
|
||||
several coins outlined against a darker background. The segmentation of
|
||||
the coins cannot be done directly from the histogram of grey values,
|
||||
because the background shares enough grey levels with the coins that a
|
||||
thresholding segmentation is not sufficient. Simply thresholding the image
|
||||
leads either to missing significant parts of the coins, or to merging parts
|
||||
of the background with the coins.
|
||||
|
||||
We first try an edge-based segmentation. We use the Canny detector to
|
||||
delineate the contours of the coins. These contours are filled using
|
||||
mathematical morphology (``scipy.ndimage.binary_fill_holes``). Small spurious
|
||||
objects are easily removed by applying a threshold on the size of
|
||||
unconnected objects. However, this method is not very robust, since contours
|
||||
that are not perfectly closed are not filled correctly. This happens for one
|
||||
of the coins.
|
||||
|
||||
We therefore try a second method, that is region-based. Here we use the
|
||||
watershed transform. An elevation map is provided by the Sobel gradient
|
||||
of the image. Markers of the background and the coins are determined from
|
||||
the extreme parts of the histogram of grey values.
|
||||
|
||||
This second method works even better, and the coins can be segmented and
|
||||
labeled individually.
|
||||
|
||||
In this example, we will see how to segment objects from a background. We use
|
||||
the ``coins`` image from ``skimage.data``, which shows several coins outlined
|
||||
against a darker background.
|
||||
"""
|
||||
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage
|
||||
import matplotlib.pyplot as plt
|
||||
import skimage
|
||||
from skimage.filter import canny, sobel
|
||||
from skimage.morphology import watershed
|
||||
|
||||
#------------------ Loading data --------------------------------
|
||||
from skimage import data
|
||||
coins = data.coins()
|
||||
|
||||
#------------ Histogram of grey values ---------------------------
|
||||
histo = np.histogram(coins, bins=np.arange(0, 256))
|
||||
coins = data.coins()
|
||||
hist = np.histogram(coins, bins=np.arange(0, 256))
|
||||
|
||||
plt.figure(figsize=(8, 3))
|
||||
plt.subplot(121)
|
||||
plt.imshow(coins, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.subplot(122)
|
||||
plt.plot(histo[1][:-1], histo[0], lw=2)
|
||||
plt.plot(hist[1][:-1], hist[0], lw=2)
|
||||
plt.title('histogram of grey values')
|
||||
|
||||
#------------------ Tentative thresholding --------------------------------
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Thresholding
|
||||
============
|
||||
|
||||
A simple way to segment the coins is to choose a threshold based on the
|
||||
histogram of grey values. Unfortunately, thresholding this image gives a binary
|
||||
image that either misses significant parts of the coins or merges parts of the
|
||||
background with the coins:
|
||||
|
||||
"""
|
||||
|
||||
plt.figure(figsize=(6, 3))
|
||||
plt.subplot(121)
|
||||
plt.imshow(coins > 100, cmap=plt.cm.gray, interpolation='nearest')
|
||||
@@ -63,73 +46,126 @@ plt.subplot(122)
|
||||
plt.imshow(coins > 150, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.title('coins > 150')
|
||||
plt.axis('off')
|
||||
margins = dict(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0, right=1)
|
||||
plt.subplots_adjust(**margins)
|
||||
|
||||
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
|
||||
right=1)
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
|
||||
#------------------ Edge-based segmentation --------------------------------
|
||||
Edge-based segmentation
|
||||
=======================
|
||||
|
||||
Next, we try to delineate the contours of the coins using edge-based
|
||||
segmentation. To do this, we first get the edges of features using the Canny
|
||||
edge-detector.
|
||||
"""
|
||||
|
||||
from skimage.filter import canny
|
||||
edges = canny(coins/255.)
|
||||
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(edges, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('Canny detector')
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
These contours are then filled using mathematical morphology.
|
||||
"""
|
||||
|
||||
from scipy import ndimage
|
||||
|
||||
fill_coins = ndimage.binary_fill_holes(edges)
|
||||
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(fill_coins, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('Filling the holes')
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Small spurious objects are easily removed by setting a minimum size for valid
|
||||
objects.
|
||||
"""
|
||||
|
||||
label_objects, nb_labels = ndimage.label(fill_coins)
|
||||
sizes = np.bincount(label_objects.ravel())
|
||||
mask_sizes = sizes > 20
|
||||
mask_sizes[0] = 0
|
||||
coins_cleaned = mask_sizes[label_objects]
|
||||
|
||||
plt.figure(figsize=(7, 3.))
|
||||
plt.subplot(131)
|
||||
plt.imshow(edges, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('Canny detector')
|
||||
plt.subplot(132)
|
||||
plt.imshow(fill_coins, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('Filling the holes')
|
||||
plt.subplot(133)
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('Removing small objects')
|
||||
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
|
||||
right=1)
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
However, this method is not very robust, since contours that are not perfectly
|
||||
closed are not filled correctly, as is the case for one unfilled coin above.
|
||||
|
||||
|
||||
#------------------ Region-based segmentation --------------------------------
|
||||
Region-based segmentation
|
||||
=========================
|
||||
|
||||
We therefore try a region-based method using the
|
||||
watershed transform. First, we find an elevation map using the Sobel gradient of the
|
||||
image.
|
||||
|
||||
"""
|
||||
|
||||
from skimage.filter import sobel
|
||||
|
||||
elevation_map = sobel(coins)
|
||||
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(elevation_map, cmap=plt.cm.jet, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('elevation_map')
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Next we find markers of the background and the coins based on the extreme parts
|
||||
of the histogram of grey values.
|
||||
"""
|
||||
|
||||
markers = np.zeros_like(coins)
|
||||
markers[coins < 30] = 1
|
||||
markers[coins > 150] = 2
|
||||
|
||||
elevation_map = sobel(coins)
|
||||
|
||||
|
||||
segmentation = watershed(elevation_map, markers)
|
||||
|
||||
|
||||
plt.figure(figsize=(7, 3))
|
||||
plt.subplot(131)
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(markers, cmap=plt.cm.spectral, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('markers')
|
||||
plt.subplot(132)
|
||||
plt.imshow(elevation_map, cmap=plt.cm.jet, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('elevation_map')
|
||||
plt.subplot(133)
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
Finally, we use the watershed transform to fill regions of the elevation map starting from the markers determined above:
|
||||
|
||||
"""
|
||||
from skimage.morphology import watershed
|
||||
segmentation = watershed(elevation_map, markers)
|
||||
|
||||
plt.figure(figsize=(4, 3))
|
||||
plt.imshow(segmentation, cmap=plt.cm.gray, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
plt.title('segmentation')
|
||||
|
||||
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
|
||||
right=1)
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
# ------------------- Removing a few small holes ---------------------
|
||||
This last method works even better, and the coins can be segmented and
|
||||
labeled individually.
|
||||
|
||||
"""
|
||||
|
||||
segmentation = ndimage.binary_fill_holes(segmentation - 1)
|
||||
|
||||
#------------------ Labeling the coins --------------------------------
|
||||
labeled_coins, _ = ndimage.label(segmentation)
|
||||
|
||||
plt.figure(figsize=(6, 3))
|
||||
@@ -141,8 +177,11 @@ plt.subplot(122)
|
||||
plt.imshow(labeled_coins, cmap=plt.cm.spectral, interpolation='nearest')
|
||||
plt.axis('off')
|
||||
|
||||
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
|
||||
right=1)
|
||||
plt.subplots_adjust(**margins)
|
||||
|
||||
"""
|
||||
.. image:: PLOT2RST.current_figure
|
||||
|
||||
"""
|
||||
|
||||
plt.show()
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
"""
|
||||
Example generation for the scikit image.
|
||||
|
||||
Generate the rst files for the examples by iterating over the python
|
||||
example files.
|
||||
|
||||
Files that generate images should start with 'plot'.
|
||||
|
||||
This code was taken from the scikit-learn.
|
||||
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
import glob
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use('Agg')
|
||||
|
||||
import token, tokenize
|
||||
|
||||
rst_template = """
|
||||
|
||||
.. _example_%(short_fname)s:
|
||||
|
||||
%(docstring)s
|
||||
|
||||
**Python source code:** :download:`%(fname)s <%(fname)s>`
|
||||
(generated using ``skimage`` |version|)
|
||||
|
||||
.. literalinclude:: %(fname)s
|
||||
:lines: %(end_row)s-
|
||||
"""
|
||||
|
||||
plot_rst_template = """
|
||||
|
||||
.. _example_%(short_fname)s:
|
||||
|
||||
%(docstring)s
|
||||
|
||||
%(image_list)s
|
||||
|
||||
**Python source code:** :download:`%(fname)s <%(fname)s>`
|
||||
(generated using ``skimage`` |version|)
|
||||
|
||||
.. literalinclude:: %(fname)s
|
||||
:lines: %(end_row)s-
|
||||
"""
|
||||
|
||||
|
||||
toctree_template = """
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
%s
|
||||
|
||||
"""
|
||||
|
||||
# The following strings are used when we have several pictures: we use
|
||||
# an html div tag that our CSS uses to turn the lists into horizontal
|
||||
# lists.
|
||||
HLIST_HEADER = """
|
||||
.. rst-class:: horizontal
|
||||
|
||||
"""
|
||||
|
||||
HLIST_IMAGE_TEMPLATE = """
|
||||
*
|
||||
|
||||
.. image:: images/%s
|
||||
:scale: 50
|
||||
"""
|
||||
|
||||
SINGLE_IMAGE = """
|
||||
.. image:: images/%s
|
||||
:align: center
|
||||
"""
|
||||
|
||||
def extract_docstring(filename):
|
||||
""" Extract a module-level docstring, if any
|
||||
"""
|
||||
lines = file(filename).readlines()
|
||||
start_row = 0
|
||||
if lines[0].startswith('#!'):
|
||||
lines.pop(0)
|
||||
start_row = 1
|
||||
|
||||
docstring = ''
|
||||
first_par = ''
|
||||
tokens = tokenize.generate_tokens(lines.__iter__().next)
|
||||
for tok_type, tok_content, _, (erow, _), _ in tokens:
|
||||
tok_type = token.tok_name[tok_type]
|
||||
if tok_type in ('NEWLINE', 'COMMENT', 'NL', 'INDENT', 'DEDENT'):
|
||||
continue
|
||||
elif tok_type == 'STRING':
|
||||
docstring = eval(tok_content)
|
||||
# If the docstring is formatted with several paragraphs, extract
|
||||
# the first one:
|
||||
paragraphs = '\n'.join(line.rstrip()
|
||||
for line in docstring.split('\n')).split('\n\n')
|
||||
if len(paragraphs) > 0:
|
||||
first_par = paragraphs[0]
|
||||
break
|
||||
return docstring, first_par, erow+1+start_row
|
||||
|
||||
|
||||
def generate_example_rst(app):
|
||||
""" Generate the list of examples, as well as the contents of
|
||||
examples.
|
||||
"""
|
||||
root_dir = os.path.join(app.builder.srcdir, 'auto_examples')
|
||||
example_dir = os.path.abspath(app.builder.srcdir + '/../' + 'examples')
|
||||
try:
|
||||
plot_gallery = eval(app.builder.config.plot_gallery)
|
||||
except TypeError:
|
||||
plot_gallery = bool(app.builder.config.plot_gallery)
|
||||
if not os.path.exists(example_dir):
|
||||
os.makedirs(example_dir)
|
||||
if not os.path.exists(root_dir):
|
||||
os.makedirs(root_dir)
|
||||
|
||||
# we create an index.rst with all examples
|
||||
fhindex = file(os.path.join(root_dir, 'index.txt'), 'w')
|
||||
fhindex.write("""\
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
.. _examples-index:
|
||||
""")
|
||||
# Here we don't use an os.walk, but we recurse only twice: flat is
|
||||
# better than nested.
|
||||
generate_dir_rst('.', fhindex, example_dir, root_dir, plot_gallery)
|
||||
for dir in sorted(os.listdir(example_dir)):
|
||||
if os.path.isdir(os.path.join(example_dir, dir)):
|
||||
generate_dir_rst(dir, fhindex, example_dir, root_dir, plot_gallery)
|
||||
fhindex.flush()
|
||||
|
||||
|
||||
def generate_dir_rst(dir, fhindex, example_dir, root_dir, plot_gallery):
|
||||
""" Generate the rst file for an example directory.
|
||||
"""
|
||||
if not dir == '.':
|
||||
target_dir = os.path.join(root_dir, dir)
|
||||
src_dir = os.path.join(example_dir, dir)
|
||||
else:
|
||||
target_dir = root_dir
|
||||
src_dir = example_dir
|
||||
if not os.path.exists(os.path.join(src_dir, 'README.txt')):
|
||||
print 80*'_'
|
||||
print ('Example directory %s does not have a README.txt file'
|
||||
% src_dir)
|
||||
print 'Skipping this directory'
|
||||
print 80*'_'
|
||||
return
|
||||
|
||||
example_description = file(os.path.join(src_dir, 'README.txt')).read()
|
||||
fhindex.write("""\n\n%s\n\n""" % example_description)
|
||||
|
||||
if not os.path.exists(target_dir):
|
||||
os.makedirs(target_dir)
|
||||
|
||||
def sort_key(a):
|
||||
# put last elements without a plot
|
||||
if not a.startswith('plot') and a.endswith('.py'):
|
||||
return 'zz' + a
|
||||
return a
|
||||
|
||||
examples = [fname for fname in sorted(os.listdir(src_dir), key=sort_key)
|
||||
if fname.endswith('py')]
|
||||
ex_names = [ex[:-3] for ex in examples] # strip '.py' extension
|
||||
fhindex.write(toctree_template % '\n '.join(ex_names))
|
||||
|
||||
for fname in examples:
|
||||
generate_file_rst(fname, target_dir, src_dir, plot_gallery)
|
||||
thumb = os.path.join(dir, 'images', 'thumb', fname[:-3] + '.png')
|
||||
link_name = os.path.join(dir, fname).replace(os.path.sep, '_')
|
||||
fhindex.write('.. figure:: %s\n' % thumb)
|
||||
if link_name.startswith('._'):
|
||||
link_name = link_name[2:]
|
||||
fhindex.write(' :figclass: gallery\n')
|
||||
if dir != '.':
|
||||
fhindex.write(' :target: ./%s/%s.html\n\n' % (dir, fname[:-3]))
|
||||
else:
|
||||
fhindex.write(' :target: ./%s.html\n\n' % link_name[:-3])
|
||||
fhindex.write(' :ref:`example_%s`\n\n' % link_name)
|
||||
fhindex.write("""
|
||||
.. raw:: html
|
||||
|
||||
<div style="clear: both"></div>
|
||||
""") # clear at the end of the section
|
||||
|
||||
|
||||
def generate_file_rst(fname, target_dir, src_dir, plot_gallery):
|
||||
""" Generate the rst file for a given example.
|
||||
"""
|
||||
base_image_name = os.path.splitext(fname)[0]
|
||||
image_fname = '%s_%%s.png' % base_image_name
|
||||
|
||||
this_template = rst_template
|
||||
last_dir = os.path.split(src_dir)[-1]
|
||||
# to avoid leading . in file names, and wrong names in links
|
||||
if last_dir == '.' or last_dir == 'examples':
|
||||
last_dir = ''
|
||||
else:
|
||||
last_dir += '_'
|
||||
short_fname = last_dir + fname
|
||||
src_file = os.path.join(src_dir, fname)
|
||||
example_file = os.path.join(target_dir, fname)
|
||||
shutil.copyfile(src_file, example_file)
|
||||
|
||||
# The following is a list containing all the figure names
|
||||
figure_list = []
|
||||
|
||||
image_dir = os.path.join(target_dir, 'images')
|
||||
thumb_dir = os.path.join(image_dir, 'thumb')
|
||||
if not os.path.exists(image_dir):
|
||||
os.makedirs(image_dir)
|
||||
if not os.path.exists(thumb_dir):
|
||||
os.makedirs(thumb_dir)
|
||||
image_path = os.path.join(image_dir, image_fname)
|
||||
thumb_file = os.path.join(thumb_dir, fname[:-3] + '.png')
|
||||
if plot_gallery and fname.startswith('plot'):
|
||||
# generate the plot as png image if file name
|
||||
# starts with plot and if it is more recent than an
|
||||
# existing image.
|
||||
first_image_file = image_path % 1
|
||||
|
||||
if (not os.path.exists(first_image_file) or
|
||||
os.stat(first_image_file).st_mtime <=
|
||||
os.stat(src_file).st_mtime):
|
||||
# We need to execute the code
|
||||
print 'plotting %s' % fname
|
||||
import matplotlib.pyplot as plt
|
||||
plt.close('all')
|
||||
cwd = os.getcwd()
|
||||
try:
|
||||
# First CD in the original example dir, so that any file created
|
||||
# by the example get created in this directory
|
||||
os.chdir(os.path.dirname(src_file))
|
||||
execfile(os.path.basename(src_file), {'pl' : plt})
|
||||
os.chdir(cwd)
|
||||
|
||||
# In order to save every figure we have two solutions :
|
||||
# * iterate from 1 to infinity and call plt.fignum_exists(n)
|
||||
# (this requires the figures to be numbered
|
||||
# incrementally: 1, 2, 3 and not 1, 2, 5)
|
||||
# * iterate over [fig_mngr.num for fig_mngr in
|
||||
# matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
|
||||
for fig_num in (fig_mngr.num for fig_mngr in
|
||||
matplotlib._pylab_helpers.Gcf.get_all_fig_managers()):
|
||||
# Set the fig_num figure as the current figure as we can't
|
||||
# save a figure that's not the current figure.
|
||||
plt.figure(fig_num)
|
||||
plt.savefig(image_path % fig_num)
|
||||
figure_list.append(image_fname % fig_num)
|
||||
except:
|
||||
print 80*'_'
|
||||
print '%s is not compiling:' % fname
|
||||
traceback.print_exc()
|
||||
print 80*'_'
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
else:
|
||||
figure_list = [f[len(image_dir):]
|
||||
for f in glob.glob(image_path % '[1-9]')]
|
||||
#for f in glob.glob(image_path % '*')]
|
||||
|
||||
# generate thumb file
|
||||
this_template = plot_rst_template
|
||||
from matplotlib import image
|
||||
if os.path.exists(first_image_file):
|
||||
image.thumbnail(first_image_file, thumb_file, 0.25)
|
||||
|
||||
if not os.path.exists(thumb_file):
|
||||
# create something not to replace the thumbnail
|
||||
shutil.copy('source/auto_examples/images/blank_image.png', thumb_file)
|
||||
|
||||
docstring, short_desc, end_row = extract_docstring(example_file)
|
||||
|
||||
# Depending on whether we have one or more figures, we're using a
|
||||
# horizontal list or a single rst call to 'image'.
|
||||
if len(figure_list) == 1:
|
||||
figure_name = figure_list[0]
|
||||
image_list = SINGLE_IMAGE % figure_name.lstrip('/')
|
||||
else:
|
||||
image_list = HLIST_HEADER
|
||||
for figure_name in figure_list:
|
||||
image_list += HLIST_IMAGE_TEMPLATE % figure_name.lstrip('/')
|
||||
|
||||
f = open(os.path.join(target_dir, fname[:-2] + 'txt'),'w')
|
||||
f.write(this_template % locals())
|
||||
f.flush()
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect('builder-inited', generate_example_rst)
|
||||
app.add_config_value('plot_gallery', True, 'html')
|
||||
@@ -0,0 +1,502 @@
|
||||
"""
|
||||
Example generation from python files.
|
||||
|
||||
Generate the rst files for the examples by iterating over the python
|
||||
example files. Files that generate images should start with 'plot'.
|
||||
|
||||
To generate your own examples, add this extension to the list of
|
||||
``extensions``in your Sphinx configuration file. In addition, make sure the
|
||||
example directory(ies) in `plot2rst_paths` (see below) points to a directory
|
||||
with examples named `plot_*.py` and include an `index.rst` file.
|
||||
|
||||
This code was adapted from scikits-image, which took it from scikits-learn.
|
||||
|
||||
Options
|
||||
-------
|
||||
The ``plot2rst`` extension accepts the following options:
|
||||
|
||||
plot2rst_paths : length-2 tuple, or list of tuples
|
||||
Tuple or list of tuples of paths to (python plot, generated rst) files,
|
||||
i.e. (source, destination). Note that both paths are relative to Sphinx
|
||||
'source' directory. Defaults to ('../examples', 'auto_examples')
|
||||
|
||||
plot2rst_rcparams : dict
|
||||
Matplotlib configuration parameters. See
|
||||
http://matplotlib.sourceforge.net/users/customizing.html for details.
|
||||
|
||||
plot2rst_default_thumb : str
|
||||
Path (relative to doc root) of default thumbnail image.
|
||||
|
||||
plot2rst_thumb_scale : float
|
||||
Scale factor for thumbnail (e.g., 0.2 to scale plot to 1/5th the
|
||||
original size).
|
||||
|
||||
plot2rst_plot_tag : str
|
||||
When this tag is found in the example file, the current plot is saved and
|
||||
tag is replaced with plot path. Defaults to 'PLOT2RST.current_figure'.
|
||||
|
||||
|
||||
Suggested CSS definitions
|
||||
-------------------------
|
||||
|
||||
div.body h2 {
|
||||
border-bottom: 1px solid #BBB;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
/*---- example gallery ----*/
|
||||
|
||||
.gallery.figure {
|
||||
float: left;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.gallery.figure img{
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.gallery.figure .caption {
|
||||
width: 200px;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import token
|
||||
import tokenize
|
||||
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import image
|
||||
|
||||
|
||||
LITERALINCLUDE = """
|
||||
.. literalinclude:: {src_name}
|
||||
:lines: {code_start}-
|
||||
|
||||
"""
|
||||
|
||||
CODE_LINK = """
|
||||
|
||||
**Python source code:** :download:`download <{0}>`
|
||||
(generated using ``skimage`` |version|)
|
||||
|
||||
"""
|
||||
|
||||
TOCTREE_TEMPLATE = """
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
%s
|
||||
|
||||
"""
|
||||
|
||||
IMAGE_TEMPLATE = """
|
||||
.. image:: images/%s
|
||||
:align: center
|
||||
|
||||
"""
|
||||
|
||||
GALLERY_IMAGE_TEMPLATE = """
|
||||
.. figure:: %(thumb)s
|
||||
:figclass: gallery
|
||||
:target: ./%(source)s.html
|
||||
|
||||
:ref:`example_%(link_name)s`
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class Path(str):
|
||||
"""Path object for manipulating directory and file paths."""
|
||||
|
||||
def __init__(self, path):
|
||||
super(Path, self).__init__(path)
|
||||
|
||||
@property
|
||||
def isdir(self):
|
||||
return os.path.isdir(self)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
"""Return True if path exists"""
|
||||
return os.path.exists(self)
|
||||
|
||||
def pjoin(self, *args):
|
||||
"""Join paths. `p` prefix prevents confusion with string method."""
|
||||
return self.__class__(os.path.join(self, *args))
|
||||
|
||||
def psplit(self):
|
||||
"""Split paths. `p` prefix prevents confusion with string method."""
|
||||
return [self.__class__(p) for p in os.path.split(self)]
|
||||
|
||||
def makedirs(self):
|
||||
if not self.exists:
|
||||
os.makedirs(self)
|
||||
|
||||
def listdir(self):
|
||||
return os.listdir(self)
|
||||
|
||||
def format(self, *args, **kwargs):
|
||||
return self.__class__(super(Path, self).format(*args, **kwargs))
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(super(Path, self).__add__(other))
|
||||
|
||||
def __iadd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect('builder-inited', generate_example_galleries)
|
||||
|
||||
app.add_config_value('plot2rst_paths',
|
||||
('../examples', 'auto_examples'), True)
|
||||
app.add_config_value('plot2rst_rcparams', {}, True)
|
||||
app.add_config_value('plot2rst_default_thumb', None, True)
|
||||
app.add_config_value('plot2rst_thumb_scale', 0.25, True)
|
||||
app.add_config_value('plot2rst_plot_tag', 'PLOT2RST.current_figure', True)
|
||||
app.add_config_value('plot2rst_index_name', 'index', True)
|
||||
|
||||
|
||||
def generate_example_galleries(app):
|
||||
cfg = app.builder.config
|
||||
|
||||
doc_src = Path(os.path.abspath(app.builder.srcdir)) # path/to/doc/source
|
||||
|
||||
if isinstance(cfg.plot2rst_paths, tuple):
|
||||
cfg.plot2rst_paths = [cfg.plot2rst_paths]
|
||||
for src_dest in cfg.plot2rst_paths:
|
||||
plot_path, rst_path = [Path(p) for p in src_dest]
|
||||
example_dir = doc_src.pjoin(plot_path)
|
||||
rst_dir = doc_src.pjoin(rst_path)
|
||||
generate_examples_and_gallery(example_dir, rst_dir, cfg)
|
||||
|
||||
|
||||
def generate_examples_and_gallery(example_dir, rst_dir, cfg):
|
||||
"""Generate rst from examples and create gallery to showcase examples."""
|
||||
if not example_dir.exists:
|
||||
print "No example directory found at", example_dir
|
||||
return
|
||||
rst_dir.makedirs()
|
||||
|
||||
# we create an index.rst with all examples
|
||||
gallery_index = file(rst_dir.pjoin('index'+cfg.source_suffix), 'w')
|
||||
|
||||
# Here we don't use an os.walk, but we recurse only twice: flat is
|
||||
# better than nested.
|
||||
write_gallery(gallery_index, example_dir, rst_dir, cfg)
|
||||
for d in sorted(example_dir.listdir()):
|
||||
example_sub = example_dir.pjoin(d)
|
||||
if example_sub.isdir:
|
||||
rst_sub = rst_dir.pjoin(d)
|
||||
rst_sub.makedirs()
|
||||
write_gallery(gallery_index, example_sub, rst_sub, cfg, depth=1)
|
||||
gallery_index.flush()
|
||||
|
||||
|
||||
def write_gallery(gallery_index, src_dir, rst_dir, cfg, depth=0):
|
||||
"""Generate the rst files for an example directory, i.e. gallery.
|
||||
|
||||
Write rst files from python examples and add example links to gallery.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gallery_index : file
|
||||
Index file for plot gallery.
|
||||
src_dir : 'str'
|
||||
Source directory for python examples.
|
||||
rst_dir : 'str'
|
||||
Destination directory for rst files generated from python examples.
|
||||
cfg : config object
|
||||
Sphinx config object created by Sphinx.
|
||||
"""
|
||||
index_name = cfg.plot2rst_index_name + cfg.source_suffix
|
||||
gallery_template = src_dir.pjoin(index_name)
|
||||
if not os.path.exists(gallery_template):
|
||||
print src_dir
|
||||
print 80*'_'
|
||||
print ('Example directory %s does not have a %s file'
|
||||
% (src_dir, index_name))
|
||||
print 'Skipping this directory'
|
||||
print 80*'_'
|
||||
return
|
||||
|
||||
gallery_description = file(gallery_template).read()
|
||||
gallery_index.write('\n\n%s\n\n' % gallery_description)
|
||||
|
||||
rst_dir.makedirs()
|
||||
examples = [fname for fname in sorted(src_dir.listdir(), key=_plots_first)
|
||||
if fname.endswith('py')]
|
||||
ex_names = [ex[:-3] for ex in examples] # strip '.py' extension
|
||||
if depth == 0:
|
||||
sub_dir = Path('')
|
||||
else:
|
||||
sub_dir_list = src_dir.psplit()[-depth:]
|
||||
sub_dir = Path('/'.join(sub_dir_list) + '/')
|
||||
gallery_index.write(TOCTREE_TEMPLATE % (sub_dir + '\n '.join(ex_names)))
|
||||
|
||||
for src_name in examples:
|
||||
write_example(src_name, src_dir, rst_dir, cfg)
|
||||
|
||||
link_name = sub_dir.pjoin(src_name)
|
||||
link_name = link_name.replace(os.path.sep, '_')
|
||||
if link_name.startswith('._'):
|
||||
link_name = link_name[2:]
|
||||
|
||||
info = {}
|
||||
info['thumb'] = sub_dir.pjoin('images/thumb', src_name[:-3] + '.png')
|
||||
info['source'] = sub_dir + src_name[:-3]
|
||||
info['link_name'] = link_name
|
||||
gallery_index.write(GALLERY_IMAGE_TEMPLATE % info)
|
||||
|
||||
|
||||
def _plots_first(fname):
|
||||
"""Decorate filename so that examples with plots are displayed first."""
|
||||
if not (fname.startswith('plot') and fname.endswith('.py')):
|
||||
return 'zz' + fname
|
||||
return fname
|
||||
|
||||
|
||||
def write_example(src_name, src_dir, rst_dir, cfg):
|
||||
"""Write rst file from a given python example.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src_name : str
|
||||
Name of example file.
|
||||
src_dir : 'str'
|
||||
Source directory for python examples.
|
||||
rst_dir : 'str'
|
||||
Destination directory for rst files generated from python examples.
|
||||
cfg : config object
|
||||
Sphinx config object created by Sphinx.
|
||||
"""
|
||||
last_dir = src_dir.psplit()[-1]
|
||||
# to avoid leading . in file names, and wrong names in links
|
||||
if last_dir == '.' or last_dir == 'examples':
|
||||
last_dir = Path('')
|
||||
else:
|
||||
last_dir += '_'
|
||||
|
||||
src_path = src_dir.pjoin(src_name)
|
||||
example_file = rst_dir.pjoin(src_name)
|
||||
shutil.copyfile(src_path, example_file)
|
||||
|
||||
image_dir = rst_dir.pjoin('images')
|
||||
thumb_dir = image_dir.pjoin('thumb')
|
||||
image_dir.makedirs()
|
||||
thumb_dir.makedirs()
|
||||
|
||||
base_image_name = os.path.splitext(src_name)[0]
|
||||
image_path = image_dir.pjoin(base_image_name + '_{0}.png')
|
||||
|
||||
basename, py_ext = os.path.splitext(src_name)
|
||||
rst_path = rst_dir.pjoin(basename + cfg.source_suffix)
|
||||
|
||||
if _plots_are_current(src_path, image_path) and rst_path.exists:
|
||||
return
|
||||
|
||||
blocks = split_code_and_text_blocks(example_file)
|
||||
if blocks[0][2].startswith('#!'):
|
||||
blocks.pop(0) # don't add shebang line to rst file.
|
||||
|
||||
rst_link = '.. _example_%s:\n\n' % (last_dir + src_name)
|
||||
figure_list, rst = process_blocks(blocks, src_path, image_path, cfg)
|
||||
|
||||
has_inline_plots = any(cfg.plot2rst_plot_tag in b[2] for b in blocks)
|
||||
if has_inline_plots:
|
||||
example_rst = ''.join([rst_link, rst])
|
||||
else:
|
||||
# print first block of text, display all plots, then display code.
|
||||
first_text_block = [b for b in blocks if b[0] == 'text'][0]
|
||||
label, (start, end), content = first_text_block
|
||||
figure_list = save_all_figures(image_path)
|
||||
rst_blocks = [IMAGE_TEMPLATE % f.lstrip('/') for f in figure_list]
|
||||
|
||||
example_rst = rst_link
|
||||
example_rst += eval(content)
|
||||
example_rst += ''.join(rst_blocks)
|
||||
code_info = dict(src_name=src_name, code_start=end)
|
||||
example_rst += LITERALINCLUDE.format(**code_info)
|
||||
|
||||
example_rst += CODE_LINK.format(src_name)
|
||||
|
||||
f = open(rst_path,'w')
|
||||
f.write(example_rst)
|
||||
f.flush()
|
||||
|
||||
thumb_path = thumb_dir.pjoin(src_name[:-3] + '.png')
|
||||
first_image_file = image_dir.pjoin(figure_list[0].lstrip('/'))
|
||||
if first_image_file.exists:
|
||||
image.thumbnail(first_image_file, thumb_path, cfg.plot2rst_thumb_scale)
|
||||
|
||||
if not thumb_path.exists:
|
||||
if cfg.plot2rst_default_thumb is None:
|
||||
print "WARNING: No plots found and default thumbnail not defined."
|
||||
print "Specify 'plot2rst_default_thumb' in Sphinx config file."
|
||||
else:
|
||||
shutil.copy(cfg.plot2rst_default_thumb, thumb_path)
|
||||
|
||||
|
||||
def _plots_are_current(src_path, image_path):
|
||||
first_image_file = Path(image_path.format(1))
|
||||
needs_replot = (not first_image_file.exists or
|
||||
_mod_time(first_image_file) <= _mod_time(src_path))
|
||||
return not needs_replot
|
||||
|
||||
|
||||
def _mod_time(file_path):
|
||||
return os.stat(file_path).st_mtime
|
||||
|
||||
|
||||
def split_code_and_text_blocks(source_file):
|
||||
"""Return list with source file separated into code and text blocks.
|
||||
|
||||
Returns
|
||||
-------
|
||||
blocks : list of (label, (start, end+1), content)
|
||||
List where each element is a tuple with the label ('text' or 'code'),
|
||||
the (start, end+1) line numbers, and content string of block.
|
||||
"""
|
||||
block_edges, idx_first_text_block = get_block_edges(source_file)
|
||||
|
||||
with open(source_file) as f:
|
||||
source_lines = f.readlines()
|
||||
|
||||
# Every other block should be a text block
|
||||
idx_text_block = np.arange(idx_first_text_block, len(block_edges), 2)
|
||||
blocks = []
|
||||
slice_ranges = zip(block_edges[:-1], block_edges[1:])
|
||||
for i, (start, end) in enumerate(slice_ranges):
|
||||
block_label = 'text' if i in idx_text_block else 'code'
|
||||
# subtract 1 from indices b/c line numbers start at 1, not 0
|
||||
content = ''.join(source_lines[start-1:end-1])
|
||||
blocks.append((block_label, (start, end), content))
|
||||
return blocks
|
||||
|
||||
|
||||
def get_block_edges(source_file):
|
||||
"""Return starting line numbers of code and text blocks
|
||||
|
||||
Returns
|
||||
-------
|
||||
block_edges : list of int
|
||||
Line number for the start of each block. Note the
|
||||
idx_first_text_block : {0 | 1}
|
||||
0 if first block is text then, else 1 (second block better be text).
|
||||
"""
|
||||
block_edges = []
|
||||
with open(source_file) as f:
|
||||
token_iter = tokenize.generate_tokens(f.readline)
|
||||
for token_tuple in token_iter:
|
||||
t_id, t_str, (srow, scol), (erow, ecol), src_line = token_tuple
|
||||
if (token.tok_name[t_id] == 'STRING' and scol == 0):
|
||||
# Add one point to line after text (for later slicing)
|
||||
block_edges.extend((srow, erow+1))
|
||||
idx_first_text_block = 0
|
||||
# when example doesn't start with text block.
|
||||
if not block_edges[0] == 1:
|
||||
block_edges.insert(0, 1)
|
||||
idx_first_text_block = 1
|
||||
# when example doesn't end with text block.
|
||||
if not block_edges[-1] == erow: # iffy: I'm using end state of loop
|
||||
block_edges.append(erow)
|
||||
return block_edges, idx_first_text_block
|
||||
|
||||
|
||||
def process_blocks(blocks, src_path, image_path, cfg):
|
||||
"""Run source, save plots as images, and convert blocks to rst.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
blocks : list of block tuples
|
||||
Code and text blocks from example. See `split_code_and_text_blocks`.
|
||||
src_path : str
|
||||
Path to example file.
|
||||
image_path : str
|
||||
Path where plots are saved (format string which accepts figure number).
|
||||
cfg : config object
|
||||
Sphinx config object created by Sphinx.
|
||||
|
||||
Returns
|
||||
-------
|
||||
figure_list : list
|
||||
List of figure names saved by the example.
|
||||
rst_text : str
|
||||
Text with code wrapped code-block directives.
|
||||
"""
|
||||
src_dir, src_name = src_path.psplit()
|
||||
if not src_name.startswith('plot'):
|
||||
return [], ''
|
||||
|
||||
# index of blocks which have inline plots
|
||||
inline_tag = cfg.plot2rst_plot_tag
|
||||
idx_inline_plot = [i for i, b in enumerate(blocks)
|
||||
if inline_tag in b[2]]
|
||||
|
||||
image_dir, image_fmt_str = image_path.psplit()
|
||||
|
||||
figure_list = []
|
||||
plt.rcdefaults()
|
||||
plt.rcParams.update(cfg.plot2rst_rcparams)
|
||||
plt.close('all')
|
||||
|
||||
example_globals = {}
|
||||
rst_blocks = []
|
||||
fig_num = 1
|
||||
for i, (blabel, brange, bcontent) in enumerate(blocks):
|
||||
if blabel == 'code':
|
||||
exec(bcontent, example_globals)
|
||||
rst_blocks.append(codestr2rst(bcontent))
|
||||
else:
|
||||
if i in idx_inline_plot:
|
||||
plt.savefig(image_path.format(fig_num))
|
||||
figure_name = image_fmt_str.format(fig_num)
|
||||
fig_num += 1
|
||||
figure_list.append(figure_name)
|
||||
figure_link = os.path.join('images', figure_name)
|
||||
bcontent = bcontent.replace(inline_tag, figure_link)
|
||||
rst_blocks.append(docstr2rst(bcontent))
|
||||
return figure_list, '\n'.join(rst_blocks)
|
||||
|
||||
|
||||
def codestr2rst(codestr):
|
||||
"""Return reStructuredText code block from code string"""
|
||||
code_directive = ".. code-block:: python\n\n"
|
||||
indented_block = '\t' + codestr.replace('\n', '\n\t')
|
||||
return code_directive + indented_block
|
||||
|
||||
|
||||
def docstr2rst(docstr):
|
||||
"""Return reStructuredText from docstring"""
|
||||
idx_whitespace = len(docstr.rstrip()) - len(docstr)
|
||||
whitespace = docstr[idx_whitespace:]
|
||||
return eval(docstr) + whitespace
|
||||
|
||||
|
||||
def save_all_figures(image_path):
|
||||
"""Save all matplotlib figures.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_path : str
|
||||
Path where plots are saved (format string which accepts figure number).
|
||||
"""
|
||||
figure_list = []
|
||||
image_dir, image_fmt_str = image_path.psplit()
|
||||
fig_mngr = matplotlib._pylab_helpers.Gcf.get_all_fig_managers()
|
||||
for fig_num in (m.num for m in fig_mngr):
|
||||
# Set the fig_num figure as the current figure as we can't
|
||||
# save a figure that's not the current figure.
|
||||
plt.figure(fig_num)
|
||||
plt.savefig(image_path.format(fig_num))
|
||||
figure_list.append(image_fmt_str.format(fig_num))
|
||||
return figure_list
|
||||
|
||||
+6
-7
@@ -23,16 +23,10 @@ sys.path.append(os.path.join(curpath, '..', 'ext'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
try:
|
||||
import gen_rst
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.pngmath', 'numpydoc',
|
||||
'sphinx.ext.autosummary', 'plot_directive', 'gen_rst']
|
||||
'sphinx.ext.autosummary', 'plot_directive', 'plot2rst']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -257,3 +251,8 @@ matplotlib.rcParams.update({
|
||||
"""
|
||||
plot_include_source = True
|
||||
plot_formats = [('png', 100)]
|
||||
|
||||
plot2rst_index_name = 'README'
|
||||
plot2rst_rcparams = {'image.cmap' : 'gray',
|
||||
'image.interpolation' : 'none'}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ h1, h2, h3, h4 {
|
||||
font-weight: normal;
|
||||
color: {{ theme_headercolor2 }};
|
||||
margin-bottom: .8em;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
Reference in New Issue
Block a user