Merge tag 'v0.6' into debian

* tag 'v0.6': (154 commits)
  PKG: Update doc versions.
  PKG: Update version to 0.6.
  DOC: Add 0.6 release notes.
  Skip test that fails for PIL < 1.1.7
  BUG: Fix structural similarity to use new signature for view_as_windows.  Remove bad gradient check.
  BUG Fix invalid import of structural_similarity.
  BUG Remove merge artefact.
  BUG: Remove double import of find contours.
  PKG: Rename as_windows to view_as_windows.
  ENH: Promote as_windows to a utility function.
  STY: Wrap long line.
  PKG: Rename _ssim to _structural_similarity.
  ENH: Automatically determine dynamic range in ssim if not specified.
  DOC: In ssim example, use correct dynamic range in algorithm and when displaying results.
  DOC: Example of structural similarity.  The example is currently broken because structural_similarity does not auto-detect dynamic range.
  ENH: Rename ssid to structural_similarity to avoid confusion with self-similarity features.
  ENH: Add SSIM gradient.
  ENH: Add Structural SIMilarity (SSIM) image comparison.
  Replace mpltools reference with skimage.
  ENH: Allow resetting the plugin state.
  ...
This commit is contained in:
Yaroslav Halchenko
2012-06-27 15:29:37 -04:00
59 changed files with 2791 additions and 600 deletions
+3 -1
View File
@@ -28,7 +28,7 @@
- Tony Yu
Reading of paletted images; build, bug and doc fixes.
Code to generate skimage logo.
Otsu thresholding, histogram equalisation, and more.
Otsu thresholding, histogram equalisation, template matching, and more.
- Zachary Pincus
Tracing of low cost paths, FreeImage I/O plugin, iso-contours,
@@ -46,6 +46,7 @@
- Pieter Holtzhausen
Incorporating CellProfiler's Sobel edge detector, build and bug fixes.
Radon transform, template matching.
- Emmanuelle Guillart
Total variation noise filtering, integration of CellProfiler's
@@ -105,3 +106,4 @@
- Johannes Schönberger
Polygon, circle and ellipse drawing functions
Adaptive thresholding
Implementation of Matlab's `regionprops`
+1 -1
View File
@@ -2,7 +2,7 @@ How to make a new release of ``skimage``
========================================
- Update release notes.
- Update the version number in setup.py and commit
- Update the version number in setup.py and bento.info and commit
- Update the docs:
- Edit ``doc/source/themes/agogo/static/docversions.js`` and commit
- Build a clean version of the docs. Run "make" in the root dir, then
+100
View File
@@ -0,0 +1,100 @@
Name: scikits-image
Version: 0.6
Summary: Image processing routines for SciPy
Url: http://scikits-image.org
DownloadUrl: http://github.com/scikits-image/scikits-image
Description: Image Processing SciKit
Image processing algorithms for SciPy, including IO, morphology, filtering,
warping, color manipulation, object detection, etc.
Please refer to the online documentation at
http://scikits-image.org/
Maintainer: Stefan van der Walt
MaintainerEmail: stefan@sun.ac.za
License: Modified BSD
Classifiers:
Development Status :: 4 - Beta,
Environment :: Console,
Intended Audience :: Developers,
Intended Audience :: Science/Research,
License :: OSI Approved :: BSD License,
Programming Language :: C,
Programming Language :: Python,
Programming Language :: Python :: 3,
Topic :: Scientific/Engineering,
Operating System :: Microsoft :: Windows,
Operating System :: POSIX,
Operating System :: Unix,
Operating System :: MacOS
HookFile: bscript
UseBackends: Waf
Library:
Packages:
skimage, skimage.color, skimage.data, skimage.draw, skimage.exposure,
skimage.feature, skimage.filter, skimage.graph, skimage.io,
skimage.io._plugins, skimage.measure, skimage.morphology,
skimage.scripts, skimage.segmentation, skimage.transform, skimage.util
Extension: skimage.morphology._pnpoly
Sources:
skimage/morphology/_pnpoly.pyx
Extension: skimage.feature._greycomatrix
Sources:
skimage/feature/_greycomatrix.pyx
Extension: skimage.feature._template
Sources:
skimage/feature/_template.pyx
Extension: skimage.io._plugins._colormixer
Sources:
skimage/io/_plugins/_colormixer.pyx
Extension: skimage.measure._find_contours
Sources:
skimage/measure/_find_contours.pyx
Extension: skimage.measure._moments
Sources:
skimage/measure/_moments.pyx
Extension: skimage.graph._mcp
Sources:
skimage/graph/_mcp.pyx
Extension: skimage.io._plugins._histograms
Sources:
skimage/io/_plugins/_histograms.pyx
Extension: skimage.transform._hough_transform
Sources:
skimage/transform/_hough_transform.pyx
Extension: skimage.filter._ctmf
Sources:
skimage/filter/_ctmf.pyx
Extension: skimage.morphology.ccomp
Sources:
skimage/morphology/ccomp.pyx
Extension: skimage.morphology._watershed
Sources:
skimage/morphology/_watershed.pyx
Extension: skimage.morphology._convex_hull
Sources:
skimage/morphology/_convex_hull.pyx
Extension: skimage.morphology._skeletonize
Sources:
skimage/morphology/_skeletonize.pyx
Extension: skimage.draw._draw
Sources:
skimage/draw/_draw.pyx
Extension: skimage.transform._project
Sources:
skimage/transform/_project.pyx
Extension: skimage.graph._spath
Sources:
skimage/graph/_spath.pyx
Extension: skimage.morphology.cmorph
Sources:
skimage/morphology/cmorph.pyx
Extension: skimage.graph.heap
Sources:
skimage/graph/heap.pyx
Executable: skivi
Module: skimage.scripts.skivi
Function: main
+12
View File
@@ -0,0 +1,12 @@
import os.path as op
from numpy.distutils.misc_util \
import \
get_numpy_include_dirs
from bento.commands import hooks
@hooks.post_configure
def post_configure(context):
conf = context.waf_context
conf.env.INCLUDES = get_numpy_include_dirs() + [op.join("skimage", "morphology")]
@@ -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()
+64
View File
@@ -0,0 +1,64 @@
"""
=========================
Measure region properties
=========================
This example shows how to measure properties of labelled image regions.
"""
import math
import matplotlib.pyplot as plt
import numpy as np
from skimage.draw import ellipse
from skimage.morphology import label
from skimage.measure import regionprops
from scipy.ndimage import geometric_transform
ANGLE = 0.2
def rotate(xy):
x, y = xy
out_x = math.cos(ANGLE) * x - math.sin(ANGLE) * y
out_y = math.sin(ANGLE) * x + math.cos(ANGLE) * y
return (out_x, out_y)
image = np.zeros((600, 600), 'int')
rr, cc = ellipse(300, 350, 100, 220)
image[rr,cc] = 1
image = geometric_transform(image, rotate)
label_img = label(image)
props = regionprops(label_img, [
'BoundingBox',
'Centroid',
'Orientation',
'MajorAxisLength',
'MinorAxisLength'
])
plt.imshow(image)
for prop in props:
x0 = prop['Centroid'][1]
y0 = prop['Centroid'][0]
x1 = x0 + math.cos(prop['Orientation']) * 0.5 * prop['MajorAxisLength']
y1 = y0 - math.sin(prop['Orientation']) * 0.5 * prop['MajorAxisLength']
x2 = x0 - math.sin(prop['Orientation']) * 0.5 * prop['MinorAxisLength']
y2 = y0 - math.cos(prop['Orientation']) * 0.5 * prop['MinorAxisLength']
plt.plot((x0, x1), (y0, y1), '-r', linewidth=2.5)
plt.plot((x0, x2), (y0, y2), '-r', linewidth=2.5)
plt.plot(x0, y0, '.g', markersize=15)
minr, minc, maxr, maxc = prop['BoundingBox']
bx = (minc, maxc, maxc, minc, minc)
by = (minr, minr, maxr, maxr, minr)
plt.plot(bx, by, '-b', linewidth=2.5)
plt.gray()
plt.axis((0, 600, 600, 0))
plt.show()
+68
View File
@@ -0,0 +1,68 @@
'''
===========================
Structural similarity index
===========================
When comparing images, the mean squared error (MSE)--while simple to
implement--is not highly indicative of perceived similarity. Structural
similarity aims to address this shortcoming by taking texture into account
[1]_, [2]_.
The example shows two modifications of the input image, each with the same MSE,
but with very different mean structural similarity indices.
.. [1] Zhou Wang; Bovik, A.C.; ,"Mean squared error: Love it or leave it? A new
look at Signal Fidelity Measures," Signal Processing Magazine, IEEE,
vol. 26, no. 1, pp. 98-117, Jan. 2009.
.. [2] Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, "Image quality
assessment: From error visibility to structural similarity," IEEE
Transactions on Image Processing, vol. 13, no. 4, pp. 600-612,
Apr. 2004.
'''
from skimage import data, color, io, exposure, img_as_float
from skimage.measure import structural_similarity as ssim
import numpy as np
img = img_as_float(data.camera())
rows, cols = img.shape
noise = np.ones_like(img) * 0.2 * (img.max() - img.min())
noise[np.random.random(size=noise.shape) > 0.5] *= -1
def mse(x, y):
return np.linalg.norm(x - y)
img_noise = img + noise
img_const = img + abs(noise)
import matplotlib.pyplot as plt
f, (ax0, ax1, ax2) = plt.subplots(1, 3)
mse_none = mse(img, img)
ssim_none = ssim(img, img, dynamic_range=img.max() - img.min())
mse_noise = mse(img, img_noise)
ssim_noise = ssim(img, img_noise, dynamic_range=img_const.max() - img_const.min())
mse_const = mse(img, img_const)
ssim_const = ssim(img, img_const, dynamic_range=img_noise.max() - img_noise.min())
label = 'MSE: %2.f, SSIM: %.2f'
ax0.imshow(img, cmap=plt.cm.gray, vmin=0, vmax=1)
ax0.set_xlabel(label % (mse_none, ssim_none))
ax0.set_title('Original image')
ax1.imshow(img_noise, cmap=plt.cm.gray, vmin=0, vmax=1)
ax1.set_xlabel(label % (mse_noise, ssim_noise))
ax1.set_title('Image with noise')
ax2.imshow(img_const, cmap=plt.cm.gray, vmin=0, vmax=1)
ax2.set_xlabel(label % (mse_const, ssim_const))
ax2.set_title('Image plus constant')
plt.show()
+83
View File
@@ -0,0 +1,83 @@
r"""
=====
Swirl
=====
Image swirling is a non-linear image deformation that creates a whirlpool
effect. This example describes the implementation of this transform in
``skimage``, as well as the underlying warp mechanism.
Image warping
`````````````
When applying a geometric transformation on an image, we typically make use of
a reverse mapping, i.e., for each pixel in the output image, we compute its
corresponding position in the input. The reason is that, if we were to do it
the other way around (map each input pixel to its new output position), some
pixels in the output may be left empty. On the other hand, each output
coordinate has exactly one corresponding location in (or outside) the input
image, and even if that position is non-integer, we may use interpolation to
compute the corresponding image value.
Performing a reverse mapping
````````````````````````````
To perform a geometric warp in ``skimage``, you simply need to provide the
reverse mapping to the ``skimage.transform.warp`` function. E.g., consider the
case where we would like to shift an image 50 pixels to the left. The reverse
mapping for such a shift would be::
def shift_left(xy):
xy[:, 0] += 50
return xy
The corresponding call to warp is::
from skimage.transform import warp
warp(image, shift_left)
The swirl transformation
````````````````````````
Consider the coordinate :math:`(x, y)` in the output image. The reverse
mapping for the swirl transformation first computes, relative to a center
:math:`(x_0, y_0)`, its polar coordinates,
.. math::
\theta = \arctan(y/x)
\rho = \sqrt{(x - x_0)^2 + (y - y_0)^2},
and then transforms them according to
.. math::
r = \ln(2) \, \mathtt{radius} / 5
\phi = \mathtt{rotation}
s = \mathtt{strength}
\theta' = \phi + s \, e^{-\rho / r + \theta}
where ``strength`` is a parameter for the amount of swirl, ``radius`` indicates
the swirl extent in pixels, and ``rotation`` adds a rotation angle. The
transformation of ``radius`` into :math:`r` is to ensure that the
transformation decays to :math:`\approx 1/1000^{\mathsf{th}}` within the
specified radius.
"""
from skimage import data
from skimage.transform import swirl
import matplotlib.pyplot as plt
image = data.checkerboard()
swirled = swirl(image, rotation=0, strength=10, radius=120, order=2)
f, (ax0, ax1) = plt.subplots(1, 2, figsize=(8, 3))
ax0.imshow(image, cmap=plt.cm.gray, interpolation='none')
ax0.axis('off')
ax1.imshow(swirled, cmap=plt.cm.gray, interpolation='none')
ax1.axis('off')
plt.show()
+56
View File
@@ -0,0 +1,56 @@
"""
=================
Template Matching
=================
In this example, we use template matching to identify the occurrence of an
image patch (in this case, a sub-image centered on a single coin). Here, we
return a single match (the exact same coin), so the maximum value in the
``match_template`` result corresponds to the coin location. The other coins
look similar, and thus have local maxima; if you expect multiple matches, you
should use a proper peak-finding function.
The ``match_template`` function uses fast, normalized cross-correlation [1]_
to find instances of the template in the image. Note that the peaks in the
output of ``match_template`` correspond to the origin (i.e. top-left corner) of
the template.
.. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and
Magic.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
from skimage.feature import match_template
image = data.coins()
coin = image[170:220, 75:130]
result = match_template(image, coin)
ij = np.unravel_index(np.argmax(result), result.shape)
x, y = ij[::-1]
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(8, 3))
ax1.imshow(coin)
ax1.set_axis_off()
ax1.set_title('template')
ax2.imshow(image)
ax2.set_axis_off()
ax2.set_title('image')
# highlight matched region
hcoin, wcoin = coin.shape
rect = plt.Rectangle((x, y), wcoin, hcoin, edgecolor='r', facecolor='none')
ax2.add_patch(rect)
ax3.imshow(result)
ax3.set_axis_off()
ax3.set_title('`match_template`\nresult')
# highlight matched region
ax3.autoscale(False)
ax3.plot(x, y, 'o', markeredgecolor='r', markerfacecolor='none', markersize=10)
plt.show()
+1 -1
View File
@@ -21,7 +21,7 @@ line.
See Wikipedia_ for more details on the algorithm.
.. _Wikipedia: <http://en.wikipedia.org/wiki/Watershed_(image_processing)>
.. _Wikipedia: http://en.wikipedia.org/wiki/Watershed_(image_processing)
"""
-299
View File
@@ -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')
+502
View File
@@ -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
+40
View File
@@ -0,0 +1,40 @@
Announcement: scikits-image 0.6
===============================
We're happy to announce the 6th version of scikits-image!
Scikits-image is an image processing toolbox for SciPy that includes algorithms
for segmentation, geometric transformations, color space manipulation,
analysis, filtering, morphology, feature detection, and more.
For more information, examples, and documentation, please visit our website
http://skimage.org
New Features
------------
- Packaged in Debian as ``python-skimage``
- Template matching
- Fast user-defined image warping
- Adaptive thresholding
- Structural similarity index
- Polygon, circle and ellipse drawing
- Peak detection
- Region properties
- TiffFile I/O plugin
... along with some bug fixes and performance tweaks.
Contributors to this release
----------------------------
- Vincent Albufera
- David Cournapeau
- Christoph Gohlke
- Emmanuelle Gouillart
- Pieter Holtzhausen
- Zachary Pincus
- Johannes Schönberger
- Tom (tangofoxtrotmike)
- James Turner
- Stefan van der Walt
- Tony S Yu
+6 -8
View File
@@ -23,17 +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', 'sphinx.ext.inheritance_diagram',
'plot_directive', 'gen_rst']
'sphinx.ext.autosummary', 'plot_directive', 'plot2rst']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -258,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 {
@@ -1,5 +1,5 @@
function insert_version_links() {
var labels = ['dev', '0.5', '0.4', '0.3'];
var labels = ['dev', '0.6', '0.5', '0.4', '0.3'];
document.write('<ul class="versions">\n');
+1 -1
View File
@@ -17,7 +17,7 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za'
URL = 'http://scikits-image.org'
LICENSE = 'Modified BSD'
DOWNLOAD_URL = 'http://github.com/scikits-image/scikits-image'
VERSION = '0.6dev'
VERSION = '0.6'
import os
import setuptools
+5 -3
View File
@@ -38,15 +38,17 @@ def cython(pyx_files, working_path=''):
cmd = 'cython -o %s %s' % (c_file, pyxfile)
print(cmd)
if platform.system() == 'Windows':
try:
status = subprocess.call(['cython', '-o', c_file, pyxfile])
except WindowsError:
# On Windows cython.exe may be missing if Cython was installed
# via distutils. Run the cython.py script instead.
status = subprocess.call(
[sys.executable,
os.path.join(os.path.dirname(sys.executable),
'Scripts', 'cython.py'),
'-o', c_file, pyxfile],
shell=True)
else:
status = subprocess.call(['cython', '-o', c_file, pyxfile])
def _md5sum(f):
m = hashlib.new('md5')
+2
View File
@@ -174,6 +174,8 @@ def rescale_intensity(image, in_range=None, out_range=None):
if out_range is None:
omin, omax = dtype_range[dtype]
if imin >= 0:
omin = 0
else:
omin, omax = out_range
+1
View File
@@ -2,3 +2,4 @@ from .hog import hog
from .greycomatrix import greycomatrix, greycoprops
from .peak import peak_local_max
from .harris import harris
from .template import match_template
+129
View File
@@ -0,0 +1,129 @@
"""
Template matching using normalized cross-correlation.
We use fast normalized cross-correlation algorithm (see [1]_ and [2]_) to
compute match probability. This algorithm calculates the normalized
cross-correlation of an image, `I`, with a template `T` according to the
following equation::
sum{ I(x, y) [T(x, y) - <T>] }
-------------------------------------------------------
sqrt(sum{ [I(x, y) - <I>]^2 } sum{ [T(x, y) - <T>]^2 })
where `<T>` is the average of the template, and `<I>` is the average of the
image *coincident with the template*, and sums are over the template and the
image window coincident with the template. Note that the numerator is simply
the cross-correlation of the image and the zero-mean template.
To speed up calculations, we use summed-area tables (a.k.a. integral images) to
quickly calculate sums of image windows inside the loop. This step relies on
the following relation (see Eq. 10 of [1])::
sum{ [I(x, y) - <I>]^2 } =
sum{ I^2(x, y) } - [sum{ I(x, y) }]^2 / N_x N_y
(Without this relation, you would need to subtract each image-window mean from
the image window *before* squaring.)
.. [1] Briechle and Hanebeck, "Template Matching using Fast Normalized
Cross Correlation", Proceedings of the SPIE (2001).
.. [2] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and
Magic.
"""
import cython
cimport numpy as np
import numpy as np
from scipy.signal import fftconvolve
from skimage.transform import integral
cdef extern from "math.h":
float sqrt(float x)
float fabs(float x)
@cython.boundscheck(False)
cdef float integrate(np.ndarray[float, ndim=2, mode="c"] sat,
int r0, int c0, int r1, int c1):
"""
Using a summed area table / integral image, calculate the sum
over a given window.
This function is the same as the `integrate` function in
`skimage.transform.integrate`, but this Cython version significantly
speeds up the code.
Parameters
----------
sat : ndarray of float
Summed area table / integral image.
r0, c0 : int
Top-left corner of block to be summed.
r1, c1 : int
Bottom-right corner of block to be summed.
Returns
-------
S : int
Sum over the given window.
"""
cdef float S = 0
S += sat[r1, c1]
if (r0 - 1 >= 0) and (c0 - 1 >= 0):
S += sat[r0 - 1, c0 - 1]
if (r0 - 1 >= 0):
S -= sat[r0 - 1, c1]
if (c0 - 1 >= 0):
S -= sat[r1, c0 - 1]
return S
@cython.boundscheck(False)
def match_template(np.ndarray[float, ndim=2, mode="c"] image,
np.ndarray[float, ndim=2, mode="c"] template):
cdef np.ndarray[float, ndim=2, mode="c"] corr
cdef np.ndarray[float, ndim=2, mode="c"] image_sat
cdef np.ndarray[float, ndim=2, mode="c"] image_sqr_sat
cdef float template_mean = np.mean(template)
cdef float template_ssd
cdef float inv_area
image_sat = integral.integral_image(image)
image_sqr_sat = integral.integral_image(image**2)
template -= template_mean
template_ssd = np.sum(template**2)
# use inversed area for accuracy
inv_area = 1.0 / (template.shape[0] * template.shape[1])
# when `dtype=float` is used, ascontiguousarray returns ``double``.
corr = np.ascontiguousarray(fftconvolve(image,
template[::-1, ::-1],
mode="valid"),
dtype=np.float32)
cdef int i, j
cdef float den, window_sqr_sum, window_mean_sqr, window_sum,
# move window through convolution results, normalizing in the process
for i in range(corr.shape[0]):
for j in range(corr.shape[1]):
# subtract 1 because `i_end` and `j_end` are used for indexing into
# summed-area table, instead of slicing windows of the image.
i_end = i + template.shape[0] - 1
j_end = j + template.shape[1] - 1
window_sum = integrate(image_sat, i, j, i_end, j_end)
window_mean_sqr = window_sum * window_sum * inv_area
window_sqr_sum = integrate(image_sqr_sat, i, j, i_end, j_end)
if window_sqr_sum <= window_mean_sqr:
corr[i, j] = 0
continue
den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd)
corr[i, j] /= den
return corr
+8 -8
View File
@@ -97,7 +97,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
magnitude = sqrt(gx ** 2 + gy ** 2)
orientation = arctan2(gy, (gx + 1e-15)) * (180 / pi) + 90
sx, sy = image.shape
sy, sx = image.shape
cx, cy = pixels_per_cell
bx, by = cells_per_block
@@ -105,7 +105,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
n_cellsy = int(np.floor(sy // cy)) # number of cells in y
# compute orientations integral images
orientation_histogram = np.zeros((n_cellsx, n_cellsy, orientations))
orientation_histogram = np.zeros((n_cellsy, n_cellsx, orientations))
for i in range(orientations):
#create new integral image for this orientation
# isolate orientations in this range
@@ -118,7 +118,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
cond2 = temp_ori > 0
temp_mag = np.where(cond2, magnitude, 0)
orientation_histogram[:,:,i] = uniform_filter(temp_mag, size=(cx, cy))[cx/2::cx, cy/2::cy].T
orientation_histogram[:,:,i] = uniform_filter(temp_mag, size=(cy, cx))[cy/2::cy, cx/2::cx]
# now for each cell, compute the histogram
@@ -140,7 +140,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
dy = radius * sin(float(o) / orientations * np.pi)
rr, cc = draw.bresenham(centre[0] - dx, centre[1] - dy,
centre[0] + dx, centre[1] + dy)
hog_image[rr, cc] += orientation_histogram[x, y, o]
hog_image[rr, cc] += orientation_histogram[y, x, o]
"""
The fourth stage computes normalisation, which takes local groups of
@@ -159,14 +159,14 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
n_blocksx = (n_cellsx - bx) + 1
n_blocksy = (n_cellsy - by) + 1
normalised_blocks = np.zeros((n_blocksx, n_blocksy,
bx, by, orientations))
normalised_blocks = np.zeros((n_blocksy, n_blocksx,
by, bx, orientations))
for x in range(n_blocksx):
for y in range(n_blocksy):
block = orientation_histogram[x:x + bx, y:y + by, :]
block = orientation_histogram[y:y + by, x:x + bx, :]
eps = 1e-5
normalised_blocks[x, y, :] = block / sqrt(block.sum() ** 2 + eps)
normalised_blocks[y, x, :] = block / sqrt(block.sum() ** 2 + eps)
"""
The final step collects the HOG descriptors from all blocks of a dense
+3
View File
@@ -12,9 +12,12 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('tests')
cython(['_greycomatrix.pyx'], working_path=base_path)
cython(['_template.pyx'], working_path=base_path)
config.add_extension('_greycomatrix', sources=['_greycomatrix.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_template', sources=['_template.c'],
include_dirs=[get_numpy_include_dirs()])
return config
+84
View File
@@ -0,0 +1,84 @@
"""template.py - Template matching
"""
import numpy as np
from . import _template
from skimage.util.dtype import convert
def match_template(image, template, pad_input=False):
"""Match a template to an image using normalized correlation.
The output is an array with values between -1.0 and 1.0, which correspond
to the probability that the template is found at that position.
Parameters
----------
image : array_like
Image to process.
template : array_like
Template to locate.
pad_input : bool
If True, pad `image` with image mean so that output is the same size as
the image, and output values correspond to the template center.
Otherwise, the output is an array with shape `(M - m + 1, N - n + 1)`
for an `(M, N)` image and an `(m, n)` template, and matches correspond
to origin (top-left corner) of the template.
Returns
-------
output : ndarray
Correlation results between -1.0 and 1.0. For an `(M, N)` image and an
`(m, n)` template, the `output` is `(M - m + 1, N - n + 1)` when
`pad_input = False` and `(M, N)` when `pad_input = True`.
Examples
--------
>>> template = np.zeros((3, 3))
>>> template[1, 1] = 1
>>> print template
[[ 0. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 0.]]
>>> image = np.zeros((6, 6))
>>> image[1, 1] = 1
>>> image[4, 4] = -1
>>> print image
[[ 0. 0. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. -1. 0.]
[ 0. 0. 0. 0. 0. 0.]]
>>> result = match_template(image, template)
>>> print np.round(result, 3)
[[ 1. -0.125 0. 0. ]
[-0.125 -0.125 0. 0. ]
[ 0. 0. 0.125 0.125]
[ 0. 0. 0.125 -1. ]]
>>> result = match_template(image, template, pad_input=True)
>>> print np.round(result, 3)
[[-0.125 -0.125 -0.125 0. 0. 0. ]
[-0.125 1. -0.125 0. 0. 0. ]
[-0.125 -0.125 -0.125 0. 0. 0. ]
[ 0. 0. 0. 0.125 0.125 0.125]
[ 0. 0. 0. 0.125 -1. 0.125]
[ 0. 0. 0. 0.125 0.125 0.125]]
"""
if np.any(np.less(image.shape, template.shape)):
raise ValueError("Image must be larger than template.")
image = convert(image, np.float32)
template = convert(template, np.float32)
if pad_input:
pad_size = tuple(np.array(image.shape) + np.array(template.shape) - 1)
pad_image = np.mean(image) * np.ones(pad_size, dtype=np.float32)
h, w = image.shape
i0, j0 = template.shape
i0 /= 2
j0 /= 2
pad_image[i0:i0+h, j0:j0+w] = image
image = pad_image
result = _template.match_template(image, template)
return result
+2 -2
View File
@@ -5,12 +5,12 @@ from skimage.feature import hog
def test_histogram_of_oriented_gradients():
# Replace with skimage.data.lena() after merge
img = scipy.misc.lena().astype(np.int8)
img = scipy.misc.lena()[:256,:].astype(np.int8)
fd = hog(img, orientations=9, pixels_per_cell=(8, 8),
cells_per_block=(1, 1))
assert len(fd) == 9 * (512//8) ** 2
assert len(fd) == 9 * (256//8) * (512//8)
if __name__ == '__main__':
from numpy.testing import run_module_suite
+124
View File
@@ -0,0 +1,124 @@
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
from skimage.morphology import diamond
from skimage.feature import match_template, peak_local_max
def test_template():
size = 100
# Float prefactors ensure that image range is between 0 and 1
image = 0.5 * np.ones((400, 400))
target = 0.1 * (np.tri(size) + np.tri(size)[::-1])
target_positions = [(50, 50), (200, 200)]
for x, y in target_positions:
image[x:x + size, y:y + size] = target
np.random.seed(1)
image += 0.1 * np.random.uniform(size=(400, 400))
result = match_template(image, target)
delta = 5
positions = peak_local_max(result, min_distance=delta)
if len(positions) > 2:
# Keep the two maximum peaks.
intensities = result[tuple(positions.T)]
i_maxsort = np.argsort(intensities)[::-1]
positions = positions[i_maxsort][:2]
# Sort so that order matches `target_positions`.
positions = positions[np.argsort(positions[:, 0])]
for xy_target, xy in zip(target_positions, positions):
yield assert_close, xy, xy_target
def test_normalization():
"""Test that `match_template` gives the correct normalization.
Normalization gives 1 for a perfect match and -1 for an inverted-match.
This test adds positive and negative squares to a zero-array and matches
the array with a positive template.
"""
n = 5
N = 20
ipos, jpos = (2, 3)
ineg, jneg = (12, 11)
image = 0.5 * np.ones((N, N))
image[ipos:ipos + n, jpos:jpos + n] = 1
image[ineg:ineg + n, jneg:jneg + n] = 0
# white square with a black border
template = np.zeros((n+2, n+2))
template[1:1+n, 1:1+n] = 1
result = match_template(image, template)
# get the max and min results.
sorted_result = np.argsort(result.flat)
iflat_min = sorted_result[0]
iflat_max = sorted_result[-1]
min_result = np.unravel_index(iflat_min, result.shape)
max_result = np.unravel_index(iflat_max, result.shape)
# shift result by 1 because of template border
assert np.all((np.array(min_result) + 1) == (ineg, jneg))
assert np.all((np.array(max_result) + 1) == (ipos, jpos))
assert np.allclose(result.flat[iflat_min], -1)
assert np.allclose(result.flat[iflat_max], 1)
def test_no_nans():
"""Test that `match_template` doesn't return NaN values.
When image values are only slightly different, floating-point errors can
cause a subtraction inside of a square root to go negative (without an
explicit check that was added to `match_template`).
"""
np.random.seed(1)
image = 0.5 + 1e-9 * np.random.normal(size=(20, 20))
template = np.ones((6, 6))
template[:3, :] = 0
result = match_template(image, template)
assert not np.any(np.isnan(result))
def test_switched_arguments():
image = np.ones((5, 5))
template = np.ones((3, 3))
np.testing.assert_raises(ValueError, match_template, template, image)
def test_pad_input():
"""Test `match_template` when `pad_input=True`.
This test places two full templates (one with values lower than the image
mean, the other higher) and two half templates, which are on the edges of
the image. The two full templates should score the top (positive and
negative) matches and the centers of the half templates should score 2nd.
"""
# Float prefactors ensure that image range is between 0 and 1
template = 0.5 * diamond(2)
image = 0.5 * np.ones((9, 19))
mid = slice(2, 7)
image[mid, :3] -= template[:, -3:] # half min template centered at 0
image[mid, 4:9] += template # full max template centered at 6
image[mid, -9:-4] -= template # full min template centered at 12
image[mid, -3:] += template[:, :3] # half max template centered at 18
result = match_template(image, template, pad_input=True)
# get the max and min results.
sorted_result = np.argsort(result.flat)
i, j = np.unravel_index(sorted_result[:2], result.shape)
assert_close(j, (12, 0))
i, j = np.unravel_index(sorted_result[-2:], result.shape)
assert_close(j, (18, 6))
if __name__ == "__main__":
from numpy import testing
testing.run_module_suite()
+2 -1
View File
@@ -17,7 +17,8 @@ cdef class MCP:
cdef object flat_costs
cdef object flat_cumulative_costs
cdef object traceback_offsets
cdef object flat_edge_map
cdef object flat_pos_edge_map
cdef object flat_neg_edge_map
cdef readonly object offsets
cdef object flat_offsets
cdef object offset_lengths
+40 -42
View File
@@ -77,47 +77,43 @@ def _offset_edge_map(shape, offsets):
"""Return an array with positions marked where offsets will step
out of bounds.
Given a shape (of length n) and a list of n-d offsets, return a shape + (n,)
sized edge_map, where, for each dimension edge_map[...,dim] has zeros at
indices at which none of the given offsets (in that dimension) will step
out of bounds. If the value is nonzero, it gives the largest offset (in
terms of absolute value) that will step out of bounds in that direction.
Given a shape (of length n) and a list of n-d offsets, return a two arrays
of (n,) + shape: pos_edge_map and neg_edge_map.
For each dimension xxx_edge_map[dim, ...] has zeros at indices at which
none of the given offsets (in that dimension) of the given sign (positive
or negative, respectively) will step out of bounds. If the value is
nonzero, it gives the largest offset (in terms of absolute value) that
will step out of bounds in that direction.
An example will be explanatory:
>>> offsets = [[-2,0], [1,1], [0,2]]
>>> edge_map = _offset_edge_map((4,4), offsets)
>>> edge_map[...,0]
array([[-2, -2, -2, -2],
>>> pos_edge_map, neg_edge_map = _offset_edge_map((4,4), offsets)
>>> neg_edge_map[0]
array([[-1, -1, -1, -1],
[-2, -2, -2, -2],
[ 0, 0, 0, 0],
[ 1, 1, 1, 1]], dtype=int8)
[ 0, 0, 0, 0]], dtype=int8)
>>> edge_map[...,1]
>>> pos_edge_map[1]
array([[0, 0, 2, 1],
[0, 0, 2, 1],
[0, 0, 2, 1],
[0, 0, 2, 1]], dtype=int8)
"""
d = len(shape)
edges = np.zeros(shape+(d,), order='F', dtype=EDGE_D)
indices = np.indices(shape) # indices.shape = (n,)+shape
#get the distance from each index to the upper or lower edge in each dim
pos_edges = (shape - indices.T).T
neg_edges = -1 - indices
# now set the distances to zero if none of the given offsets could reach
offsets = np.asarray(offsets)
for i in range(d):
slices = [slice(None)] * (d+1)
slices[d] = i
distinct_offsets = set(offsets[:,i])
if 0 in distinct_offsets:
distinct_offsets.remove(0)
for offset in sorted(distinct_offsets, key=np.absolute, reverse=True):
# process offsets with larger absolute values first, so that smaller
# offsets will overwrite the correct region of the offsets array.
slice_stop = -offset
if offset > 0:
slice_stop -= 1
slice_step = -np.sign(offset)
slices[i] = slice(None, slice_stop, slice_step)
edges[tuple(slices)] = offset
return edges
maxes = offsets.max(axis=0)
mins = offsets.min(axis=0)
for pos, neg, mx, mn in zip(pos_edges, neg_edges, maxes, mins):
pos[pos > mx] = 0
neg[neg < mn] = 0
return pos_edges.astype(EDGE_D), neg_edges.astype(EDGE_D)
def make_offsets(d, fully_connected):
"""Make a list of offsets from a center point defining a n-dim
@@ -296,9 +292,10 @@ cdef class MCP:
# The edge map stores more than a boolean "on some edge" flag so as to
# allow us to examine the non-out-of-bounds neighbors for a given edge
# point while excluding the neighbors which are outside the array.
self.flat_edge_map = \
_offset_edge_map(costs.shape, self.offsets).reshape(
(size, self.dim), order='F')
pos, neg = _offset_edge_map(costs.shape, self.offsets)
self.flat_pos_edge_map = pos.reshape((self.dim, size), order='F')
self.flat_neg_edge_map = neg.reshape((self.dim, size), order='F')
# The offset lengths are the distances traveled along each offset
self.offset_lengths = np.sqrt(
@@ -393,7 +390,10 @@ cdef class MCP:
self.flat_cumulative_costs
cdef np.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \
self.traceback_offsets
cdef np.ndarray[EDGE_T, ndim=2] flat_edge_map = self.flat_edge_map
cdef np.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \
self.flat_pos_edge_map
cdef np.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \
self.flat_neg_edge_map
cdef np.ndarray[OFFSET_T, ndim=2] offsets = self.offsets
cdef np.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets
cdef np.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths
@@ -413,7 +413,7 @@ cdef class MCP:
cdef BOOL_T is_at_edge, use_offset
cdef INDEX_T d, i
cdef OFFSET_T offset
cdef EDGE_T edge_val
cdef EDGE_T pos_edge_val, neg_edge_val
cdef int num_ends_found = 0
cdef FLOAT_T inf = np.inf
cdef FLOAT_T travel_cost
@@ -449,7 +449,8 @@ cdef class MCP:
# edge along any axis
is_at_edge = 0
for d in range(dim):
if flat_edge_map[index, d] != 0:
if (flat_pos_edge_map[d, index] != 0 or
flat_neg_edge_map[d, index] != 0):
is_at_edge = 1
break
@@ -466,14 +467,11 @@ cdef class MCP:
if is_at_edge:
for d in range(dim):
offset = offsets[i, d]
edge_val = flat_edge_map[index, d]
if (offset < 0 and
edge_val < 0 and
offset <= edge_val) or \
(offset > 0 and
edge_val > 0 and
offset >= edge_val):
pos_edge_val = flat_pos_edge_map[d, index]
neg_edge_val = flat_neg_edge_map[d, index]
if (pos_edge_val > 0 and offset >= pos_edge_val) or \
(neg_edge_val < 0 and offset <= neg_edge_val):
# the offset puts us out of bounds...
use_offset = 0
break
# If not at an edge, or the specific offset doesn't
+16 -1
View File
@@ -114,8 +114,23 @@ def test_no_diagonal():
(7, 2)])
def test_offsets():
offsets = [(1,i) for i in range(10)] + [(1, -i) for i in range(1,10)]
m = mcp.MCP(a, offsets=offsets)
costs, traceback = m.find_costs([(1,6)])
assert_array_equal(traceback,
[[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1],
[15, 14, 13, 12, 11, 10, 0, 1],
[10, 0, 1, 2, 3, 4, 5, 6],
[10, 0, 1, 2, 3, 4, 5, 6],
[10, 0, 1, 2, 3, 4, 5, 6],
[10, 0, 1, 2, 3, 4, 5, 6],
[10, 0, 1, 2, 3, 4, 5, 6]])
def test_crashing():
for shape in [(100, 100), (5, 8, 13, 17)]:
for shape in [(100, 100), (5, 8, 13, 17)]*5:
yield _test_random, shape
def _test_random(shape):
+31 -16
View File
@@ -8,22 +8,7 @@ from ._plugins import use as use_plugin
from ._plugins import available as plugins
from ._plugins import info as plugin_info
from ._plugins import configuration as plugin_order
available_plugins = plugins()
for preferred_plugin in ['matplotlib', 'pil', 'qt', 'freeimage', 'null']:
if preferred_plugin in available_plugins:
try:
use_plugin(preferred_plugin)
break
except ImportError:
pass
# Use PIL as the default imread plugin, since matplotlib (1.2.x)
# is buggy (flips PNGs around, returns bytes as floats, etc.)
try:
use_plugin('pil', 'imread')
except ImportError:
pass
from ._plugins import reset_plugins as _reset_plugins
from .sift import *
from .collection import *
@@ -32,6 +17,34 @@ from ._io import *
from .video import *
available_plugins = plugins()
def _load_preferred_plugins():
# Load preferred plugin for each io function.
io_funcs = ['imsave', 'imshow', 'imread_collection', 'imread']
preferred_plugins = ['matplotlib', 'pil', 'qt', 'freeimage', 'null']
for func in io_funcs:
for plugin in preferred_plugins:
if plugin not in available_plugins:
continue
try:
use_plugin(plugin, kind=func)
break
except (ImportError, RuntimeError):
pass
# Use PIL as the default imread plugin, since matplotlib (1.2.x)
# is buggy (flips PNGs around, returns bytes as floats, etc.)
try:
use_plugin('pil', 'imread')
except ImportError:
pass
def reset_plugins():
_reset_plugins()
_load_preferred_plugins()
def _update_doc(doc):
"""Add a list of plugins to the module docstring, formatted as
a ReStructuredText table.
@@ -61,3 +74,5 @@ def _update_doc(doc):
return doc
__doc__ = _update_doc(__doc__)
reset_plugins()
+5 -1
View File
@@ -95,7 +95,11 @@ def imread_collection(load_pattern, conserve_memory=True):
isinstance(hdu, pyfits.PrimaryHDU):
# Ignore (primary) header units with no data (use '.size'
# rather than '.data' to avoid actually loading the image):
if hdu.size() > 0:
try:
data_size = hdu.size()
except TypeError: # (size changed to int in PyFITS 3.1)
data_size = hdu.size
if data_size > 0:
ext_list.append((filename, n))
hdulist.close()
+13 -4
View File
@@ -55,19 +55,22 @@ def load_freeimage():
try:
freeimage = loader.LoadLibrary(lib)
break
except Exception, e:
except Exception:
if lib not in bare_libs:
# Don't record errors when it couldn't load the library from
# a bare name -- this fails often, and doesn't provide any
# useful debugging information anyway, beyond "couldn't find
# library..."
errors.append((lib, e))
# Get exception instance in Python 2.x/3.x compatible manner
e_type, e_value, e_tb = sys.exc_info()
del e_tb
errors.append((lib, e_value))
if freeimage is None:
if errors:
# No freeimage library loaded, and load-errors reported for some
# candidate libs
err_txt = ['%s:\n%s'%(l, e.message) for l, e in errors]
err_txt = ['%s:\n%s'%(l, str(e.message)) for l, e in errors]
raise OSError('One or more FreeImage libraries were found, but '
'could not be loaded due to the following errors:\n'+
'\n\n'.join(err_txt))
@@ -340,6 +343,9 @@ class METADATA_DATATYPE(object):
FIDT_DOUBLE = 12 # 64-bit IEEE floating point
FIDT_IFD = 13 # 32-bit unsigned integer (offset)
FIDT_PALETTE = 14 # 32-bit RGBQUAD
FIDT_LONG8 = 16 # 64-bit unsigned integer
FIDT_SLONG8 = 17 # 64-bit signed integer
FIDT_IFD8 = 18 # 64-bit unsigned integer (offset)
dtypes = {
FIDT_BYTE: numpy.uint8,
@@ -357,7 +363,10 @@ class METADATA_DATATYPE(object):
FIDT_DOUBLE: numpy.float64,
FIDT_IFD: numpy.uint32,
FIDT_PALETTE: [('R', numpy.uint8), ('G', numpy.uint8),
('B', numpy.uint8), ('A', numpy.uint8)]
('B', numpy.uint8), ('A', numpy.uint8)],
FIDT_LONG8: numpy.uint64,
FIDT_SLONG8: numpy.int64,
FIDT_IFD8: numpy.uint64
}
+15 -6
View File
@@ -2,23 +2,32 @@
"""
__all__ = ['use', 'available', 'call', 'info', 'configuration']
__all__ = ['use', 'available', 'call', 'info', 'configuration', 'reset_plugins']
import warnings
from ConfigParser import ConfigParser
import os.path
from glob import glob
plugin_store = {'imread': [],
'imsave': [],
'imshow': [],
'imread_collection': [],
'_app_show': []}
plugin_store = None
plugin_provides = {}
plugin_module_name = {}
plugin_meta_data = {}
def reset_plugins():
"""Clear the plugin state to the default, i.e., where no plugins are loaded.
"""
global plugin_store
plugin_store = {'imread': [],
'imsave': [],
'imshow': [],
'imread_collection': [],
'_app_show': []}
reset_plugins()
def _scan_plugins():
"""Scan the plugins directory for .ini files and parse them
to gather plugin meta-data.
+1 -1
View File
@@ -3,4 +3,4 @@ try:
except ImportError:
raise ImportError("The tifffile module could not be found.\n"
"It can be obtained at "
"<http://www.lfd.uci.edu/~gohlke/code/tifffile.py.html>\n")
"<http://www.lfd.uci.edu/~gohlke/code/tifffile.py>\n")
+1 -1
View File
@@ -9,7 +9,7 @@ from skimage.util import img_as_ubyte
try:
import multiprocessing
CPU_COUNT = multiprocessing.cpu_count()
except ImportError:
except:
CPU_COUNT = 2
class GuiLockError(Exception):
+5
View File
@@ -26,6 +26,11 @@ def test_fits_plugin_import():
else:
assert pyfits_available == True
def teardown():
io.reset_plugins()
@skipif(not pyfits_available)
def test_imread_MEF():
io.use_plugin('fits')
+4
View File
@@ -27,6 +27,10 @@ def setup_module(self):
pass
def teardown():
sio.reset_plugins()
@skipif(not FI_available)
def test_imread():
img = sio.imread(os.path.join(si.data_dir, 'color.png'))
+12 -3
View File
@@ -6,7 +6,7 @@ from numpy.testing.decorators import skipif
from tempfile import NamedTemporaryFile
from skimage import data_dir
from skimage.io import imread, imsave, use_plugin
from skimage.io import imread, imsave, use_plugin, reset_plugins
try:
from PIL import Image
@@ -18,6 +18,10 @@ else:
PIL_available = True
def teardown():
reset_plugins()
def setup_module(self):
"""The effect of the `plugin.use` call may be overridden by later imports.
Call `use_plugin` directly before the tests to ensure that PIL is used.
@@ -65,10 +69,12 @@ def test_bilevel():
def test_imread_uint16():
expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy'))
img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16.tif'))
assert img.dtype == np.uint16
assert np.issubdtype(img.dtype, np.uint16)
assert_array_almost_equal(img, expected)
@skipif(not PIL_available)
# Big endian images not correctly loaded for PIL < 1.1.7
# Renable test when PIL 1.1.7 is more common.
@skipif(True)
def test_imread_uint16_big_endian():
expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy'))
img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16B.tif'))
@@ -97,3 +103,6 @@ class TestSave:
else:
x = (x * 255).astype(dtype)
yield self.roundtrip, dtype, x
if __name__ == "__main__":
run_module_suite()
+1 -4
View File
@@ -4,8 +4,6 @@ from skimage import io
from skimage.io._plugins import plugin
from numpy.testing.decorators import skipif
from copy import deepcopy
try:
io.use_plugin('pil')
PIL_available = True
@@ -22,11 +20,10 @@ except OSError:
def setup_module(self):
self.backup_plugin_store = deepcopy(plugin.plugin_store)
plugin.use('test') # see ../_plugins/test_plugin.py
def teardown_module(self):
plugin.plugin_store = self.backup_plugin_store
io.reset_plugins()
class TestPlugin:
def test_read(self):
+1 -3
View File
@@ -17,9 +17,7 @@ except ImportError:
def teardown():
if TF_available:
for k, v in _plugins.items():
sio.use_plugin(v[0], k)
sio.reset_plugins()
@skipif(not TF_available)
+2
View File
@@ -1 +1,3 @@
from .find_contours import find_contours
from ._regionprops import regionprops
from ._structural_similarity import structural_similarity
+52
View File
@@ -0,0 +1,52 @@
#cython: boundscheck=False
#cython: wraparound=False
#cython: cdivision=True
import numpy as np
cimport numpy as np
def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc,
int order):
cdef int p, q, r, c
cdef np.ndarray[np.double_t, ndim=2] mu
mu = np.zeros((order + 1, order + 1), 'double')
for p in range(order + 1):
for q in range(order + 1):
for r in range(array.shape[0]):
for c in range(array.shape[1]):
mu[p,q] += array[r,c] * (r - cr) ** q * (c - cc) ** p
return mu
def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order):
cdef int p, q
cdef np.ndarray[np.double_t, ndim=2] nu
nu = np.zeros((order + 1, order + 1), 'double')
for p in range(order + 1):
for q in range(order + 1):
if p + q >= 2:
nu[p,q] = mu[p,q] / mu[0,0]**(<double>(p + q) / 2 + 1)
else:
nu[p,q] = np.nan
return nu
def hu_moments(np.ndarray[np.double_t, ndim=2] nu):
cdef np.ndarray[np.double_t, ndim=1] hu = np.zeros((7,), 'double')
cdef double t0 = nu[3,0] + nu[1,2]
cdef double t1 = nu[2,1] + nu[0,3]
cdef double q0 = t0 * t0
cdef double q1 = t1 * t1
cdef double n4 = 4 * nu[1,1]
cdef double s = nu[2,0] + nu[0,2]
cdef double d = nu[2,0] - nu[0,2]
hu[0] = s
hu[1] = d * d + n4 * nu[1,1]
hu[3] = q0 + q1
hu[5] = d * (q0 - q1) + n4 * t0 * t1
t0 *= q0 - 3 * q1
t1 *= 3 * q0 - q1
q0 = nu[3,0]- 3 * nu[1,2]
q1 = 3 * nu[2,1] - nu[0,3]
hu[2] = q0 * q0 + q1 * q1
hu[4] = q0 * t0 + q1 * t1
hu[6] = q1 * t0 - q0 * t1
return hu
+353
View File
@@ -0,0 +1,353 @@
# coding: utf-8
from math import sqrt, atan, pi as PI
import numpy as np
from scipy import ndimage
from skimage.morphology import convex_hull_image
from . import _moments
__all__ = ['regionprops']
STREL_8 = np.ones((3, 3), 'int8')
PROPS = (
'Area',
'BoundingBox',
'CentralMoments',
'Centroid',
'ConvexArea',
# 'ConvexHull',
'ConvexImage',
'Eccentricity',
'EquivDiameter',
'EulerNumber',
'Extent',
# 'Extrema',
'FilledArea',
'FilledImage',
'HuMoments',
'Image',
'MajorAxisLength',
'MaxIntensity',
'MeanIntensity',
'MinIntensity',
'MinorAxisLength',
'Moments',
'NormalizedMoments',
'Orientation',
# 'Perimeter',
# 'PixelIdxList',
# 'PixelList',
'Solidity',
# 'SubarrayIdx'
'WeightedCentralMoments',
'WeightedCentroid',
'WeightedHuMoments',
'WeightedMoments',
'WeightedNormalizedMoments'
)
def regionprops(label_image, properties=['Area', 'Centroid'],
intensity_image=None):
"""Measure properties of labelled image regions.
Parameters
----------
label_image : N x M ndarray
Labelled input image.
properties : {'all', list}
Shape measurements to be determined for each labelled image region.
Default is `['Area', 'Centroid']`. The following properties can be
determined:
* Area : int
Number of pixels of region.
* BoundingBox : tuple
Bounding box `(min_row, min_col, max_row, max_col)`
* CentralMoments : 3 x 3 ndarray
Central moments (translation invariant) up to 3rd order.
mu_ji = sum{ array(x, y) * (x - x_c)^j * (y - y_c)^i }
where the sum is over the `x`, `y` coordinates of the region,
and `x_c` and `y_c` are the coordinates of the region's centroid.
* Centroid : array
Centroid coordinate tuple `(row, col)`.
* ConvexArea : int
Number of pixels of convex hull image.
* ConvexImage : H x J ndarray
Binary convex hull image which has the same size as bounding box.
* Eccentricity : float
Eccentricity of the ellipse that has the same second-moments as the
region. The eccentricity is the ratio of the distance between its
minor and major axis length. The value is between 0 and 1.
* EquivDiameter : float
The diameter of a circle with the same area as the region.
* EulerNumber : int
Euler number of region. Computed as number of objects (= 1)
subtracted by number of holes (8-connectivity).
* Extent : float
Ratio of pixels in the region to pixels in the total bounding box.
Computed as `Area / (rows*cols)`
* FilledArea : int
Number of pixels of filled region.
* FilledImage : H x J ndarray
Binary region image with filled holes which has the same size as
bounding box.
* HuMoments : tuple
Hu moments (translation, scale and rotation invariant).
* Image : H x J ndarray
Sliced binary region image which has the same size as bounding box.
* MajorAxisLength : float
The length of the major axis of the ellipse that has the same
normalized second central moments as the region.
* MaxIntensity: float
Value with the greatest intensity in the region.
* MeanIntensity: float
Value with the mean intensity in the region.
* MinIntensity: float
Value with the least intensity in the region.
* MinorAxisLength : float
The length of the minor axis of the ellipse that has the same
normalized second central moments as the region.
* Moments : 3 x 3 ndarray
Spatial moments up to 3rd order.
m_ji = sum{ array(x, y) * x^j * y^i }
where the sum is over the `x`, `y` coordinates of the region.
* NormalizedMoments : 3 x 3 ndarray
Normalized moments (translation and scale invariant) up to 3rd
order.
nu_ji = mu_ji / m_00^[(i+j)/2 + 1]
where `m_00` is the zeroth spatial moment.
* Orientation : float
Angle between the X-axis and the major axis of the ellipse that has
the same second-moments as the region. Ranging from `-pi/2` to
`-pi/2` in counter-clockwise direction.
* Solidity : float
Ratio of pixels in the region to pixels of the convex hull image.
* WeightedCentralMoments : 3 x 3 ndarray
Central moments (translation invariant) of intensity image up to 3rd
order.
wmu_ji = sum{ array(x, y) * (x - x_c)^j * (y - y_c)^i }
where the sum is over the `x`, `y` coordinates of the region,
and `x_c` and `y_c` are the coordinates of the region's centroid.
* WeightedCentroid : array
Centroid coordinate tuple `(row, col)` weighted with intensity
image.
* WeightedHuMoments : tuple
Hu moments (translation, scale and rotation invariant) of intensity
image.
* WeightedMoments : 3 x 3 ndarray
Spatial moments of intensity image up to 3rd order.
wm_ji = sum{ array(x, y) * x^j * y^i }
where the sum is over the `x`, `y` coordinates of the region.
* WeightedNormalizedMoments : 3 x 3 ndarray
Normalized moments (translation and scale invariant) of intensity
image up to 3rd order.
wnu_ji = wmu_ji / wm_00^[(i+j)/2 + 1]
where `wm_00` is the zeroth spatial moment (intensity-weighted
area).
intensity_image : N x M ndarray, optional
Intensity image with same size as labelled image. Default is None.
Returns
-------
properties : list of dicts
List containing a property dict for each region. The property dicts
contain all the specified properties plus a 'Label' field.
References
----------
Wilhelm Burger, Mark Burge. Principles of Digital Image Processing: Core
Algorithms. Springer-Verlag, London, 2009.
B. Jähne. Digital Image Processing. Springer-Verlag,
Berlin-Heidelberg, 6. edition, 2005.
T. H. Reiss. Recognizing Planar Objects Using Invariant Image Features,
from Lecture notes in computer science, p. 676. Springer, Berlin, 1993.
http://en.wikipedia.org/wiki/Image_moment
Examples
--------
>>> from skimage.data import coins
>>> from skimage.morphology import label
>>> img = coins() > 110
>>> label_img = label(img)
>>> props = regionprops(label_img)
>>> props[0]['Centroid'] # centroid of first labelled object
"""
if not np.issubdtype(label_image.dtype, 'int'):
raise TypeError('labelled image must be of integer dtype')
# determine all properties if nothing specified
if properties == 'all':
properties = PROPS
props = []
objects = ndimage.find_objects(label_image)
for i, sl in enumerate(objects):
label = i + 1
# create property dict for current label
obj_props = {}
props.append(obj_props)
obj_props['Label'] = label
array = (label_image[sl] == label).astype('double')
# upper left corner of object bbox
r0 = sl[0].start
c0 = sl[1].start
m = _moments.central_moments(array, 0, 0, 3)
# centroid
cr = m[0,1] / m[0,0]
cc = m[1,0] / m[0,0]
mu = _moments.central_moments(array, cr, cc, 3)
#: elements of the inertia tensor [a b; b c]
a = mu[2,0] / mu[0,0]
b = mu[1,1] / mu[0,0]
c = mu[0,2] / mu[0,0]
#: eigen values of inertia tensor
l1 = (a + c) / 2 + sqrt(4 * b ** 2 + (a - c) ** 2) / 2
l2 = (a + c) / 2 - sqrt(4 * b ** 2 + (a - c) ** 2) / 2
# cached results which are used by several properties
_filled_image = None
_convex_image = None
_nu = None
if 'Area' in properties:
obj_props['Area'] = m[0,0]
if 'BoundingBox' in properties:
obj_props['BoundingBox'] = (r0, c0, sl[0].stop, sl[1].stop)
if 'Centroid' in properties:
obj_props['Centroid'] = cr + r0, cc + c0
if 'CentralMoments' in properties:
obj_props['CentralMoments'] = mu
if 'ConvexArea' in properties:
if _convex_image is None:
_convex_image = convex_hull_image(array)
obj_props['ConvexArea'] = np.sum(_convex_image)
if 'ConvexImage' in properties:
if _convex_image is None:
_convex_image = convex_hull_image(array)
obj_props['ConvexImage'] = _convex_image
if 'Eccentricity' in properties:
if l1 == 0:
obj_props['Eccentricity'] = 0
else:
obj_props['Eccentricity'] = sqrt(1 - l2 / l1)
if 'EquivDiameter' in properties:
obj_props['EquivDiameter'] = sqrt(4 * m[0,0] / PI)
if 'EulerNumber' in properties:
if _filled_image is None:
_filled_image = ndimage.binary_fill_holes(array, STREL_8)
euler_array = _filled_image != array
_, num = ndimage.label(euler_array, STREL_8)
obj_props['EulerNumber'] = - num
if 'Extent' in properties:
obj_props['Extent'] = m[0,0] / (array.shape[0] * array.shape[1])
if 'HuMoments' in properties:
if _nu is None:
_nu = _moments.normalized_moments(mu, 3)
obj_props['HuMoments'] = _moments.hu_moments(_nu)
if 'Image' in properties:
obj_props['Image'] = array
if 'FilledArea' in properties:
if _filled_image is None:
_filled_image = ndimage.binary_fill_holes(array, STREL_8)
obj_props['FilledArea'] = np.sum(_filled_image)
if 'FilledImage' in properties:
if _filled_image is None:
_filled_image = ndimage.binary_fill_holes(array, STREL_8)
obj_props['FilledImage'] = _filled_image
if 'MajorAxisLength' in properties:
obj_props['MajorAxisLength'] = 4 * sqrt(l1)
if 'MinorAxisLength' in properties:
obj_props['MinorAxisLength'] = 4 * sqrt(l2)
if 'Moments' in properties:
obj_props['Moments'] = m
if 'NormalizedMoments' in properties:
if _nu is None:
_nu = _moments.normalized_moments(mu, 3)
obj_props['NormalizedMoments'] = _nu
if 'Orientation' in properties:
if a - c == 0:
obj_props['Orientation'] = PI / 2
else:
obj_props['Orientation'] = - 0.5 * atan(2 * b / (a - c))
if 'Solidity' in properties:
if _convex_image is None:
_convex_image = convex_hull_image(array)
obj_props['Solidity'] = m[0,0] / np.sum(_convex_image)
if intensity_image is not None:
weighted_array = array * intensity_image[sl]
wm = _moments.central_moments(weighted_array, 0, 0, 3)
# weighted centroid
wcr = wm[0,1] / wm[0,0]
wcc = wm[1,0] / wm[0,0]
wmu = _moments.central_moments(weighted_array, wcr, wcc, 3)
# cached results which are used by several properties
_wnu = None
_vals = None
if 'MaxIntensity' in properties:
if _vals is None:
_vals = weighted_array[array.astype('bool')]
obj_props['MaxIntensity'] = np.max(_vals)
if 'MeanIntensity' in properties:
if _vals is None:
_vals = weighted_array[array.astype('bool')]
obj_props['MeanIntensity'] = np.mean(_vals)
if 'MinIntensity' in properties:
if _vals is None:
_vals = weighted_array[array.astype('bool')]
obj_props['MinIntensity'] = np.min(_vals)
if 'WeightedCentralMoments' in properties:
obj_props['WeightedCentralMoments'] = wmu
if 'WeightedCentroid' in properties:
obj_props['WeightedCentroid'] = wcr + r0, wcc + c0
if 'WeightedHuMoments' in properties:
if _wnu is None:
_wnu = _moments.normalized_moments(wmu, 3)
obj_props['WeightedHuMoments'] = _moments.hu_moments(_wnu)
if 'WeightedMoments' in properties:
obj_props['WeightedMoments'] = wm
if 'WeightedNormalizedMoments' in properties:
if _wnu is None:
_wnu = _moments.normalized_moments(wmu, 3)
obj_props['WeightedNormalizedMoments'] = _wnu
return props
+105
View File
@@ -0,0 +1,105 @@
from __future__ import division
__all__ = ['structural_similarity']
import numpy as np
from ..util.dtype import dtype_range
from ..util.shape import view_as_windows
def structural_similarity(X, Y, win_size=7,
gradient=False, dynamic_range=None):
"""Compute the mean structural similarity index between two images.
Parameters
----------
X, Y : (N,N) ndarray
Images.
win_size : int
The side-length of the sliding window used in comparison. Must
be an odd value.
gradient : bool
If True, also return the gradient.
dynamic_range : int
Dynamic range of the input image (distance between minimum and
maximum possible values). By default, this is estimated from
the image data-type.
Returns
-------
s : float
Strucutural similarity.
grad : (N * N,) ndarray
Gradient of the structural similarity index between X and Y.
This is only returned if `gradient` is set to True.
References
----------
.. [1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P.
(2004). Image quality assessment: From error visibility to
structural similarity. IEEE Transactions on Image Processing,
13, 600-612.
"""
if not X.dtype == Y.dtype:
raise ValueError('Input images must have the same dtype.')
if not X.shape == Y.shape:
raise ValueError('Input images must have the same dimensions.')
if not (win_size % 2 == 1):
raise ValueError('Window size must be odd.')
if dynamic_range is None:
dmin, dmax = dtype_range[X.dtype.type]
dynamic_range = dmax - dmin
XW = view_as_windows(X, (win_size, win_size))
YW = view_as_windows(Y, (win_size, win_size))
NS = len(XW)
NP = win_size * win_size
ux = np.mean(np.mean(XW, axis=2), axis=2)
uy = np.mean(np.mean(YW, axis=2), axis=2)
# Compute variances var(X), var(Y) and var(X, Y)
cov_norm = 1 / (win_size**2 - 1)
XWM = XW - ux[..., None, None]
YWM = YW - uy[..., None, None]
vx = cov_norm * np.sum(np.sum(XWM**2, axis=2), axis=2)
vy = cov_norm * np.sum(np.sum(YWM**2, axis=2), axis=2)
vxy = cov_norm * np.sum(np.sum(XWM * YWM, axis=2), axis=2)
R = dynamic_range
K1 = 0.01
K2 = 0.03
C1 = (K1 * R)**2
C2 = (K2 * R)**2
A1, A2, B1, B2 = (v[..., None, None] for v in
(2 * ux * uy + C1,
2 * vxy + C2,
ux**2 + uy**2 + C1,
vx + vy + C2))
S = np.mean((A1 * A2) / (B1 * B2))
if gradient:
local_grad = 2 / (NP * B1**2 * B2**2) * \
(
A1 * B1 * (B2 * XW - A2 * YW) - \
B1 * B2 * (A2 - A1) * ux[..., None, None] + \
A1 * A2 * (B1 - B2) * uy[..., None, None]
)
grad = np.zeros_like(X, dtype=float)
OW = view_as_windows(grad, (win_size, win_size))
OW += local_grad
grad /= NS
return S, grad
else:
return S
+3
View File
@@ -12,9 +12,12 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('tests')
cython(['_find_contours.pyx'], working_path=base_path)
cython(['_moments.pyx'], working_path=base_path)
config.add_extension('_find_contours', sources=['_find_contours.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_moments', sources=['_moments.c'],
include_dirs=[get_numpy_include_dirs()])
return config
+249
View File
@@ -0,0 +1,249 @@
from numpy.testing import assert_array_equal, assert_almost_equal, \
assert_array_almost_equal
import numpy as np
from skimage.measure import regionprops
SAMPLE = np.array(
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1],
[0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]
)
INTENSITY_SAMPLE = SAMPLE.copy()
INTENSITY_SAMPLE[1,9:11] = 2
def test_area():
area = regionprops(SAMPLE, ['Area'])[0]['Area']
assert area == np.sum(SAMPLE)
def test_bbox():
bbox = regionprops(SAMPLE, ['BoundingBox'])[0]['BoundingBox']
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]))
SAMPLE_mod = SAMPLE.copy()
SAMPLE_mod[:,-1] = 0
bbox = regionprops(SAMPLE_mod, ['BoundingBox'])[0]['BoundingBox']
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1))
def test_central_moments():
mu = regionprops(SAMPLE, ['CentralMoments'])[0]['CentralMoments']
#: determined with OpenCV
assert_almost_equal(mu[0,2], 436.00000000000045)
# different from OpenCV results, bug in OpenCV
assert_almost_equal(mu[0,3], -737.333333333333)
assert_almost_equal(mu[1,1], -87.33333333333303)
assert_almost_equal(mu[1,2], -127.5555555555593)
assert_almost_equal(mu[2,0], 1259.7777777777774)
assert_almost_equal(mu[2,1], 2000.296296296291)
assert_almost_equal(mu[3,0], -760.0246913580195)
def test_centroid():
centroid = regionprops(SAMPLE, ['Centroid'])[0]['Centroid']
# determined with MATLAB
assert_array_almost_equal(centroid, (5.66666666666666, 9.444444444444444))
def test_convex_area():
area = regionprops(SAMPLE, ['ConvexArea'])[0]['ConvexArea']
# determined with MATLAB
assert area == 124
def test_convex_image():
img = regionprops(SAMPLE, ['ConvexImage'])[0]['ConvexImage']
# determined with MATLAB
ref = np.array(
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
)
assert_array_equal(img, ref)
def test_eccentricity():
eps = regionprops(SAMPLE, ['Eccentricity'])[0]['Eccentricity']
assert_almost_equal(eps, 0.814629313427)
def test_equiv_diameter():
diameter = regionprops(SAMPLE, ['EquivDiameter'])[0]['EquivDiameter']
# determined with MATLAB
assert_almost_equal(diameter, 9.57461472963)
def test_euler_number():
en = regionprops(SAMPLE, ['EulerNumber'])[0]['EulerNumber']
assert en == 0
SAMPLE_mod = SAMPLE.copy()
SAMPLE_mod[7,-3] = 0
en = regionprops(SAMPLE_mod, ['EulerNumber'])[0]['EulerNumber']
assert en == -1
def test_extent():
extent = regionprops(SAMPLE, ['Extent'])[0]['Extent']
assert_almost_equal(extent, 0.4)
def test_hu_moments():
hu = regionprops(SAMPLE, ['HuMoments'])[0]['HuMoments']
ref = np.array([
3.27117627e-01,
2.63869194e-02,
2.35390060e-02,
1.23151193e-03,
1.38882330e-06,
-2.72586158e-05,
6.48350653e-06
])
# bug in OpenCV caused in Central Moments calculation?
assert_array_almost_equal(hu, ref)
def test_image():
img = regionprops(SAMPLE, ['Image'])[0]['Image']
assert_array_equal(img, SAMPLE)
def test_filled_area():
area = regionprops(SAMPLE, ['FilledArea'])[0]['FilledArea']
assert area == np.sum(SAMPLE)
SAMPLE_mod = SAMPLE.copy()
SAMPLE_mod[7,-3] = 0
area = regionprops(SAMPLE_mod, ['FilledArea'])[0]['FilledArea']
assert area == np.sum(SAMPLE)
def test_major_axis_length():
length = regionprops(SAMPLE, ['MajorAxisLength'])[0]['MajorAxisLength']
# MATLAB has different interpretation of ellipse than found in literature,
# here implemented as found in literature
assert_almost_equal(length, 16.7924234999)
def test_max_intensity():
intensity = regionprops(SAMPLE, ['MaxIntensity'], INTENSITY_SAMPLE
)[0]['MaxIntensity']
assert_almost_equal(intensity, 2)
def test_mean_intensity():
intensity = regionprops(SAMPLE, ['MeanIntensity'], INTENSITY_SAMPLE
)[0]['MeanIntensity']
assert_almost_equal(intensity, 1.02777777777777)
def test_min_intensity():
intensity = regionprops(SAMPLE, ['MinIntensity'], INTENSITY_SAMPLE
)[0]['MinIntensity']
assert_almost_equal(intensity, 1)
def test_minor_axis_length():
length = regionprops(SAMPLE, ['MinorAxisLength'])[0]['MinorAxisLength']
# MATLAB has different interpretation of ellipse than found in literature,
# here implemented as found in literature
assert_almost_equal(length, 9.739302807263)
def test_moments():
m = regionprops(SAMPLE, ['Moments'])[0]['Moments']
#: determined with OpenCV
assert_almost_equal(m[0,0], 72.0)
assert_almost_equal(m[0,1], 408.0)
assert_almost_equal(m[0,2], 2748.0)
assert_almost_equal(m[0,3], 19776.0)
assert_almost_equal(m[1,0], 680.0)
assert_almost_equal(m[1,1], 3766.0)
assert_almost_equal(m[1,2], 24836.0)
assert_almost_equal(m[2,0], 7682.0)
assert_almost_equal(m[2,1], 43882.0)
assert_almost_equal(m[3,0], 95588.0)
def test_normalized_moments():
nu = regionprops(SAMPLE, ['NormalizedMoments'])[0]['NormalizedMoments']
#: determined with OpenCV
assert_almost_equal(nu[0,2], 0.08410493827160502)
assert_almost_equal(nu[1,1], -0.016846707818929982)
assert_almost_equal(nu[1,2], -0.002899800614433943)
assert_almost_equal(nu[2,0], 0.24301268861454037)
assert_almost_equal(nu[2,1], 0.045473992910668816)
assert_almost_equal(nu[3,0], -0.017278118992041805)
def test_orientation():
orientation = regionprops(SAMPLE, ['Orientation'])[0]['Orientation']
# determined with MATLAB
assert_almost_equal(orientation, 0.10446844651921)
def test_solidity():
solidity = regionprops(SAMPLE, ['Solidity'])[0]['Solidity']
# determined with MATLAB
assert_almost_equal(solidity, 0.580645161290323)
def test_weighted_central_moments():
wmu = regionprops(SAMPLE, ['WeightedCentralMoments'], INTENSITY_SAMPLE
)[0]['WeightedCentralMoments']
ref = np.array(
[[ 7.4000000000e+01, -2.1316282073e-13, 4.7837837838e+02,
-7.5943608473e+02],
[ 3.7303493627e-14, -8.7837837838e+01, -1.4801314828e+02,
-1.2714707125e+03],
[ 1.2602837838e+03, 2.1571526662e+03, 6.6989799420e+03,
1.5304076361e+04],
[ -7.6561796932e+02, -4.2385971907e+03, -9.9501164076e+03,
-3.3156729271e+04]]
)
np.set_printoptions(precision=10)
assert_array_almost_equal(wmu, ref)
def test_weighted_centroid():
centroid = regionprops(SAMPLE, ['WeightedCentroid'], INTENSITY_SAMPLE
)[0]['WeightedCentroid']
assert_array_almost_equal(centroid, (5.540540540540, 9.445945945945))
def test_weighted_hu_moments():
whu = regionprops(SAMPLE, ['WeightedHuMoments'], INTENSITY_SAMPLE
)[0]['WeightedHuMoments']
ref = np.array([
3.1750587329e-01,
2.1417517159e-02,
2.3609322038e-02,
1.2565683360e-03,
8.3014209421e-07,
-3.5073773473e-05,
6.7936409056e-06
])
assert_array_almost_equal(whu, ref)
def test_weighted_moments():
wm = regionprops(SAMPLE, ['WeightedMoments'], INTENSITY_SAMPLE
)[0]['WeightedMoments']
ref = np.array(
[[ 7.4000000000e+01, 4.1000000000e+02, 2.7500000000e+03,
1.9778000000e+04],
[ 6.9900000000e+02, 3.7850000000e+03, 2.4855000000e+04,
1.7500100000e+05],
[ 7.8630000000e+03, 4.4063000000e+04, 2.9347700000e+05,
2.0810510000e+06],
[ 9.7317000000e+04, 5.7256700000e+05, 3.9007170000e+06,
2.8078871000e+07]]
)
assert_array_almost_equal(wm, ref)
def test_weighted_normalized_moments():
wnu = regionprops(SAMPLE, ['WeightedNormalizedMoments'], INTENSITY_SAMPLE
)[0]['WeightedNormalizedMoments']
ref = np.array(
[[ np.nan, np.nan, 0.0873590903, -0.0161217406],
[ np.nan, -0.0160405109, -0.0031421072, -0.0031376984],
[ 0.230146783, 0.0457932622, 0.0165315478, 0.0043903193],
[-0.0162529732, -0.0104598869, -0.0028544152, -0.0011057191]]
)
assert_array_almost_equal(wnu, ref)
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()
@@ -0,0 +1,58 @@
import numpy as np
from numpy.testing import assert_equal
from skimage.measure import structural_similarity as ssim
import scipy.optimize as opt
def test_ssim_patch_range():
N = 51
X = (np.random.random((N, N)) * 255).astype(np.uint8)
Y = (np.random.random((N, N)) * 255).astype(np.uint8)
assert(ssim(X, Y, win_size=N) < 0.1)
assert_equal(ssim(X, X, win_size=N), 1)
def test_ssim_image():
N = 100
X = (np.random.random((N, N)) * 255).astype(np.uint8)
Y = (np.random.random((N, N)) * 255).astype(np.uint8)
S0 = ssim(X, X, win_size=3)
assert_equal(S0, 1)
S1 = ssim(X, Y, win_size=3)
assert(S1 < 0.3)
## Come up with a better way of testing the gradient
##
## def test_ssim_grad():
## N = 30
## X = np.random.random((N, N)) * 255
## Y = np.random.random((N, N)) * 255
## def func(Y):
## return ssim(X, Y, dynamic_range=255)
## def grad(Y):
## return ssim(X, Y, dynamic_range=255, gradient=True)[1]
## assert(np.all(opt.check_grad(func, grad, Y) < 0.05))
def test_ssim_dtype():
N = 30
X = np.random.random((N, N))
Y = np.random.random((N, N))
S1 = ssim(X, Y)
X = (X * 255).astype(np.uint8)
Y = (X * 255).astype(np.uint8)
S2 = ssim(X, Y)
assert S1 < 0.1
assert S2 < 0.1
if __name__ == "__main__":
np.testing.run_module_suite()
+3
View File
@@ -155,6 +155,9 @@ def label(np.ndarray[DTYPE_t, ndim=2] input,
raise ValueError('Neighbors must be either 4 or 8.')
# Initialize the first row
if data[0, 0] == background:
link_bg(forest_p, 0, &background_node)
for j in range(1, cols):
if data[0, j] == background:
link_bg(forest_p, j, &background_node)
+21
View File
@@ -61,5 +61,26 @@ class TestConnectedComponents:
[0, 0, 1],
[-1, -1, -1]])
def test_background_two_regions(self):
x = np.array([[0, 0, 6],
[0, 0, 6],
[5, 5, 5]])
assert_array_equal(label(x, background=0),
[[-1, -1, 0],
[-1, -1, 0],
[ 1, 1, 1]])
def test_background_one_region_center(self):
x = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
assert_array_equal(label(x, neighbors=4, background=0),
[[-1, -1, -1],
[-1, 0, -1],
[-1, -1, -1]])
if __name__ == "__main__":
run_module_suite()
+2
View File
@@ -4,3 +4,5 @@ from .finite_radon_transform import *
from .project import *
from ._project import homography as fast_homography
from .integral import *
from ._warp import warp
from ._warp_zoo import swirl
+113
View File
@@ -0,0 +1,113 @@
__all__ = ['warp']
import numpy as np
from scipy import ndimage
from skimage.util import img_as_float
def _stackcopy(a, b):
"""Copy b into each color layer of a, such that::
a[:,:,0] = a[:,:,1] = ... = b
Parameters
----------
a : (M, N) or (M, N, P) ndarray
Target array.
b : (M, N)
Source array.
Notes
-----
Color images are stored as an ``MxNx3`` or ``MxNx4`` arrays.
"""
if a.ndim == 3:
a[:] = b[:, :, np.newaxis]
else:
a[:] = b
def warp(image, reverse_map, map_args={},
output_shape=None, order=1, mode='constant', cval=0.):
"""Warp an image according to a given coordinate transformation.
Parameters
----------
image : 2-D array
Input image.
reverse_map : callable xy = f(xy, **kwargs)
Reverse coordinate map. A function that transforms a Px2 array of
``(x, y)`` coordinates in the *output image* into their corresponding
coordinates in the *source image*. Also see examples below.
map_args : dict, optional
Keyword arguments passed to `reverse_map`.
output_shape : tuple (rows, cols)
Shape of the output image generated.
order : int
Order of splines used in interpolation. See
`scipy.ndimage.map_coordinates` for detail.
mode : string
How to handle values outside the image borders. See
`scipy.ndimage.map_coordinates` for detail.
cval : string
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Examples
--------
Shift an image to the right:
>>> from skimage import data
>>> image = data.camera()
>>>
>>> def shift_right(xy):
... xy[:, 0] -= 10
... return xy
>>>
>>> warp(image, shift_right)
"""
if image.ndim < 2:
raise ValueError("Input must have more than 1 dimension.")
image = np.atleast_3d(img_as_float(image))
ishape = np.array(image.shape)
bands = ishape[2]
if output_shape is None:
output_shape = ishape
coords = np.empty(np.r_[3, output_shape], dtype=float)
## Construct transformed coordinates
rows, cols = output_shape[:2]
# Reshape grid coordinates into a (P, 2) array of (x, y) pairs
tf_coords = np.indices((cols, rows), dtype=float).reshape(2, -1).T
# Map each (x, y) pair to the source image according to
# the user-provided mapping
tf_coords = reverse_map(tf_coords, **map_args)
# Reshape back to a (2, M, N) coordinate grid
tf_coords = tf_coords.T.reshape((-1, cols, rows)).swapaxes(1, 2)
# Place the y-coordinate mapping
_stackcopy(coords[1, ...], tf_coords[0, ...])
# Place the x-coordinate mapping
_stackcopy(coords[0, ...], tf_coords[1, ...])
# colour-coordinate mapping
coords[2, ...] = range(bands)
# Prefilter not necessary for order 1 interpolation
prefilter = order > 1
mapped = ndimage.map_coordinates(image, coords, prefilter=prefilter,
mode=mode, order=order, cval=cval)
# The spline filters sometimes return results outside [0, 1],
# so clip to ensure valid data
return np.clip(mapped.squeeze(), 0, 1)
+74
View File
@@ -0,0 +1,74 @@
from __future__ import division
import numpy as np
from ._warp import warp
def _swirl_mapping(xy, center, rotation, strength, radius):
x, y = xy.T
x0, y0 = center
rho = np.sqrt((x - x0)**2 + (y - y0)**2)
# Ensure that the transformation decays to approximately 1/1000-th
# within the specified radius.
radius = radius / 5 * np.log(2)
theta = rotation + strength * \
np.exp(-rho / radius) + \
np.arctan2(y - y0, x - x0)
xy[..., 0] = x0 + rho * np.cos(theta)
xy[..., 1] = y0 + rho * np.sin(theta)
return xy
def swirl(image, center=None, strength=1, radius=100, rotation=0,
output_shape=None, order=1, mode='constant', cval=0):
"""Perform a swirl transformation.
Parameters
----------
image : ndarray
Input image.
center : (x,y) tuple or (2,) ndarray
Center coordinate of transformation.
strength : float
The amount of swirling applied.
radius : float
The extent of the swirl in pixels. The effect dies out
rapidly beyond `radius`.
rotation : float
Additional rotation applied to the image.
Returns
-------
swirled : ndarray
Swirled version of the input.
Other parameters
----------------
output_shape : tuple or ndarray
Size of the generated output image.
order : int
Order of splines used in interpolation. See
`scipy.ndimage.map_coordinates` for detail.
mode : string
How to handle values outside the image borders. See
`scipy.ndimage.map_coordinates` for detail.
cval : string
Used in conjunction with mode 'constant', the value outside
the image boundaries.
"""
if center is None:
center = np.array(image.shape)[:2] / 2
warp_args = {'center': center,
'rotation': rotation,
'strength': strength,
'radius': radius}
return warp(image, _swirl_mapping, map_args=warp_args,
output_shape=output_shape,
order=order, mode=mode, cval=cval)
+3 -7
View File
@@ -4,18 +4,12 @@
import numpy as np
from scipy.ndimage import interpolation as ndii
from ._warp import _stackcopy
__all__ = ['homography']
eps = np.finfo(float).eps
def _stackcopy(a, b):
"""a[:,:,0] = a[:,:,1] = ... = b"""
if a.ndim == 3:
a.transpose().swapaxes(1, 2)[:] = b
else:
a[:] = b
def homography(image, H, output_shape=None, order=1,
mode='constant', cval=0.):
"""Perform a projective transformation (homography) on an image.
@@ -106,6 +100,8 @@ def homography(image, H, output_shape=None, order=1,
coords = np.empty(np.r_[3, output_shape], dtype=float)
# TODO: Refactor this method to use transform.warp instead.
# Construct transformed coordinates
rows, cols = output_shape[:2]
rows, cols = np.mgrid[:rows, :cols]
+21 -21
View File
@@ -1,23 +1,23 @@
"""
radon.py - Radon and inverse radon transforms
Based on code of Justin K. Romberg
Based on code of Justin K. Romberg
(http://www.clear.rice.edu/elec431/projects96/DSP/bpanalysis.html)
J. Gillam and Chris Griffin.
References:
References:
-B.R. Ramesh, N. Srinivasa, K. Rajgopal, "An Algorithm for Computing
the Discrete Radon Transform With Some Applications", Proceedings of
the Fourth IEEE Region 10 International Conference, TENCON '89, 1989.
-A. C. Kak, Malcolm Slaney, "Principles of Computerized Tomographic
Imaging", IEEE Press 1988.
"""
from __future__ import division
import numpy as np
from scipy.fftpack import fftshift, fft, ifft
from ._project import homography
__all__ = ["radon", "iradon"]
__all__ = ["radon", "iradon"]
def radon(image, theta=None):
@@ -31,7 +31,7 @@ def radon(image, theta=None):
Input image.
theta : array_like, dtype=float, optional (default np.arange(180))
Projection angles (in degrees).
Returns
-------
output : ndarray
@@ -41,7 +41,7 @@ def radon(image, theta=None):
if image.ndim != 2:
raise ValueError('The input image must be 2-D')
if theta == None:
theta = np.arange(180)
theta = np.arange(180)
height, width = image.shape
diagonal = np.sqrt(height ** 2 + width ** 2)
heightpad = np.ceil(diagonal - height)
@@ -57,7 +57,7 @@ def radon(image, theta=None):
out = np.zeros((max(padded_image.shape), len(theta)))
h, w = padded_image.shape
dh, dw = h / 2, w / 2
dh, dw = h // 2, w // 2
shift0 = np.array([[1, 0, -dw],
[0, 1, -dh],
[0, 0, 1]])
@@ -92,7 +92,7 @@ def iradon(radon_image, theta=None, output_size=None,
Reconstruct an image from the radon transform, using the filtered
back projection algorithm.
Parameters
----------
radon_image : array_like, dtype=float
@@ -110,7 +110,7 @@ def iradon(radon_image, theta=None, output_size=None,
interpolation : str, optional (default linear)
Interpolation method used in reconstruction.
Methods available: nearest, linear.
Returns
-------
output : ndarray
@@ -121,19 +121,19 @@ def iradon(radon_image, theta=None, output_size=None,
It applies the fourier slice theorem to reconstruct an image by
multiplying the frequency domain of the filter with the FFT of the
projection data. This algorithm is called filtered back projection.
"""
if radon_image.ndim != 2:
raise ValueError('The input image must be 2-D')
if theta == None:
m, n = radon_image.shape
theta = np.linspace(0, 180, n, endpoint=False)
th = (np.pi / 180.0) * theta
th = (np.pi / 180.0) * theta
# if output size not specified, estimate from input radon image
if not output_size:
output_size = int(np.floor(np.sqrt((radon_image.shape[0]) ** 2 / 2.0)))
n = radon_image.shape[0]
img = radon_image.copy()
# resize image to next power of two for fourier analysis
# speeds up fourier and lessens artifacts
@@ -142,7 +142,7 @@ def iradon(radon_image, theta=None, output_size=None,
img.resize((order, img.shape[1]))
# construct the fourier filter
freqs = np.zeros((order, 1))
f = fftshift(abs(np.mgrid[-1:1:2 / order])).reshape(-1, 1)
w = 2 * np.pi * f
# start from first element to avoid divide by zero
@@ -160,8 +160,8 @@ def iradon(radon_image, theta=None, output_size=None,
f[1:] = 1
else:
raise ValueError("Unknown filter: %s" % filter)
filter_ft = np.tile(f, (1, len(theta)))
filter_ft = np.tile(f, (1, len(theta)))
# apply filter in fourier domain
projection = fft(img, axis=0) * filter_ft
radon_filtered = np.real(ifft(projection, axis=0))
@@ -169,15 +169,15 @@ def iradon(radon_image, theta=None, output_size=None,
radon_filtered = radon_filtered[:radon_image.shape[0], :]
reconstructed = np.zeros((output_size, output_size))
mid_index = np.ceil(n / 2.0)
x = output_size
y = output_size
[X, Y] = np.mgrid[0.0:x, 0.0:y]
xpr = X - int(output_size) / 2
ypr = Y - int(output_size) / 2
xpr = X - int(output_size) // 2
ypr = Y - int(output_size) // 2
# reconstruct image by interpolation
if interpolation == "nearest":
if interpolation == "nearest":
for i in range(len(theta)):
k = np.round(mid_index + xpr * np.sin(th[i]) - ypr * np.cos(th[i]))
reconstructed += radon_filtered[
@@ -186,12 +186,12 @@ def iradon(radon_image, theta=None, output_size=None,
for i in range(len(theta)):
t = xpr*np.sin(th[i]) - ypr*np.cos(th[i])
a = np.floor(t)
b = mid_index + a
b = mid_index + a
b0 = ((((b + 1 > 0) & (b + 1 < n)) * (b + 1)) - 1).astype(np.int)
b1 = ((((b > 0) & (b < n)) * b) - 1).astype(np.int)
reconstructed += (t - a) * radon_filtered[b0, i] + \
(a - t + 1) * radon_filtered[b1, i]
else:
raise ValueError("Unknown interpolation: %s" % interpolation)
return reconstructed * np.pi / (2 * len(th))
+1 -1
View File
@@ -1,7 +1,7 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
from skimage.transform.project import _stackcopy
from skimage.transform._warp import _stackcopy
from skimage.transform import homography, fast_homography
from skimage import data
from skimage.color import rgb2gray
+17
View File
@@ -0,0 +1,17 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
from skimage import transform as tf, data, img_as_float
def test_roundtrip():
image = img_as_float(data.checkerboard())
swirl_params = {'radius': 80, 'rotation': 0, 'order': 2, 'mode': 'reflect'}
swirled = tf.swirl(image, strength=10, **swirl_params)
unswirled = tf.swirl(swirled, strength=-10, **swirl_params)
assert np.mean(np.abs(image - unswirled)) < 0.01
if __name__ == "__main__":
np.testing.run_module_suite()
+1
View File
@@ -1 +1,2 @@
from .dtype import *
from .shape import *
+145 -79
View File
@@ -10,8 +10,8 @@ dtype_range = {np.uint8: (0, 255),
np.uint16: (0, 65535),
np.int8: (-128, 127),
np.int16: (-32768, 32767),
np.float32: (0, 1),
np.float64: (0, 1)}
np.float32: (-1, 1),
np.float64: (-1, 1)}
integer_types = (np.uint8, np.uint16, np.int8, np.int16)
@@ -20,19 +20,24 @@ _supported_types = (np.uint8, np.uint16, np.uint32,
np.float32, np.float64)
if np.__version__ >= "1.6.0":
dtype_range[np.float16] = (0, 1)
dtype_range[np.float16] = (-1, 1)
_supported_types += (np.float16, )
def convert(image, dtype, force_copy=False):
def convert(image, dtype, force_copy=False, uniform=False):
"""
Convert an image to the requested data-type.
Warnings are issued in case of precision loss, or when
negative values have to be scaled into the positive domain.
Floating point values must be in the range [0.0, 1.0].
Warnings are issued in case of precision loss, or when negative values
are clipped during conversion to unsigned integer types (sign loss).
Floating point values are expected to be normalized and will be clipped
to the range [0.0, 1.0] or [-1.0, 1.0] when converting to unsigned or
signed integers respectively.
Numbers are not shifted to the negative side when converting from
floating point or unsigned integer types to signed integer types.
unsigned to signed integer types. Negative values will be clipped when
converting to unsigned integers.
Parameters
----------
@@ -42,17 +47,29 @@ def convert(image, dtype, force_copy=False):
Target data-type.
force_copy : bool
Force a copy of the data, irrespective of its current dtype.
uniform : bool
Uniformly quantize the floating point range to the integer range.
By default (uniform=False) floating point values are scaled and
rounded to the nearest integers, which minimizes back and forth
conversion errors.
References
----------
(1) DirectX data conversion rules.
http://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx
(2) Data Conversions.
In "OpenGL ES 2.0 Specification v2.0.25", pp 7-8. Khronos Group, 2010.
(3) Proper treatment of pixels as integers. A.W. Paeth.
In "Graphics Gems I", pp 249-256. Morgan Kaufmann, 1990.
(4) Dirty Pixels. J. Blinn.
In "Jim Blinn's corner: Dirty Pixels", pp 47-57. Morgan Kaufmann, 1998.
"""
image = np.asarray(image)
dtype = np.dtype(dtype).type
dtype_in = image.dtype.type
dtypeobj = np.dtype(dtype)
dtypeobj_in = np.dtype(dtype_in)
kind = dtypeobj.kind
kind_in = dtypeobj_in.kind
itemsize = dtypeobj.itemsize
itemsize_in = dtypeobj_in.itemsize
dtypeobj_in = image.dtype
dtype = dtypeobj.type
dtype_in = dtypeobj_in.type
if dtype_in == dtype:
if force_copy:
@@ -74,93 +91,142 @@ def convert(image, dtype, force_copy=False):
# Return first of `dtypes` with itemsize greater than `itemsize`
return next(dt for dt in dtypes if itemsize < np.dtype(dt).itemsize)
def _dtype2(kind, bits, itemsize=1):
# Return dtype of `kind` that can store a `bits` wide unsigned int
c = lambda x, y: x <= y if kind == 'u' else x < y
s = next(i for i in (itemsize, ) + (2, 4, 8) if c(bits, i*8))
return np.dtype(kind + str(s))
def _scale(a, n, m, copy=True):
# Scale unsigned/positive integers from n to m bits
# Numbers can be represented exactly only if m is a multiple of n
# Output array is of same kind as input.
kind = a.dtype.kind
if n == m:
return a.copy() if copy else a
elif n > m:
# downscale with precision loss
prec_loss()
if copy:
b = np.empty(a.shape, _dtype2(kind, m))
np.floor_divide(a, 2**(n - m), out=b, dtype=a.dtype,
casting='unsafe')
return b
else:
a //= 2**(n - m)
return a
elif m % n == 0:
# exact upscale to a multiple of n bits
if copy:
b = np.empty(a.shape, _dtype2(kind, m))
np.multiply(a, (2**m - 1) // (2**n - 1), out=b, dtype=b.dtype)
return b
else:
a = np.array(a, _dtype2(kind, m, a.dtype.itemsize), copy=False)
a *= (2**m - 1) // (2**n - 1)
return a
else:
# upscale to a multiple of n bits,
# then downscale with precision loss
prec_loss()
o = (m // n + 1) * n
if copy:
b = np.empty(a.shape, _dtype2(kind, o))
np.multiply(a, (2**o - 1) // (2**n - 1), out=b, dtype=b.dtype)
b //= 2**(o - m)
return b
else:
a = np.array(a, _dtype2(kind, o, a.dtype.itemsize), copy=False)
a *= (2**o - 1) // (2**n - 1)
a //= 2**(o - m)
return a
kind = dtypeobj.kind
kind_in = dtypeobj_in.kind
itemsize = dtypeobj.itemsize
itemsize_in = dtypeobj_in.itemsize
if kind in 'ui':
imin = np.iinfo(dtype).min
imax = np.iinfo(dtype).max
if kind_in in 'ui':
imin_in = np.iinfo(dtype_in).min
imax_in = np.iinfo(dtype_in).max
if kind_in == 'f':
if np.min(image) < 0 or np.max(image) > 1:
raise ValueError("Images of type float must be between 0 and 1")
if np.min(image) < -1.0 or np.max(image) > 1.0:
raise ValueError("Images of type float must be between -1 and 1.")
if kind == 'f':
# floating point -> floating point
if itemsize_in > itemsize:
prec_loss()
return dtype(image)
# floating point -> integer
prec_loss()
# use float type that can represent output integer type
image = np.array(image, _dtype(itemsize, dtype_in,
np.float32, np.float64))
image *= np.iinfo(dtype).max + 1
np.clip(image, 0, np.iinfo(dtype).max, out=image)
if not uniform:
if kind == 'u':
image *= imax
else:
image *= imax - imin
image -= 1.0
image /= 2.0
np.rint(image, out=image)
np.clip(image, imin, imax, out=image)
elif kind == 'u':
image *= imax + 1
np.clip(image, 0, imax, out=image)
else:
image *= (imax - imin + 1.0) / 2.0
np.floor(image, out=image)
np.clip(image, imin, imax, out=image)
return dtype(image)
if kind == 'f':
# integer -> floating point
if itemsize_in >= itemsize:
prec_loss()
# use float type that can represent input integers
# use float type that can exactly represent input integers
image = np.array(image, _dtype(itemsize_in, dtype,
np.float32, np.float64))
if np.iinfo(dtype_in).min:
sign_loss()
image -= np.iinfo(dtype_in).min
image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min
return dtype(image)
if kind_in == 'u':
# unsigned integer -> integer
shift = 1 if kind == 'i' else 0
if itemsize_in > itemsize:
prec_loss()
image = image >> 8 * (itemsize_in - itemsize) + shift
return dtype(image)
result = dtype(image)
result <<= 8 * (itemsize - itemsize_in) - shift
if itemsize - itemsize_in == 3:
# uint8 -> (u)int32
# hint: 4294967295 == (255 << 24) + (255 << 16) + (255 << 8) + 255
image = dtype(image)
image *= 2**16 + 2**8 + 1
if shift:
result += image >> shift
if kind_in == 'u':
image /= imax_in
# DirectX uses this conversion also for signed ints
#if imin_in:
# np.maximum(image, -1.0, out=image)
else:
result += image
return dtype(result)
image *= 2.0
image += 1.0
image /= imax_in - imin_in
return dtype(image)
if kind_in == 'u':
if kind == 'i':
# unsigned integer -> signed integer
image = _scale(image, 8*itemsize_in, 8*itemsize-1)
return image.view(dtype)
else:
# unsigned integer -> unsigned integer
return _scale(image, 8*itemsize_in, 8*itemsize)
if kind == 'u':
# signed integer -> unsigned integer
sign_loss()
# use next higher precision signed integer type
image = np.array(image, _dtype(itemsize_in,
np.int16, np.int32, np.int64))
image -= np.iinfo(dtype_in).min
if itemsize_in == itemsize:
return dtype(image)
if itemsize_in > itemsize:
prec_loss()
image >>= 8 * (itemsize_in - itemsize)
return dtype(image)
result = dtype(image)
result <<= 8 * (itemsize - itemsize_in)
if itemsize - itemsize_in == 3:
# int8 -> uint32
image = dtype(image)
image *= 2**16 + 2**8 + 1
result += dtype(image)
image = _scale(image, 8*itemsize_in-1, 8*itemsize)
result = np.empty(image.shape, dtype)
np.maximum(image, 0, out=result, dtype=image.dtype, casting='unsafe')
return result
if kind == 'i':
# signed integer -> signed integer
if itemsize_in > itemsize:
prec_loss()
return dtype(image // 2**(8 * (itemsize_in - itemsize)))
# use next higher precision signed integer type
image = np.array(image, _dtype(itemsize_in,
np.int16, np.int32, np.int64))
image -= np.iinfo(dtype_in).min
# use next higher precision signed integer type
result = np.array(image, _dtype(image.itemsize, np.int32, np.int64))
result *= 2**(8 * (itemsize - itemsize_in))
if itemsize - itemsize_in == 3:
# int8 -> int32
image = dtype(image)
image *= 2**16 + 2**8 + 1
result += image
result += np.iinfo(dtype).min
return dtype(result)
# signed integer -> signed integer
if itemsize_in > itemsize:
return _scale(image, 8*itemsize_in-1, 8*itemsize-1)
image = image.astype(_dtype2('i', itemsize*8))
image -= imin_in
image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False)
image += imin
return dtype(image)
def img_as_float(image, force_copy=False):
+13 -12
View File
@@ -9,12 +9,13 @@ dtype_range = {np.uint8: (0, 255),
np.uint16: (0, 65535),
np.int8: (-128, 127),
np.int16: (-32768, 32767),
np.float32: (0, 1),
np.float64: (0, 1)}
np.float32: (-1.0, 1.0),
np.float64: (-1.0, 1.0)}
def _verify_range(msg, x, vmin, vmax):
def _verify_range(msg, x, vmin, vmax, dtype):
assert_equal(x[0], vmin)
assert_equal(x[-1], vmax)
assert x.dtype == dtype
def test_range():
for dtype in dtype_range:
@@ -29,12 +30,13 @@ def test_range():
omin, omax = dtype_range[dt]
if imin == 0:
if imin == 0 or omin == 0:
omin = 0
imin = 0
yield _verify_range, \
"From %s to %s" % (np.dtype(dtype), np.dtype(dt)), \
y, omin, omax
yield (_verify_range,
"From %s to %s" % (np.dtype(dtype), np.dtype(dt)),
y, omin, omax, np.dtype(dt))
def test_range_extra_dtypes():
@@ -57,9 +59,9 @@ def test_range_extra_dtypes():
x = np.linspace(imin, imax, 10).astype(dtype_in)
y = convert(x, dt)
omin, omax = dtype_range_extra[dt]
yield _verify_range, \
"From %s to %s" % (np.dtype(dtype_in), np.dtype(dt)), \
y, omin, omax
yield (_verify_range,
"From %s to %s" % (np.dtype(dtype_in), np.dtype(dt)),
y, omin, omax, np.dtype(dt))
def test_unsupported_dtype():
@@ -70,7 +72,7 @@ def test_unsupported_dtype():
def test_float_out_of_range():
too_high = np.array([2], dtype=np.float32)
assert_raises(ValueError, img_as_int, too_high)
too_low = np.array([-1], dtype=np.float32)
too_low = np.array([-2], dtype=np.float32)
assert_raises(ValueError, img_as_int, too_low)
@@ -84,4 +86,3 @@ def test_copy():
if __name__ == '__main__':
np.testing.run_module_suite()