Merged with master

This commit is contained in:
Michael Hansen
2014-03-17 08:40:58 -04:00
170 changed files with 11164 additions and 3458 deletions
+4
View File
@@ -7,3 +7,7 @@ include = */skimage/*
omit =
*/setup.py
[report]
exclude_lines =
def __repr__
if __name__ == .__main__.:
+10 -3
View File
@@ -63,8 +63,12 @@ script:
- "echo 'backend.qt4 : PyQt4' >> $HOME/.matplotlib/matplotlibrc"
# Run all tests
- nosetests --exe -v --with-doctest --with-cov --cov skimage --cov-config .coveragerc skimage
- if [[ $PYVER == '3.x' ]]; then
- nosetests --exe -v --with-doctest --with-cov --cov skimage --cov-config .coveragerc skimage
- fi
- if [[ $PYVER == '2.x' ]]; then
- nosetests --exe -v --with-doctest skimage
- fi
# Run all doc examples
- export PYTHONPATH=$(pwd):$PYTHONPATH
- for f in doc/examples/*.py; do $PYTHON "$f"; if [ $? -ne 0 ]; then exit 1; fi done
@@ -74,4 +78,7 @@ script:
- flake8 --exit-zero --exclude=test_*,six.py skimage doc/examples viewer_examples
after_success:
- coveralls
- if [[ $PYVER == '3.x' ]]; then
- coveralls
- fi
+24 -3
View File
@@ -144,14 +144,14 @@
Color separation (color deconvolution) for several stainings.
- Jostein Bø Fløystad
Reconstruction circle mode for Radon transform
Simultaneous Algebraic Reconstruction Technique for inverse Radon transform
Tomography: radon/iradon improvements and SART implementation
Phase unwrapping integration
- Matt Terry
Color difference functions
- Eugene Dvoretsky
Yen threshold implementation.
Yen, Ridler-Calvard (ISODATA) threshold implementations.
- Riaan van den Dool
skimage.io plugin: GDAL
@@ -161,3 +161,24 @@
- Michael Hansen
novice submodule
- Munther Gdeisat
Phase unwrapping implementation
- Miguel Arevallilo Herraez
Phase unwrapping implementation
- Hussein Abdul-Rahman
Phase unwrapping implementation
- Gregor Thalhammer
Phase unwrapping integration
- François Orieux
Image deconvolution http://research.orieux.fr
- Vighnesh Birodkar
Blob Detection
- Axel Donath
Blob Detection
+15
View File
@@ -1,3 +1,15 @@
Version 0.11
------------
* Remove deprecated `reverse_map` parameter of `skimage.transform.warp`
* Change depecrated `enforce_connectivity=False`on skimage.segmentation.slic
and set it to True as default
* Remove deprecated `skimage.measure.fit.BaseModel._params` attribute
* Remove deprecated `skimage.measure.fit.BaseModel._params`,
`skimage.transform.ProjectiveTransform._matrix`,
`skimage.transform.PolynomialTransform._params`,
`skimage.transform.PiecewiseAffineTransform.affines_*` attributes
* Remove deprecated functions `skimage.filter.denoise_*`
Version 0.10
------------
* Remove deprecated functions in `skimage.filter.rank.*`
@@ -14,3 +26,6 @@ Version 0.10
* Remove deprecated `skimage.color.is_gray` and `skimage.color.is_rgb`
functions
* Enable doctests of experimental `skimage.feature.brief`
* Remove deprecated `skimage.segmentation.visualize_boundaries`
* Remove deprecated `skimage.morphology.greyscale_*`
* Remove deprecated `skimage.exposure.equalize`
+19 -12
View File
@@ -35,13 +35,11 @@ Library:
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
skimage.scripts, skimage.restoration, skimage.segmentation,
skimage.transform, skimage.util
Extension: skimage.morphology._pnpoly
Sources:
skimage/morphology/_pnpoly.pyx
Extension: skimage.feature._template
Sources:
skimage/feature/_template.pyx
Extension: skimage.io._plugins._colormixer
Sources:
skimage/io/_plugins/_colormixer.pyx
@@ -66,9 +64,6 @@ Library:
Extension: skimage.filter._ctmf
Sources:
skimage/filter/_ctmf.pyx
Extension: skimage.filter._denoise_cy
Sources:
skimage/filter/_denoise_cy.pyx
Extension: skimage.morphology.ccomp
Sources:
skimage/morphology/ccomp.pyx
@@ -96,9 +91,12 @@ Library:
Extension: skimage.feature.censure_cy
Sources:
skimage/feature/censure_cy.pyx
Extension: skimage.feature._brief_cy
Extension: skimage.feature.orb_cy
Sources:
skimage/feature/_brief_cy.pyx
skimage/feature/orb_cy.pyx
Extension: skimage.feature.brief_cy
Sources:
skimage/feature/brief_cy.pyx
Extension: skimage.feature.corner_cy
Sources:
skimage/feature/corner_cy.pyx
@@ -123,9 +121,6 @@ Library:
Extension: skimage.transform._warps_cy
Sources:
skimage/transform/_warps_cy.pyx
Extension: skimage._shared.interpolation
Sources:
skimage/_shared/interpolation.pyx
Extension: skimage.segmentation._felzenszwalb_cy
Sources:
skimage/segmentation/_felzenszwalb_cy.pyx
@@ -144,6 +139,18 @@ Library:
Extension: skimage.filter.rank.bilateral_cy
Sources:
skimage/filter/rank/bilateral_cy.pyx
Extension: skimage.restoration._unwrap_1d
Sources:
skimage/restoration/_unwrap_1d.pyx
Extension: skimage.restoration._unwrap_2d
Sources:
skimage/restoration/_unwrap_2d.pyx skimage/exposure/unwrap_2d_ljmu.c
Extension: skimage.restoration._unwrap_3d
Sources:
skimage/restoration/_unwrap_3d.pyx skimage/exposure/unwrap_3d_ljmu.c
Extension: skimage.restoration._denoise_cy
Sources:
skimage/restoration/_denoise_cy.pyx
Executable: skivi
Module: skimage.scripts.skivi
+2 -2
View File
@@ -33,14 +33,14 @@ First we create a transformation using explicit parameters:
tform = tf.SimilarityTransform(scale=1, rotation=math.pi / 2,
translation=(0, 1))
print(tform._matrix)
print(tform.params)
"""
Alternatively you can define a transformation by the transformation matrix
itself:
"""
matrix = tform._matrix.copy()
matrix = tform.params.copy()
matrix[1, 2] = 2
tform2 = tf.SimilarityTransform(matrix)
+61
View File
@@ -0,0 +1,61 @@
"""
=======================
BRIEF binary descriptor
=======================
This example demonstrates the BRIEF binary description algorithm.
The descriptor consists of relatively few bits and can be computed using
a set of intensity difference tests. The short binary descriptor results
in low memory footprint and very efficient matching based on the Hamming
distance metric.
BRIEF does not provide rotation-invariance. Scale-invariance can be achieved by
detecting and extracting features at different scales.
"""
from skimage import data
from skimage import transform as tf
from skimage.feature import (match_descriptors, corner_peaks, corner_harris,
plot_matches, BRIEF)
from skimage.color import rgb2gray
import matplotlib.pyplot as plt
img1 = rgb2gray(data.lena())
tform = tf.AffineTransform(scale=(1.2, 1.2), translation=(0, -100))
img2 = tf.warp(img1, tform)
img3 = tf.rotate(img1, 25)
keypoints1 = corner_peaks(corner_harris(img1), min_distance=5)
keypoints2 = corner_peaks(corner_harris(img2), min_distance=5)
keypoints3 = corner_peaks(corner_harris(img3), min_distance=5)
extractor = BRIEF()
extractor.extract(img1, keypoints1)
keypoints1 = keypoints1[extractor.mask]
descriptors1 = extractor.descriptors
extractor.extract(img2, keypoints2)
keypoints2 = keypoints2[extractor.mask]
descriptors2 = extractor.descriptors
extractor.extract(img3, keypoints3)
keypoints3 = keypoints3[extractor.mask]
descriptors3 = extractor.descriptors
matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True)
matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True)
fig, ax = plt.subplots(nrows=2, ncols=1)
plt.gray()
plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12)
ax[0].axis('off')
plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13)
ax[1].axis('off')
plt.show()
+43
View File
@@ -0,0 +1,43 @@
"""
========================
CENSURE feature detector
========================
The CENSURE feature detector is a scale-invariant center-surround detector
(CENSURE) that claims to outperform other detectors and is capable of real-time
implementation.
"""
from skimage import data
from skimage import transform as tf
from skimage.feature import CENSURE
from skimage.color import rgb2gray
import matplotlib.pyplot as plt
img1 = rgb2gray(data.lena())
tform = tf.AffineTransform(scale=(1.5, 1.5), rotation=0.5,
translation=(150, -200))
img2 = tf.warp(img1, tform)
detector = CENSURE()
fig, ax = plt.subplots(nrows=1, ncols=2)
plt.gray()
detector.detect(img1)
ax[0].imshow(img1)
ax[0].axis('off')
ax[0].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0],
2 ** detector.scales, facecolors='none', edgecolors='r')
detector.detect(img2)
ax[1].imshow(img2)
ax[1].axis('off')
ax[1].scatter(detector.keypoints[:, 1], detector.keypoints[:, 0],
2 ** detector.scales, facecolors='none', edgecolors='r')
plt.show()
@@ -48,7 +48,7 @@ from skimage.util import img_as_ubyte
image = img_as_ubyte(data.coins()[0:95, 70:370])
edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(5, 2))
# Detect two radii
hough_radii = np.arange(15, 30, 2)
@@ -77,6 +77,8 @@ ax.imshow(image, cmap=plt.cm.gray)
"""
.. image:: PLOT2RST.current_figure
Ellipse detection
=================
@@ -137,7 +139,7 @@ image_rgb[cy, cx] = (0, 0, 255)
edges = color.gray2rgb(edges)
edges[cy, cx] = (250, 0, 0)
fig2, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, figsize=(10, 6))
fig2, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, figsize=(8, 4))
ax1.set_title('Original picture')
ax1.imshow(image_rgb)
@@ -146,3 +148,8 @@ ax2.set_title('Edge (white) and result (red)')
ax2.imshow(edges)
plt.show()
"""
.. image:: PLOT2RST.current_figure
"""
+1 -1
View File
@@ -29,7 +29,7 @@ import numpy as np
import matplotlib.pyplot as plt
from skimage import data, img_as_float
from skimage.filter import denoise_tv_chambolle, denoise_bilateral
from skimage.restoration import denoise_tv_chambolle, denoise_bilateral
lena = img_as_float(data.lena())
+7 -3
View File
@@ -17,6 +17,8 @@ that fall within the 2nd and 98th percentiles [2]_.
.. [2] http://homepages.inf.ed.ac.uk/rbf/HIPR2/stretch.htm
"""
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
@@ -24,6 +26,9 @@ from skimage import data, img_as_float
from skimage import exposure
matplotlib.rcParams['font.size'] = 8
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
@@ -55,8 +60,7 @@ def plot_img_and_hist(img, axes, bins=256):
img = data.moon()
# Contrast stretching
p2 = np.percentile(img, 2)
p98 = np.percentile(img, 98)
p2, p98 = np.percentile(img, (2, 98))
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
# Equalization
@@ -66,7 +70,7 @@ img_eq = exposure.equalize_hist(img)
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)
# Display results
f, axes = plt.subplots(2, 4, figsize=(8, 4))
f, axes = plt.subplots(nrows=2, ncols=4, figsize=(8, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
+4 -7
View File
@@ -24,9 +24,6 @@ from skimage.util import img_as_float
from skimage.filter import gabor_kernel
matplotlib.rcParams['font.size'] = 9
def compute_feats(image, kernels):
feats = np.zeros((len(kernels), 2), dtype=np.double)
for k, kernel in enumerate(kernels):
@@ -104,24 +101,24 @@ for theta in (0, 1):
# Save kernel and the power image for each image
results.append((kernel, [power(img, kernel) for img in images]))
fig, axes = plt.subplots(nrows=5, ncols=4, figsize=(9, 6))
fig, axes = plt.subplots(nrows=5, ncols=4, figsize=(5, 6))
plt.gray()
fig.suptitle('Image responses for Gabor filter kernels', fontsize=15)
fig.suptitle('Image responses for Gabor filter kernels', fontsize=12)
axes[0][0].axis('off')
# Plot original images
for label, img, ax in zip(image_names, images, axes[0][1:]):
ax.imshow(img)
ax.set_title(label)
ax.set_title(label, fontsize=9)
ax.axis('off')
for label, (kernel, powers), ax_row in zip(kernel_params, results, axes[1:]):
# Plot Gabor kernel
ax = ax_row[0]
ax.imshow(np.real(kernel), interpolation='nearest')
ax.set_ylabel(label)
ax.set_ylabel(label, fontsize=7)
ax.set_xticks([])
ax.set_yticks([])
+1 -1
View File
@@ -43,7 +43,7 @@ image_label_overlay = label2rgb(label_image, image=image)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
ax.imshow(image_label_overlay)
for region in regionprops(label_image, ['Area', 'BoundingBox']):
for region in regionprops(label_image):
# skip small images
if region['Area'] < 100:
+6 -4
View File
@@ -20,6 +20,7 @@ References
"""
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from skimage import data
@@ -30,6 +31,9 @@ from skimage.morphology import disk
from skimage.filter import rank
matplotlib.rcParams['font.size'] = 9
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
@@ -59,9 +63,7 @@ def plot_img_and_hist(img, axes, bins=256):
# Load an example image
img = img_as_ubyte(data.moon())
# Contrast stretching
p2 = np.percentile(img, 2)
p98 = np.percentile(img, 98)
# Global equalize
img_rescale = exposure.equalize_hist(img)
# Equalization
@@ -70,7 +72,7 @@ img_eq = rank.equalize(img, selem=selem)
# Display results
f, axes = plt.subplots(2, 3, figsize=(8, 4))
f, axes = plt.subplots(2, 3, figsize=(8, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
+29 -16
View File
@@ -15,6 +15,7 @@ The example compares the local threshold with the global threshold.
.. [1] http://en.wikipedia.org/wiki/Otsu's_method
"""
import matplotlib
import matplotlib.pyplot as plt
from skimage import data
@@ -23,29 +24,41 @@ from skimage.filter import threshold_otsu, rank
from skimage.util import img_as_ubyte
p8 = img_as_ubyte(data.page())
matplotlib.rcParams['font.size'] = 9
radius = 10
img = img_as_ubyte(data.page())
radius = 15
selem = disk(radius)
loc_otsu = rank.otsu(p8, selem)
t_glob_otsu = threshold_otsu(p8)
glob_otsu = p8 >= t_glob_otsu
local_otsu = rank.otsu(img, selem)
threshold_global_otsu = threshold_otsu(img)
global_otsu = img >= threshold_global_otsu
plt.figure()
plt.figure(figsize=(8, 5))
plt.subplot(2, 2, 1)
plt.imshow(p8, cmap=plt.cm.gray)
plt.xlabel('original')
plt.colorbar()
plt.imshow(img, cmap=plt.cm.gray)
plt.title('Original')
plt.colorbar(orientation='horizontal')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(loc_otsu, cmap=plt.cm.gray)
plt.xlabel('local Otsu ($radius=%d$)' % radius)
plt.colorbar()
plt.imshow(local_otsu, cmap=plt.cm.gray)
plt.title('Local Otsu (radius=%d)' % radius)
plt.colorbar(orientation='horizontal')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(p8 >= loc_otsu, cmap=plt.cm.gray)
plt.xlabel('original >= local Otsu' % t_glob_otsu)
plt.imshow(img >= local_otsu, cmap=plt.cm.gray)
plt.title('Original >= Local Otsu' % threshold_global_otsu)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(glob_otsu, cmap=plt.cm.gray)
plt.xlabel('global Otsu ($t = %d$)' % t_glob_otsu)
plt.imshow(global_otsu, cmap=plt.cm.gray)
plt.title('Global Otsu (threshold = %d)' % threshold_global_otsu)
plt.axis('off')
plt.show()
+11 -17
View File
@@ -27,7 +27,8 @@ from matplotlib import pyplot as plt
from skimage import data
from skimage.util import img_as_float
from skimage.feature import corner_harris, corner_subpix, corner_peaks
from skimage.feature import (corner_harris, corner_subpix, corner_peaks,
plot_matches)
from skimage.transform import warp, AffineTransform
from skimage.exposure import rescale_intensity
from skimage.color import rgb2gray
@@ -117,28 +118,21 @@ print(tform.scale, tform.translation, tform.rotation)
print(model.scale, model.translation, model.rotation)
print(model_robust.scale, model_robust.translation, model_robust.rotation)
# visualize correspondences
img_combined = np.concatenate((img_orig_gray, img_warped_gray), axis=1)
# visualize correspondence
fig, ax = plt.subplots(nrows=2, ncols=1)
plt.gray()
ax[0].imshow(img_combined, interpolation='nearest')
inlier_idxs = np.nonzero(inliers)[0]
plot_matches(ax[0], img_orig_gray, img_warped_gray, src, dst,
np.column_stack((inlier_idxs, inlier_idxs)), matches_color='b')
ax[0].axis('off')
ax[0].axis((0, 400, 200, 0))
ax[0].set_title('Correct correspondences')
ax[1].imshow(img_combined, interpolation='nearest')
outlier_idxs = np.nonzero(outliers)[0]
plot_matches(ax[1], img_orig_gray, img_warped_gray, src, dst,
np.column_stack((outlier_idxs, outlier_idxs)), matches_color='r')
ax[1].axis('off')
ax[1].axis((0, 400, 200, 0))
ax[1].set_title('Faulty correspondences')
for ax_idx, (m, color) in enumerate(((inliers, 'g'), (outliers, 'r'))):
ax[ax_idx].plot((src[m, 1], dst[m, 1] + 200), (src[m, 0], dst[m, 0]), '-',
color=color)
ax[ax_idx].plot(src[m, 1], src[m, 0], '.', markersize=10, color=color)
ax[ax_idx].plot(dst[m, 1] + 200, dst[m, 0], '.', markersize=10,
color=color)
plt.show()
+56
View File
@@ -0,0 +1,56 @@
"""
==========================================
ORB feature detector and binary descriptor
==========================================
This example demonstrates the ORB feature detection and binary description
algorithm. It uses an oriented FAST detection method and the rotated BRIEF
descriptors.
Unlike BRIEF, ORB is comparatively scale- and rotation-invariant while still
employing the very efficient Hamming distance metric for matching. As such, it
is preferred for real-time applications.
"""
from skimage import data
from skimage import transform as tf
from skimage.feature import (match_descriptors, corner_harris,
corner_peaks, ORB, plot_matches)
from skimage.color import rgb2gray
import matplotlib.pyplot as plt
img1 = rgb2gray(data.lena())
img2 = tf.rotate(img1, 180)
tform = tf.AffineTransform(scale=(1.3, 1.1), rotation=0.5,
translation=(0, -200))
img3 = tf.warp(img1, tform)
descriptor_extractor = ORB(n_keypoints=200)
descriptor_extractor.detect_and_extract(img1)
keypoints1 = descriptor_extractor.keypoints
descriptors1 = descriptor_extractor.descriptors
descriptor_extractor.detect_and_extract(img2)
keypoints2 = descriptor_extractor.keypoints
descriptors2 = descriptor_extractor.descriptors
descriptor_extractor.detect_and_extract(img3)
keypoints3 = descriptor_extractor.keypoints
descriptors3 = descriptor_extractor.descriptors
matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True)
matches13 = match_descriptors(descriptors1, descriptors3, cross_check=True)
fig, ax = plt.subplots(nrows=2, ncols=1)
plt.gray()
plot_matches(ax[0], img1, img2, keypoints1, keypoints2, matches12)
ax[0].axis('off')
plot_matches(ax[1], img1, img3, keypoints1, keypoints3, matches13)
ax[1].axis('off')
plt.show()
+4
View File
@@ -14,12 +14,16 @@ the intra-class variance.
.. [1] http://en.wikipedia.org/wiki/Otsu's_method
"""
import matplotlib
import matplotlib.pyplot as plt
from skimage.data import camera
from skimage.filter import threshold_otsu
matplotlib.rcParams['font.size'] = 9
image = camera()
thresh = threshold_otsu(image)
binary = image > thresh
+116
View File
@@ -0,0 +1,116 @@
"""
================
Phase Unwrapping
================
Some signals can only be observed modulo 2*pi, and this can also apply to
two- and three dimensional images. In these cases phase unwrapping is
needed to recover the underlying, unwrapped signal. In this example we will
demonstrate an algorithm [1]_ implemented in ``skimage`` at work for such a
problem. One-, two- and three dimensional images can all be unwrapped using
skimage. Here we will demonstrate phase unwrapping in the two dimensional case.
"""
import numpy as np
from matplotlib import pyplot as plt
from skimage import data, img_as_float, color, exposure
from skimage.restoration import unwrap_phase
# Load an image as a floating-point grayscale
image = color.rgb2gray(img_as_float(data.chelsea()))
# Scale the image to [0, 4*pi]
image = exposure.rescale_intensity(image, out_range=(0, 4 * np.pi))
# Create a phase-wrapped image in the interval [-pi, pi)
image_wrapped = np.angle(np.exp(1j * image))
# Perform phase unwrapping
image_unwrapped = unwrap_phase(image_wrapped)
plt.figure()
plt.subplot(221)
plt.title('Original')
plt.imshow(image, cmap='gray', vmin=0, vmax=4 * np.pi)
plt.colorbar()
plt.subplot(222)
plt.title('Wrapped phase')
plt.imshow(image_wrapped, cmap='gray', vmin=-np.pi, vmax=np.pi)
plt.colorbar()
plt.subplot(223)
plt.title('After phase unwrapping')
plt.imshow(image_unwrapped, cmap='gray')
plt.colorbar()
plt.subplot(224)
plt.title('Unwrapped minus original')
plt.imshow(image_unwrapped - image, cmap='gray')
plt.colorbar()
"""
.. image:: PLOT2RST.current_figure
The unwrapping procedure accepts masked arrays, and can also optionally
assume cyclic boundaries to connect edges of an image. In the example below,
we study a simple phase ramp which has been split in two by masking
a row of the image.
"""
# Create a simple ramp
image = np.ones((100, 100)) * np.linspace(0, 8 * np.pi, 100).reshape((-1, 1))
# Mask the image to split it in two horizontally
mask = np.zeros_like(image, dtype=np.bool)
mask[image.shape[0] // 2, :] = True
image_wrapped = np.ma.array(np.angle(np.exp(1j * image)), mask=mask)
# Unwrap image without wrap around
image_unwrapped_no_wrap_around = unwrap_phase(image_wrapped,
wrap_around=(False, False))
# Unwrap with wrap around enabled for the 0th dimension
image_unwrapped_wrap_around = unwrap_phase(image_wrapped,
wrap_around=(True, False))
plt.figure()
plt.subplot(221)
plt.title('Original')
plt.imshow(np.ma.array(image, mask=mask), cmap='jet')
plt.colorbar()
plt.subplot(222)
plt.title('Wrapped phase')
plt.imshow(image_wrapped, cmap='jet', vmin=-np.pi, vmax=np.pi)
plt.colorbar()
plt.subplot(223)
plt.title('Unwrapped without wrap_around')
plt.imshow(image_unwrapped_no_wrap_around, cmap='jet')
plt.colorbar()
plt.subplot(224)
plt.title('Unwrapped with wrap_around')
plt.imshow(image_unwrapped_wrap_around, cmap='jet')
plt.colorbar()
plt.show()
"""
.. image:: PLOT2RST.current_figure
In the figures above, the masked row can be seen as a white line across
the image. The difference between the two unwrapped images in the bottom row
is clear: Without unwrapping (lower left), the regions above and below the
masked boundary do not interact at all, resulting in an offset between the
two regions of an arbitrary integer times two pi. We could just as well have
unwrapped the regions as two separate images. With wrap around enabled for the
vertical direction (lower rigth), the situation changes: Unwrapping paths are
now allowed to pass from the bottom to the top of the image and vice versa, in
effect providing a way to determine the offset between the two regions.
References
----------
.. [1] Miguel Arevallilo Herraez, David R. Burton, Michael J. Lalor,
and Munther A. Gdeisat, "Fast two-dimensional phase-unwrapping
algorithm based on sorting by reliability following a noncontinuous
path", Journal Applied Optics, Vol. 41, No. 35, pp. 7437, 2002
"""
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""
=====================
Deconvolution of Lena
=====================
In this example, we deconvolve a noisy version of Lena using Wiener
and unsupervised Wiener algorithms. This algorithms are based on
linear models that can't restore sharp edge as much as non-linear
methods (like TV restoration) but are much faster.
Wiener filter
-------------
The inverse filter based on the PSF (Point Spread Function),
the prior regularisation (penalisation of high frequency) and the
tradeoff between the data and prior adequacy. The regularization
parameter must be hand tuned.
Unsupervised Wiener
-------------------
This algorithm has a self-tuned regularisation parameters based on
data learning. This is not common and based on the following
publication. The algorithm is based on a iterative Gibbs sampler that
draw alternatively samples of posterior conditionnal law of the image,
the noise power and the image frequency power.
.. [1] François Orieux, Jean-François Giovannelli, and Thomas
Rodet, "Bayesian estimation of regularization and point
spread function parameters for Wiener-Hunt deconvolution",
J. Opt. Soc. Am. A 27, 1593-1607 (2010)
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import color, data, restoration
lena = color.rgb2gray(data.lena())
from scipy.signal import convolve2d as conv2
psf = np.ones((5, 5)) / 25
lena = conv2(lena, psf, 'same')
lena += 0.1 * lena.std() * np.random.standard_normal(lena.shape)
deconvolved, _ = restoration.unsupervised_wiener(lena, psf)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 5))
plt.gray()
ax[0].imshow(lena, vmin=deconvolved.min(), vmax=deconvolved.max())
ax[0].axis('off')
ax[0].set_title('Data')
ax[1].imshow(deconvolved)
ax[1].axis('off')
ax[1].set_title('Self tuned restoration')
fig.subplots_adjust(wspace=0.02, hspace=0.2,
top=0.9, bottom=0.05, left=0, right=1)
plt.show()
+11 -12
View File
@@ -4,11 +4,17 @@ Shapes
======
This example shows how to draw several different shapes:
* line
* Bezier curve
* polygon
* circle
* ellipse
- line
- Bezier curve
- polygon
- circle
- ellipse
Anti-aliased drawing for:
- line
- circle
"""
import math
@@ -69,13 +75,6 @@ ax1.imshow(img)
ax1.set_title('No anti-aliasing')
ax1.axis('off')
"""
Anti-aliased drawing for:
* line
* circle
"""
from skimage.draw import line_aa, circle_perimeter_aa
+5 -1
View File
@@ -22,12 +22,16 @@ but with very different mean structural similarity indices.
"""
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from skimage import data, img_as_float
from skimage.measure import structural_similarity as ssim
matplotlib.rcParams['font.size'] = 9
img = img_as_float(data.camera())
rows, cols = img.shape
@@ -41,7 +45,7 @@ def mse(x, y):
img_noise = img + noise
img_const = img + abs(noise)
f, (ax0, ax1, ax2) = plt.subplots(1, 3)
f, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, figsize=(8, 4))
mse_none = mse(img, img)
ssim_none = ssim(img, img, dynamic_range=img.max() - img.min())
Regular → Executable
+1 -1
View File
@@ -125,7 +125,7 @@ if __name__ == '__main__':
sh("touch .nojekyll")
sh('git add .nojekyll')
sh('git add index.html')
sh('git add %s' % tag)
sh('git add --all %s' % tag)
sh2('git commit -m"Updated doc release: %s"' % tag)
print('Most recent commit:')
+5 -1
View File
@@ -134,7 +134,11 @@ dtype range::
Here, the ``in_range`` argument is set to the maximum range for a 10-bit image.
By default, ``rescale_intensity`` stretches the values of ``in_range`` to match
the range of the dtype.
the range of the dtype. ``rescale_intensity`` also accepts strings as inputs
to ``in_range`` and ``out_range``, so the example above could also be written
as::
>>> image = exposure.rescale_intensity(img10bit, in_range='uint10')
Note about negative values
+8 -8
View File
@@ -24,26 +24,26 @@ alternatively, ``skimage.io.imshow`` which adds support for multiple
io-plugins) to display images. The advantage of ``ImageViewer`` is that you can
easily add plugins for manipulating images. Currently, only a few plugins are
implemented, but it is easy to write your own. Before going into the details,
let's see an example of how a plugin is added to the viewer:
let's see an example of how a pre-defined plugin is added to the viewer:
.. code-block:: python
from skimage.viewer.plugins import Canny
from skimage.viewer.plugins.lineprofile import LineProfile
viewer = ImageViewer(image)
viewer += Canny(view)
viewer += LineProfile(viewer)
viewer.show()
At the moment, there aren't very many plugins pre-defined, but there's a really
simple interface for creating your own plugin. First, let's create a plugin to
call the total-variation denoising function, ``tv_denoise``:
At the moment, there are not many plugins pre-defined, but there is a really
simple interface for creating your own plugin. First, let us create a plugin to
call the total-variation denoising function, ``denoise_tv_bregman``:
.. code-block:: python
from skimage.filter import tv_denoise
from skimage.filter import denoise_tv_bregman
from skimage.viewer.plugins.base import Plugin
denoise_plugin = Plugin(image_filter=tv_denoise)
denoise_plugin = Plugin(image_filter=denoise_tv_bregman)
.. note::
Regular → Executable
+5 -1
View File
@@ -21,10 +21,14 @@ VERSION = '0.10dev'
PYTHON_VERSION = (2, 5)
DEPENDENCIES = {
'numpy': (1, 6),
'Cython': (0, 17),
'six': (1, 3),
}
# Only require Cython if we have a developer checkout
if VERSION.endswith('dev'):
DEPENDENCIES['Cython'] = (0, 17)
import os
import sys
+4
View File
@@ -29,12 +29,16 @@ measure
Measurement of image properties, e.g., similarity and contours.
morphology
Morphological operations, e.g. opening or skeletonization.
restoration
Restoration algorithms.
segmentation
Splitting an image into self-similar regions.
transform
Geometric and other transforms, e.g. rotation or the Radon transform.
util
Generic utilities.
viewer
Interactive image viewer and plugin framework.
Utility Functions
-----------------
+3 -21
View File
@@ -3,7 +3,6 @@ import os
import hashlib
import subprocess
# WindowsError is not defined on unix systems
try:
WindowsError
@@ -26,7 +25,7 @@ def cython(pyx_files, working_path=''):
return
try:
import Cython
from Cython.Build import cythonize
except ImportError:
# If cython is not found, we do nothing -- the build will make use of
# the distributed .c files
@@ -39,24 +38,7 @@ def cython(pyx_files, working_path=''):
if not _changed(pyxfile):
continue
c_file = pyxfile[:-4] + '.c'
# run cython compiler
cmd = 'cython -o %s %s' % (c_file, pyxfile)
print(cmd)
try:
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.
subprocess.call(
[sys.executable,
os.path.join(os.path.dirname(sys.executable),
'Scripts', 'cython.py'),
'-o', c_file, pyxfile],
shell=True)
cythonize(pyxfile)
def _md5sum(f):
m = hashlib.new('md5')
@@ -86,4 +68,4 @@ def _changed(filename):
with open(filename_cache, 'wb') as cf:
cf.write(md5_new.encode('utf-8'))
return md5_cached != md5_new
return md5_cached != md5_new.encode('utf-8')
+324 -20
View File
@@ -1,27 +1,331 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
from libc.math cimport ceil, floor
cdef double nearest_neighbour_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r,
double c, char mode,
double cval)
cdef double bilinear_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef inline Py_ssize_t round(double r):
return <Py_ssize_t>((r + 0.5) if (r > 0.0) else (r - 0.5))
cdef double quadratic_interpolation(double x, double[3] f)
cdef double biquadratic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef double cubic_interpolation(double x, double[4] f)
cdef double bicubic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef inline double nearest_neighbour_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r,
double c, char mode,
double cval):
"""Nearest neighbour interpolation at a given position in the image.
cdef double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t r,
Py_ssize_t c, char mode, double cval)
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
cdef double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims,
Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, char mode, double cval)
Returns
-------
value : double
Interpolated value.
cdef Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode)
"""
return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval)
cdef inline double bilinear_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bilinear interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef double dr, dc
cdef Py_ssize_t minr, minc, maxr, maxc
minr = <Py_ssize_t>floor(r)
minc = <Py_ssize_t>floor(c)
maxr = <Py_ssize_t>ceil(r)
maxc = <Py_ssize_t>ceil(c)
dr = r - minr
dc = c - minc
top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, minr, maxc, mode, cval)
bottom = (1 - dc) * get_pixel2d(image, rows, cols, maxr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, maxr, maxc, mode, cval)
return (1 - dr) * top + dr * bottom
cdef inline double quadratic_interpolation(double x, double[3] f):
"""Quadratic interpolation.
Parameters
----------
x : double
Position in the interval [-1, 1].
f : double[4]
Function values at positions [-1, 0, 1].
Returns
-------
value : double
Interpolated value.
"""
return f[1] - 0.25 * (f[0] - f[2]) * x
cdef inline double biquadratic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Biquadratic interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef Py_ssize_t r0 = round(r)
cdef Py_ssize_t c0 = round(c)
if r < 0:
r0 -= 1
if c < 0:
c0 -= 1
# scale position to range [-1, 1]
cdef double xr = (r - r0) - 1
cdef double xc = (c - c0) - 1
if r == r0:
xr += 1
if c == c0:
xc += 1
cdef double fc[3], fr[3]
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 3):
for pc in range(c0, c0 + 3):
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = quadratic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
return quadratic_interpolation(xr, fr)
cdef inline double cubic_interpolation(double x, double[4] f):
"""Cubic interpolation.
Parameters
----------
x : double
Position in the interval [0, 1].
f : double[4]
Function values at positions [0, 1/3, 2/3, 1].
Returns
-------
value : double
Interpolated value.
"""
return \
f[1] + 0.5 * x * \
(f[2] - f[0] + x * \
(2.0 * f[0] - 5.0 * f[1] + 4.0 * f[2] - f[3] + x * \
(3.0 * (f[1] - f[2]) + f[3] - f[0])))
cdef inline double bicubic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bicubic interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef Py_ssize_t r0 = <Py_ssize_t>r - 1
cdef Py_ssize_t c0 = <Py_ssize_t>c - 1
if r < 0:
r0 -= 1
if c < 0:
c0 -= 1
# scale position to range [0, 1]
cdef double xr = (r - r0) / 3
cdef double xc = (c - c0) / 3
cdef double fc[4], fr[4]
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 4):
for pc in range(c0, c0 + 4):
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = cubic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
return cubic_interpolation(xr, fr)
cdef inline double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c, char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : int
Position at which to get the pixel.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Pixel value at given position.
"""
if mode == 'C':
if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1):
return cval
else:
return image[r * cols + c]
else:
return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)]
cdef inline double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t dims, Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
----------
image : double array
Input image.
rows, cols, dims : int
Shape of image.
r, c, d : int
Position at which to get the pixel.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Pixel value at given position.
"""
if mode == 'C':
if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1):
return cval
else:
return image[r * cols * dims + c * dims + d]
else:
return image[coord_map(rows, r, mode) * cols * dims
+ coord_map(cols, c, mode) * dims
+ d]
cdef inline Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode):
"""
Wrap a coordinate, according to a given mode.
Parameters
----------
dim : int
Maximum coordinate.
coord : int
Coord provided by user. May be < 0 or > dim.
mode : {'W', 'R', 'N'}
Whether to wrap or reflect the coordinate if it
falls outside [0, dim).
"""
dim = dim - 1
if mode == 'R': # reflect
if coord < 0:
# How many times times does the coordinate wrap?
if <Py_ssize_t>(-coord / dim) % 2 != 0:
return dim - <Py_ssize_t>(-coord % dim)
else:
return <Py_ssize_t>(-coord % dim)
elif coord > dim:
if <Py_ssize_t>(coord / dim) % 2 != 0:
return <Py_ssize_t>(dim - (coord % dim))
else:
return <Py_ssize_t>(coord % dim)
elif mode == 'W': # wrap
if coord < 0:
return <Py_ssize_t>(dim - (-coord % dim))
elif coord > dim:
return <Py_ssize_t>(coord % dim)
elif mode == 'N': # nearest
if coord < 0:
return 0
elif coord > dim:
return dim
return coord
-331
View File
@@ -1,331 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
from libc.math cimport ceil, floor
cdef inline Py_ssize_t round(double r):
return <Py_ssize_t>((r + 0.5) if (r > 0.0) else (r - 0.5))
cdef inline double nearest_neighbour_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r,
double c, char mode,
double cval):
"""Nearest neighbour interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval)
cdef inline double bilinear_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bilinear interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef double dr, dc
cdef Py_ssize_t minr, minc, maxr, maxc
minr = <Py_ssize_t>floor(r)
minc = <Py_ssize_t>floor(c)
maxr = <Py_ssize_t>ceil(r)
maxc = <Py_ssize_t>ceil(c)
dr = r - minr
dc = c - minc
top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, minr, maxc, mode, cval)
bottom = (1 - dc) * get_pixel2d(image, rows, cols, maxr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, maxr, maxc, mode, cval)
return (1 - dr) * top + dr * bottom
cdef inline double quadratic_interpolation(double x, double[3] f):
"""Quadratic interpolation.
Parameters
----------
x : double
Position in the interval [-1, 1].
f : double[4]
Function values at positions [-1, 0, 1].
Returns
-------
value : double
Interpolated value.
"""
return f[1] - 0.25 * (f[0] - f[2]) * x
cdef inline double biquadratic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Biquadratic interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef Py_ssize_t r0 = round(r)
cdef Py_ssize_t c0 = round(c)
if r < 0:
r0 -= 1
if c < 0:
c0 -= 1
# scale position to range [-1, 1]
cdef double xr = (r - r0) - 1
cdef double xc = (c - c0) - 1
if r == r0:
xr += 1
if c == c0:
xc += 1
cdef double fc[3], fr[3]
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 3):
for pc in range(c0, c0 + 3):
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = quadratic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
return quadratic_interpolation(xr, fr)
cdef inline double cubic_interpolation(double x, double[4] f):
"""Cubic interpolation.
Parameters
----------
x : double
Position in the interval [0, 1].
f : double[4]
Function values at positions [0, 1/3, 2/3, 1].
Returns
-------
value : double
Interpolated value.
"""
return \
f[1] + 0.5 * x * \
(f[2] - f[0] + x * \
(2.0 * f[0] - 5.0 * f[1] + 4.0 * f[2] - f[3] + x * \
(3.0 * (f[1] - f[2]) + f[3] - f[0])))
cdef inline double bicubic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bicubic interpolation at a given position in the image.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : double
Position at which to interpolate.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Interpolated value.
"""
cdef Py_ssize_t r0 = <Py_ssize_t>r - 1
cdef Py_ssize_t c0 = <Py_ssize_t>c - 1
if r < 0:
r0 -= 1
if c < 0:
c0 -= 1
# scale position to range [0, 1]
cdef double xr = (r - r0) / 3
cdef double xc = (c - c0) / 3
cdef double fc[4], fr[4]
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 4):
for pc in range(c0, c0 + 4):
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = cubic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
return cubic_interpolation(xr, fr)
cdef inline double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c, char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
----------
image : double array
Input image.
rows, cols : int
Shape of image.
r, c : int
Position at which to get the pixel.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Pixel value at given position.
"""
if mode == 'C':
if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1):
return cval
else:
return image[r * cols + c]
else:
return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)]
cdef inline double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t dims, Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
----------
image : double array
Input image.
rows, cols, dims : int
Shape of image.
r, c, d : int
Position at which to get the pixel.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Pixel value at given position.
"""
if mode == 'C':
if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1):
return cval
else:
return image[r * cols * dims + c * dims + d]
else:
return image[coord_map(rows, r, mode) * cols * dims
+ coord_map(cols, c, mode) * dims
+ d]
cdef inline Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode):
"""
Wrap a coordinate, according to a given mode.
Parameters
----------
dim : int
Maximum coordinate.
coord : int
Coord provided by user. May be < 0 or > dim.
mode : {'W', 'R', 'N'}
Whether to wrap or reflect the coordinate if it
falls outside [0, dim).
"""
dim = dim - 1
if mode == 'R': # reflect
if coord < 0:
# How many times times does the coordinate wrap?
if <Py_ssize_t>(-coord / dim) % 2 != 0:
return dim - <Py_ssize_t>(-coord % dim)
else:
return <Py_ssize_t>(-coord % dim)
elif coord > dim:
if <Py_ssize_t>(coord / dim) % 2 != 0:
return <Py_ssize_t>(dim - (coord % dim))
else:
return <Py_ssize_t>(coord % dim)
elif mode == 'W': # wrap
if coord < 0:
return <Py_ssize_t>(dim - (-coord % dim))
elif coord > dim:
return <Py_ssize_t>(coord % dim)
elif mode == 'N': # nearest
if coord < 0:
return 0
elif coord > dim:
return dim
return coord
-3
View File
@@ -14,12 +14,9 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('tests')
cython(['geometry.pyx'], working_path=base_path)
cython(['interpolation.pyx'], working_path=base_path)
cython(['transform.pyx'], working_path=base_path)
config.add_extension('geometry', sources=['geometry.c'])
config.add_extension('interpolation', sources=['interpolation.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('transform', sources=['transform.c'],
include_dirs=[get_numpy_include_dirs()])
-423
View File
@@ -1,423 +0,0 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
def iterbytes(buf):
return (ord(byte) for byte in buf)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
del builtins
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
+51
View File
@@ -1,6 +1,12 @@
"""Testing utilities."""
import re
SKIP_RE = re.compile("(\s*>>>.*?)(\s*)#\s*skip\s+if\s+(.*)$")
def _assert_less(a, b, msg=None):
message = "%r is not lower than %r" % (a, b)
if msg is not None:
@@ -24,3 +30,48 @@ try:
from nose.tools import assert_greater
except ImportError:
assert_greater = _assert_greater
def doctest_skip_parser(func):
""" Decorator replaces custom skip test markup in doctests
Say a function has a docstring::
>>> something # skip if not HAVE_AMODULE
>>> something + else
>>> something # skip if HAVE_BMODULE
This decorator will evaluate the expresssion after ``skip if``. If this
evaluates to True, then the comment is replaced by ``# doctest: +SKIP``. If
False, then the comment is just removed. The expression is evaluated in the
``globals`` scope of `func`.
For example, if the module global ``HAVE_AMODULE`` is False, and module
global ``HAVE_BMODULE`` is False, the returned function will have docstring::
>>> something # doctest: +SKIP
>>> something + else
>>> something
"""
lines = func.__doc__.split('\n')
new_lines = []
for line in lines:
match = SKIP_RE.match(line)
if match is None:
new_lines.append(line)
continue
code, space, expr = match.groups()
try:
# Works as a function decorator
if eval(expr, func.__globals__):
code = code + space + "# doctest: +SKIP"
except AttributeError:
# Works as a class decorator
if eval(expr, func.__init__.__globals__):
code = code + space + "# doctest: +SKIP"
new_lines.append(code)
func.__doc__ = "\n".join(new_lines)
return func
+87
View File
@@ -0,0 +1,87 @@
""" Testing decorators module
"""
import numpy as np
from nose.tools import (assert_true, assert_raises, assert_equal)
from skimage._shared.testing import doctest_skip_parser
def test_skipper():
def f():
pass
class c():
def __init__(self):
self.me = "I think, therefore..."
docstring = \
""" Header
>>> something # skip if not HAVE_AMODULE
>>> something + else
>>> a = 1 # skip if not HAVE_BMODULE
>>> something2 # skip if HAVE_AMODULE
"""
f.__doc__ = docstring
c.__doc__ = docstring
global HAVE_AMODULE, HAVE_BMODULE
HAVE_AMODULE = False
HAVE_BMODULE = True
f2 = doctest_skip_parser(f)
c2 = doctest_skip_parser(c)
assert_true(f is f2)
assert_true(c is c2)
assert_equal(f2.__doc__,
""" Header
>>> something # doctest: +SKIP
>>> something + else
>>> a = 1
>>> something2
""")
assert_equal(c2.__doc__,
""" Header
>>> something # doctest: +SKIP
>>> something + else
>>> a = 1
>>> something2
""")
HAVE_AMODULE = True
HAVE_BMODULE = False
f.__doc__ = docstring
c.__doc__ = docstring
f2 = doctest_skip_parser(f)
c2 = doctest_skip_parser(c)
assert_true(f is f2)
assert_equal(f2.__doc__,
""" Header
>>> something
>>> something + else
>>> a = 1 # doctest: +SKIP
>>> something2 # doctest: +SKIP
""")
assert_equal(c2.__doc__,
""" Header
>>> something
>>> something + else
>>> a = 1 # doctest: +SKIP
>>> something2 # doctest: +SKIP
""")
del HAVE_AMODULE
f.__doc__ = docstring
c.__doc__ = docstring
assert_raises(NameError, doctest_skip_parser, f)
assert_raises(NameError, doctest_skip_parser, c)
if __name__ == '__main__':
np.testing.run_module_suite()
+1
View File
@@ -41,4 +41,5 @@ cdef float integrate(float[:, ::1] sat, Py_ssize_t r0, Py_ssize_t c0,
if (c0 - 1 >= 0):
S -= sat[r1, c0 - 1]
return S
+4
View File
@@ -13,6 +13,10 @@ from .colorconv import (convert_colorspace,
lab2xyz,
lab2rgb,
rgb2lab,
xyz2luv,
luv2xyz,
luv2rgb,
rgb2luv,
rgb2hed,
hed2rgb,
lab2lch,
+246 -63
View File
@@ -29,9 +29,12 @@ Supported color spaces
* LAB CIE : Lightness, a, b
Colorspace derived from XYZ CIE that is intended to be more
perceptually uniform
* LUV CIE : Lightness, u, v
Colorspace derived from XYZ CIE that is intended to be more
perceptually uniform
* LCH CIE : Lightness, Chroma, Hue
Defined in terms of LAB CIE. C and H are the polar representation of
a and b. The polar angle C is defined to be on (0, 2*pi)
a and b. The polar angle C is defined to be on ``(0, 2*pi)``
:author: Nicolas Pinto (rgb2hsv)
:author: Ralf Gommers (hsv2rgb)
@@ -68,7 +71,7 @@ def guess_spatial_dimensions(image):
-------
spatial_dims : int or None
The number of spatial dimensions of `image`. If ambiguous, the value
is `None`.
is ``None``.
Raises
------
@@ -122,12 +125,12 @@ def convert_colorspace(arr, fromspace, tospace):
The image to convert.
fromspace : str
The color space to convert from. Valid color space strings are
['RGB', 'HSV', 'RGB CIE', 'XYZ']. Value may also be specified as lower
case.
``['RGB', 'HSV', 'RGB CIE', 'XYZ']``. Value may also be specified as
lower case.
tospace : str
The color space to convert to. Valid color space strings are
['RGB', 'HSV', 'RGB CIE', 'XYZ']. Value may also be specified as lower
case.
``['RGB', 'HSV', 'RGB CIE', 'XYZ']``. Value may also be specified as
lower case.
Returns
-------
@@ -137,7 +140,7 @@ def convert_colorspace(arr, fromspace, tospace):
Notes
-----
Conversion occurs through the "central" RGB color space, i.e. conversion
from XYZ to HSV is implemented as XYZ -> RGB -> HSV instead of directly.
from XYZ to HSV is implemented as ``XYZ -> RGB -> HSV`` instead of directly.
Examples
--------
@@ -181,17 +184,17 @@ def rgb2hsv(rgb):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in HSV format, in a 3-D array of shape (.., .., 3).
The image in HSV format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
@@ -259,21 +262,21 @@ def hsv2rgb(hsv):
Parameters
----------
hsv : array_like
The image in HSV format, in a 3-D array of shape (.., .., 3).
The image in HSV format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `hsv` is not a 3-D array of shape (.., .., 3).
If `hsv` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
The conversion assumes an input data range of [0, 1] for all
The conversion assumes an input data range of ``[0, 1]`` for all
color components.
Conversion between RGB and HSV color spaces results in some loss of
@@ -468,17 +471,17 @@ def xyz2rgb(xyz):
Parameters
----------
xyz : array_like
The image in XYZ format, in a 3-D array of shape (.., .., 3).
The image in XYZ format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `xyz` is not a 3-D array of shape (.., .., 3).
If `xyz` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
@@ -513,18 +516,18 @@ def rgb2xyz(rgb):
----------
rgb : array_like
The image in RGB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Returns
-------
out : ndarray
The image in XYZ format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Raises
------
ValueError
If `rgb` is not a 3- or 4-D array of shape (.., ..,[ ..,] 3).
If `rgb` is not a 3- or 4-D array of shape ``(.., ..,[ ..,] 3)``.
Notes
-----
@@ -556,17 +559,17 @@ def rgb2rgbcie(rgb):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB CIE format, in a 3-D array of shape (.., .., 3).
The image in RGB CIE format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
References
----------
@@ -588,17 +591,17 @@ def rgbcie2rgb(rgbcie):
Parameters
----------
rgbcie : array_like
The image in RGB CIE format, in a 3-D array of shape (.., .., 3).
The image in RGB CIE format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `rgbcie` is not a 3-D array of shape (.., .., 3).
If `rgbcie` is not a 3-D array of shape ``(.., .., 3)``.
References
----------
@@ -621,8 +624,8 @@ def rgb2gray(rgb):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3),
or in RGBA format with shape (.., .., 4).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``,
or in RGBA format with shape ``(.., .., 4)``.
Returns
-------
@@ -632,8 +635,8 @@ def rgb2gray(rgb):
Raises
------
ValueError
If `rgb2gray` is not a 3-D array of shape (.., .., 3) or
(.., .., 4).
If `rgb2gray` is not a 3-D array of shape ``(.., .., 3)`` or
``(.., .., 4)``.
References
----------
@@ -698,18 +701,18 @@ def xyz2lab(xyz):
----------
xyz : array_like
The image in XYZ format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Returns
-------
out : ndarray
The image in CIE-LAB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Raises
------
ValueError
If `xyz` is not a 3-D array of shape (.., ..,[ ..,] 3).
If `xyz` is not a 3-D array of shape ``(.., ..,[ ..,] 3)``.
Notes
-----
@@ -755,21 +758,21 @@ def lab2xyz(lab):
Parameters
----------
lab : array_like
The image in lab format, in a 3-D array of shape (.., .., 3).
The image in lab format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in XYZ format, in a 3-D array of shape (.., .., 3).
The image in XYZ format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `lab` is not a 3-D array of shape (.., .., 3).
If `lab` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
Observer= 2A, Illuminant= D65
Observer = 2A, Illuminant = D65
CIE XYZ tristimulus values x_ref = 95.047, y_ref = 100., z_ref = 108.883
References
@@ -804,18 +807,18 @@ def rgb2lab(rgb):
----------
rgb : array_like
The image in RGB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Returns
-------
out : ndarray
The image in Lab format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
``(.., ..,[ ..,] 3)``.
Raises
------
ValueError
If `rgb` is not a 3- or 4-D array of shape (.., ..,[ ..,] 3).
If `rgb` is not a 3- or 4-D array of shape ``(.., ..,[ ..,] 3)``.
Notes
-----
@@ -830,17 +833,17 @@ def lab2rgb(lab):
Parameters
----------
rgb : array_like
The image in Lab format, in a 3-D array of shape (.., .., 3).
The image in Lab format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `lab` is not a 3-D array of shape (.., .., 3).
If `lab` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
@@ -849,23 +852,203 @@ def lab2rgb(lab):
return xyz2rgb(lab2xyz(lab))
def xyz2luv(xyz):
"""XYZ to CIE-Luv color space conversion.
Parameters
----------
xyz : (M, N, [P,] 3) array_like
The 3 or 4 dimensional image in XYZ format. Final dimension denotes
channels.
Returns
-------
out : (M, N, [P,] 3) ndarray
The image in CIE-Luv format. Same dimensions as input.
Raises
------
ValueError
If `xyz` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
Notes
-----
XYZ conversion weights use Observer = 2A. Reference whitepoint for D65
Illuminant, with XYZ tristimulus values of ``(95.047, 100., 108.883)``.
References
----------
.. [1] http://www.easyrgb.com/index.php?X=MATH&H=16#text16
.. [2] http://en.wikipedia.org/wiki/CIELUV
Examples
--------
>>> from skimage import data
>>> from skimage.color import rgb2xyz, xyz2luv
>>> lena = data.lena()
>>> lena_xyz = rgb2xyz(lena)
>>> lena_luv = xyz2luv(lena_xyz)
"""
arr = _prepare_colorarray(xyz)
# extract channels
x, y, z = arr[..., 0], arr[..., 1], arr[..., 2]
eps = np.finfo(np.float).eps
# compute y_r and L
L = y / lab_ref_white[1]
mask = L > 0.008856
L[mask] = 116. * np.power(L[mask], 1. / 3.) - 16.
L[~mask] = 903.3 * L[~mask]
u0 = 4*lab_ref_white[0] / np.dot([1, 15, 3], lab_ref_white)
v0 = 9*lab_ref_white[1] / np.dot([1, 15, 3], lab_ref_white)
# u' and v' helper functions
def fu(X, Y, Z):
return (4.*X) / (X + 15.*Y + 3.*Z + eps)
def fv(X, Y, Z):
return (9.*Y) / (X + 15.*Y + 3.*Z + eps)
# compute u and v using helper functions
u = 13.*L * (fu(x, y, z) - u0)
v = 13.*L * (fv(x, y, z) - v0)
return np.concatenate([q[..., np.newaxis] for q in [L, u, v]], axis=-1)
def luv2xyz(luv):
"""CIE-Luv to XYZ color space conversion.
Parameters
----------
luv : (M, N, [P,] 3) array_like
The 3 or 4 dimensional image in CIE-Luv format. Final dimension denotes
channels.
Returns
-------
out : (M, N, [P,] 3) ndarray
The image in XYZ format. Same dimensions as input.
Raises
------
ValueError
If `luv` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
Notes
-----
XYZ conversion weights use Observer = 2A. Reference whitepoint for D65
Illuminant, with XYZ tristimulus values of ``(95.047, 100., 108.883)``.
References
----------
.. [1] http://www.easyrgb.com/index.php?X=MATH&H=16#text16
.. [2] http://en.wikipedia.org/wiki/CIELUV
"""
arr = _prepare_colorarray(luv).copy()
L, u, v = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2]
eps = np.finfo(np.float).eps
# compute y
y = L.copy()
mask = y > 7.999625
y[mask] = np.power((y[mask]+16.) / 116., 3.)
y[~mask] = y[~mask] / 903.3
y *= lab_ref_white[1]
# reference white x,z
uv_weights = [1, 15, 3]
u0 = 4*lab_ref_white[0] / np.dot(uv_weights, lab_ref_white)
v0 = 9*lab_ref_white[1] / np.dot(uv_weights, lab_ref_white)
# compute intermediate values
a = u0 + u / (13.*L + eps)
b = v0 + v / (13.*L + eps)
c = 3*y * (5*b-3)
# compute x and z
z = ((a-4)*c - 15*a*b*y) / (12*b)
x = -(c/b + 3.*z)
return np.concatenate([q[..., np.newaxis] for q in [x, y, z]], axis=-1)
def rgb2luv(rgb):
"""RGB to CIE-Luv color space conversion.
Parameters
----------
rgb : (M, N, [P,] 3) array_like
The 3 or 4 dimensional image in RGB format. Final dimension denotes
channels.
Returns
-------
out : (M, N, [P,] 3) ndarray
The image in CIE Luv format. Same dimensions as input.
Raises
------
ValueError
If `rgb` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
Notes
-----
This function uses rgb2xyz and xyz2luv.
"""
return xyz2luv(rgb2xyz(rgb))
def luv2rgb(luv):
"""Luv to RGB color space conversion.
Parameters
----------
luv : (M, N, [P,] 3) array_like
The 3 or 4 dimensional image in CIE Luv format. Final dimension denotes
channels.
Returns
-------
out : (M, N, [P,] 3) ndarray
The image in RGB format. Same dimensions as input.
Raises
------
ValueError
If `luv` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
Notes
-----
This function uses luv2xyz and xyz2rgb.
"""
return xyz2rgb(luv2xyz(luv))
def rgb2hed(rgb):
"""RGB to Haematoxylin-Eosin-DAB (HED) color space conversion.
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in HED format, in a 3-D array of shape (.., .., 3).
The image in HED format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
References
@@ -891,17 +1074,17 @@ def hed2rgb(hed):
Parameters
----------
hed : array_like
The image in the HED color space, in a 3-D array of shape (.., .., 3).
The image in the HED color space, in a 3-D array of shape ``(.., .., 3)``.
Returns
-------
out : ndarray
The image in RGB, in a 3-D array of shape (.., .., 3).
The image in RGB, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `hed` is not a 3-D array of shape (.., .., 3).
If `hed` is not a 3-D array of shape ``(.., .., 3)``.
References
----------
@@ -927,19 +1110,19 @@ def separate_stains(rgb, conv_matrix):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
conv_matrix: ndarray
The stain separation matrix as described by G. Landini [1]_.
Returns
-------
out : ndarray
The image in stain color space, in a 3-D array of shape (.., .., 3).
The image in stain color space, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
@@ -981,19 +1164,19 @@ def combine_stains(stains, conv_matrix):
Parameters
----------
stains : array_like
The image in stain color space, in a 3-D array of shape (.., .., 3).
The image in stain color space, in a 3-D array of shape ``(.., .., 3)``.
conv_matrix: ndarray
The stain separation matrix as described by G. Landini [1]_.
Returns
-------
out : ndarray
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
Raises
------
ValueError
If `stains` is not a 3-D array of shape (.., .., 3).
If `stains` is not a 3-D array of shape ``(.., .., 3)``.
Notes
-----
@@ -1043,9 +1226,9 @@ def lab2lch(lab):
Parameters
----------
lab : array_like
The N-D image in CIE-LAB format. The last (`N+1`th) dimension must have
at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color
channels. Subsequent elements are copied.
The N-D image in CIE-LAB format. The last (``N+1``-th) dimension must
have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
color channels. Subsequent elements are copied.
Returns
-------
@@ -1059,7 +1242,7 @@ def lab2lch(lab):
Notes
-----
The Hue is expressed as an angle between (0, 2*pi)
The Hue is expressed as an angle between ``(0, 2*pi)``
Examples
--------
@@ -1079,7 +1262,7 @@ def lab2lch(lab):
def _cart2polar_2pi(x, y):
"""convert cartesian coordiantes to polar (uses non-standard theta range!)
NON-STANDARD RANGE! Maps to (0, 2*pi) rather than usual (-pi, +pi)
NON-STANDARD RANGE! Maps to ``(0, 2*pi)`` rather than usual ``(-pi, +pi)``
"""
r, t = np.hypot(x, y), np.arctan2(y, x)
t += np.where(t < 0., 2 * np.pi, 0)
@@ -1094,9 +1277,9 @@ def lch2lab(lch):
Parameters
----------
lch : array_like
The N-D image in CIE-LCH format. The last (`N+1`th) dimension must have
at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color
channels. Subsequent elements are copied.
The N-D image in CIE-LCH format. The last (``N+1``-th) dimension must
have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
color channels. Subsequent elements are copied.
Returns
-------
+54 -10
View File
@@ -33,6 +33,8 @@ from skimage.color import (rgb2hsv, hsv2rgb,
rgb2grey, gray2rgb,
xyz2lab, lab2xyz,
lab2rgb, rgb2lab,
xyz2luv, luv2xyz,
luv2rgb, rgb2luv,
is_rgb, is_gray,
lab2lch, lch2lab,
guess_spatial_dimensions
@@ -69,17 +71,24 @@ class TestColorconv(TestCase):
colbars_point75_array = np.swapaxes(colbars_point75.reshape(3, 4, 2), 0, 2)
xyz_array = np.array([[[0.4124, 0.21260, 0.01930]], # red
[[0, 0, 0]], # black
[[.9505, 1., 1.089]], # white
[[.1805, .0722, .9505]], # blue
[[.07719, .15438, .02573]], # green
])
[[0, 0, 0]], # black
[[.9505, 1., 1.089]], # white
[[.1805, .0722, .9505]], # blue
[[.07719, .15438, .02573]], # green
])
lab_array = np.array([[[53.233, 80.109, 67.220]], # red
[[0., 0., 0.]], # black
[[100.0, 0.005, -0.010]], # white
[[32.303, 79.197, -107.864]], # blue
[[46.229, -51.7, 49.898]], # green
])
[[0., 0., 0.]], # black
[[100.0, 0.005, -0.010]], # white
[[32.303, 79.197, -107.864]], # blue
[[46.229, -51.7, 49.898]], # green
])
luv_array = np.array([[[53.233, 175.053, 37.751]], # red
[[0., 0., 0.]], # black
[[100., 0.001, -0.017]], # white
[[32.303, -9.400, -130.358]], # blue
[[46.228, -43.774, 56.589]], # green
])
# RGB to HSV
def test_rgb2hsv_conversion(self):
@@ -250,6 +259,41 @@ class TestColorconv(TestCase):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(lab2rgb(rgb2lab(img_rgb)), img_rgb)
# test matrices for xyz2luv and luv2xyz generated using
# http://www.easyrgb.com/index.php?X=CALC
# Note: easyrgb website displays xyz*100
def test_xyz2luv(self):
assert_array_almost_equal(xyz2luv(self.xyz_array),
self.luv_array, decimal=3)
def test_luv2xyz(self):
assert_array_almost_equal(luv2xyz(self.luv_array),
self.xyz_array, decimal=3)
def test_rgb2luv_brucelindbloom(self):
"""
Test the RGB->Lab conversion by comparing to the calculator on the
authoritative Bruce Lindbloom
[website](http://brucelindbloom.com/index.html?ColorCalculator.html).
"""
# Obtained with D65 white point, sRGB model and gamma
gt_for_colbars = np.array([
[100, 0, 0],
[97.1393, 7.7056, 106.7866],
[91.1132, -70.4773, -15.2042],
[87.7347, -83.0776, 107.3985],
[60.3242, 84.0714, -108.6834],
[53.2408, 175.0151, 37.7564],
[32.2970, -9.4054, -130.3423],
[0, 0, 0]]).T
gt_array = np.swapaxes(gt_for_colbars.reshape(3, 4, 2), 0, 2)
assert_array_almost_equal(rgb2luv(self.colbars_array),
gt_array, decimal=2)
def test_luv_rgb_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(luv2rgb(rgb2luv(img_rgb)), img_rgb)
def test_lab_lch_roundtrip(self):
rgb = img_as_float(self.img_rgb)
lab = rgb2lab(rgb)
+256
View File
@@ -0,0 +1,256 @@
8.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00 5.000000000000000000e+00
4.000000000000000000e+00 2.000000000000000000e+00 7.000000000000000000e+00 -1.200000000000000000e+01
-1.100000000000000000e+01 9.000000000000000000e+00 -8.000000000000000000e+00 2.000000000000000000e+00
7.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01 -1.300000000000000000e+01
2.000000000000000000e+00 -1.300000000000000000e+01 2.000000000000000000e+00 1.200000000000000000e+01
1.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+00 6.000000000000000000e+00
-2.000000000000000000e+00 -1.000000000000000000e+01 -2.000000000000000000e+00 -4.000000000000000000e+00
-1.300000000000000000e+01 -1.300000000000000000e+01 -1.100000000000000000e+01 -8.000000000000000000e+00
-1.300000000000000000e+01 -3.000000000000000000e+00 -1.200000000000000000e+01 -9.000000000000000000e+00
1.000000000000000000e+01 4.000000000000000000e+00 1.100000000000000000e+01 9.000000000000000000e+00
-1.300000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00 -9.000000000000000000e+00
-1.100000000000000000e+01 7.000000000000000000e+00 -9.000000000000000000e+00 1.200000000000000000e+01
7.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01 6.000000000000000000e+00
-4.000000000000000000e+00 -5.000000000000000000e+00 -3.000000000000000000e+00 0.000000000000000000e+00
-1.300000000000000000e+01 2.000000000000000000e+00 -1.200000000000000000e+01 -3.000000000000000000e+00
-9.000000000000000000e+00 0.000000000000000000e+00 -7.000000000000000000e+00 5.000000000000000000e+00
1.200000000000000000e+01 -6.000000000000000000e+00 1.200000000000000000e+01 -1.000000000000000000e+00
-3.000000000000000000e+00 6.000000000000000000e+00 -2.000000000000000000e+00 1.200000000000000000e+01
-6.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 -8.000000000000000000e+00
1.100000000000000000e+01 -1.300000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00
4.000000000000000000e+00 7.000000000000000000e+00 5.000000000000000000e+00 1.000000000000000000e+00
5.000000000000000000e+00 -3.000000000000000000e+00 1.000000000000000000e+01 -3.000000000000000000e+00
3.000000000000000000e+00 -7.000000000000000000e+00 6.000000000000000000e+00 1.200000000000000000e+01
-8.000000000000000000e+00 -7.000000000000000000e+00 -6.000000000000000000e+00 -2.000000000000000000e+00
-2.000000000000000000e+00 1.100000000000000000e+01 -1.000000000000000000e+00 -1.000000000000000000e+01
-1.300000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00 1.000000000000000000e+01
-7.000000000000000000e+00 3.000000000000000000e+00 -5.000000000000000000e+00 -3.000000000000000000e+00
-4.000000000000000000e+00 2.000000000000000000e+00 -3.000000000000000000e+00 7.000000000000000000e+00
-1.000000000000000000e+01 -1.200000000000000000e+01 -6.000000000000000000e+00 1.100000000000000000e+01
5.000000000000000000e+00 -1.200000000000000000e+01 6.000000000000000000e+00 -7.000000000000000000e+00
5.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00 -1.000000000000000000e+00
1.000000000000000000e+00 0.000000000000000000e+00 4.000000000000000000e+00 -5.000000000000000000e+00
9.000000000000000000e+00 1.100000000000000000e+01 1.100000000000000000e+01 -1.300000000000000000e+01
4.000000000000000000e+00 7.000000000000000000e+00 4.000000000000000000e+00 1.200000000000000000e+01
2.000000000000000000e+00 -1.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00
-4.000000000000000000e+00 -1.200000000000000000e+01 -2.000000000000000000e+00 7.000000000000000000e+00
-8.000000000000000000e+00 -5.000000000000000000e+00 -7.000000000000000000e+00 -1.000000000000000000e+01
4.000000000000000000e+00 1.100000000000000000e+01 9.000000000000000000e+00 1.200000000000000000e+01
0.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00 -1.300000000000000000e+01
-1.300000000000000000e+01 -2.000000000000000000e+00 -8.000000000000000000e+00 2.000000000000000000e+00
-3.000000000000000000e+00 -2.000000000000000000e+00 -2.000000000000000000e+00 3.000000000000000000e+00
-6.000000000000000000e+00 9.000000000000000000e+00 -4.000000000000000000e+00 -9.000000000000000000e+00
8.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+01 7.000000000000000000e+00
0.000000000000000000e+00 9.000000000000000000e+00 1.000000000000000000e+00 3.000000000000000000e+00
7.000000000000000000e+00 -5.000000000000000000e+00 1.100000000000000000e+01 -1.000000000000000000e+01
-1.300000000000000000e+01 -6.000000000000000000e+00 -1.100000000000000000e+01 0.000000000000000000e+00
1.000000000000000000e+01 7.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+00
-6.000000000000000000e+00 -3.000000000000000000e+00 -6.000000000000000000e+00 1.200000000000000000e+01
1.000000000000000000e+01 -9.000000000000000000e+00 1.200000000000000000e+01 -4.000000000000000000e+00
-1.300000000000000000e+01 8.000000000000000000e+00 -8.000000000000000000e+00 -1.200000000000000000e+01
-1.300000000000000000e+01 0.000000000000000000e+00 -8.000000000000000000e+00 -4.000000000000000000e+00
3.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00 8.000000000000000000e+00
5.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+01 -7.000000000000000000e+00
-1.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+00 -1.200000000000000000e+01
3.000000000000000000e+00 -1.000000000000000000e+01 5.000000000000000000e+00 6.000000000000000000e+00
2.000000000000000000e+00 -4.000000000000000000e+00 3.000000000000000000e+00 -1.000000000000000000e+01
-1.300000000000000000e+01 0.000000000000000000e+00 -1.300000000000000000e+01 5.000000000000000000e+00
-1.300000000000000000e+01 -7.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01
-1.300000000000000000e+01 3.000000000000000000e+00 -1.100000000000000000e+01 8.000000000000000000e+00
-7.000000000000000000e+00 1.200000000000000000e+01 -4.000000000000000000e+00 7.000000000000000000e+00
6.000000000000000000e+00 -1.000000000000000000e+01 1.200000000000000000e+01 8.000000000000000000e+00
-9.000000000000000000e+00 -1.000000000000000000e+00 -7.000000000000000000e+00 -6.000000000000000000e+00
-2.000000000000000000e+00 -5.000000000000000000e+00 0.000000000000000000e+00 1.200000000000000000e+01
-1.200000000000000000e+01 5.000000000000000000e+00 -7.000000000000000000e+00 5.000000000000000000e+00
3.000000000000000000e+00 -1.000000000000000000e+01 8.000000000000000000e+00 -1.300000000000000000e+01
-7.000000000000000000e+00 -7.000000000000000000e+00 -4.000000000000000000e+00 5.000000000000000000e+00
-3.000000000000000000e+00 -2.000000000000000000e+00 -1.000000000000000000e+00 -7.000000000000000000e+00
2.000000000000000000e+00 9.000000000000000000e+00 5.000000000000000000e+00 -1.100000000000000000e+01
-1.100000000000000000e+01 -1.300000000000000000e+01 -5.000000000000000000e+00 -1.300000000000000000e+01
-1.000000000000000000e+00 6.000000000000000000e+00 0.000000000000000000e+00 -1.000000000000000000e+00
5.000000000000000000e+00 -3.000000000000000000e+00 5.000000000000000000e+00 2.000000000000000000e+00
-4.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 1.200000000000000000e+01
-9.000000000000000000e+00 -6.000000000000000000e+00 -9.000000000000000000e+00 6.000000000000000000e+00
-1.200000000000000000e+01 -1.000000000000000000e+01 -8.000000000000000000e+00 -4.000000000000000000e+00
1.000000000000000000e+01 2.000000000000000000e+00 1.200000000000000000e+01 -3.000000000000000000e+00
7.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01
-7.000000000000000000e+00 -1.300000000000000000e+01 -6.000000000000000000e+00 5.000000000000000000e+00
-4.000000000000000000e+00 9.000000000000000000e+00 -3.000000000000000000e+00 4.000000000000000000e+00
7.000000000000000000e+00 -1.000000000000000000e+00 1.200000000000000000e+01 2.000000000000000000e+00
-7.000000000000000000e+00 6.000000000000000000e+00 -5.000000000000000000e+00 1.000000000000000000e+00
-1.300000000000000000e+01 1.100000000000000000e+01 -1.200000000000000000e+01 5.000000000000000000e+00
-3.000000000000000000e+00 7.000000000000000000e+00 -2.000000000000000000e+00 -6.000000000000000000e+00
7.000000000000000000e+00 -8.000000000000000000e+00 1.200000000000000000e+01 -7.000000000000000000e+00
-1.300000000000000000e+01 -7.000000000000000000e+00 -1.100000000000000000e+01 -1.200000000000000000e+01
1.000000000000000000e+00 -3.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01
2.000000000000000000e+00 -6.000000000000000000e+00 3.000000000000000000e+00 0.000000000000000000e+00
-4.000000000000000000e+00 3.000000000000000000e+00 -2.000000000000000000e+00 -1.300000000000000000e+01
-1.000000000000000000e+00 -1.300000000000000000e+01 1.000000000000000000e+00 9.000000000000000000e+00
7.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 -6.000000000000000000e+00
1.000000000000000000e+00 -1.000000000000000000e+00 3.000000000000000000e+00 1.200000000000000000e+01
9.000000000000000000e+00 1.000000000000000000e+00 1.200000000000000000e+01 6.000000000000000000e+00
-1.000000000000000000e+00 -9.000000000000000000e+00 -1.000000000000000000e+00 3.000000000000000000e+00
-1.300000000000000000e+01 -1.300000000000000000e+01 -1.000000000000000000e+01 5.000000000000000000e+00
7.000000000000000000e+00 7.000000000000000000e+00 1.000000000000000000e+01 1.200000000000000000e+01
1.200000000000000000e+01 -5.000000000000000000e+00 1.200000000000000000e+01 9.000000000000000000e+00
6.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00 1.100000000000000000e+01
5.000000000000000000e+00 -1.300000000000000000e+01 6.000000000000000000e+00 1.000000000000000000e+01
2.000000000000000000e+00 -1.200000000000000000e+01 2.000000000000000000e+00 3.000000000000000000e+00
3.000000000000000000e+00 8.000000000000000000e+00 4.000000000000000000e+00 -6.000000000000000000e+00
2.000000000000000000e+00 6.000000000000000000e+00 1.200000000000000000e+01 -1.300000000000000000e+01
9.000000000000000000e+00 -1.200000000000000000e+01 1.000000000000000000e+01 3.000000000000000000e+00
-8.000000000000000000e+00 4.000000000000000000e+00 -7.000000000000000000e+00 9.000000000000000000e+00
-1.100000000000000000e+01 1.200000000000000000e+01 -4.000000000000000000e+00 -6.000000000000000000e+00
1.000000000000000000e+00 1.200000000000000000e+01 2.000000000000000000e+00 -8.000000000000000000e+00
6.000000000000000000e+00 -9.000000000000000000e+00 7.000000000000000000e+00 -4.000000000000000000e+00
2.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 -2.000000000000000000e+00
6.000000000000000000e+00 3.000000000000000000e+00 1.100000000000000000e+01 0.000000000000000000e+00
3.000000000000000000e+00 -3.000000000000000000e+00 8.000000000000000000e+00 -8.000000000000000000e+00
7.000000000000000000e+00 8.000000000000000000e+00 9.000000000000000000e+00 3.000000000000000000e+00
-1.100000000000000000e+01 -5.000000000000000000e+00 -6.000000000000000000e+00 -4.000000000000000000e+00
-1.000000000000000000e+01 1.100000000000000000e+01 -5.000000000000000000e+00 1.000000000000000000e+01
-5.000000000000000000e+00 -8.000000000000000000e+00 -3.000000000000000000e+00 1.200000000000000000e+01
-1.000000000000000000e+01 5.000000000000000000e+00 -9.000000000000000000e+00 0.000000000000000000e+00
8.000000000000000000e+00 -1.000000000000000000e+00 1.200000000000000000e+01 -6.000000000000000000e+00
4.000000000000000000e+00 -6.000000000000000000e+00 6.000000000000000000e+00 -1.100000000000000000e+01
-1.000000000000000000e+01 1.200000000000000000e+01 -8.000000000000000000e+00 7.000000000000000000e+00
4.000000000000000000e+00 -2.000000000000000000e+00 6.000000000000000000e+00 7.000000000000000000e+00
-2.000000000000000000e+00 0.000000000000000000e+00 -2.000000000000000000e+00 1.200000000000000000e+01
-5.000000000000000000e+00 -8.000000000000000000e+00 -5.000000000000000000e+00 2.000000000000000000e+00
7.000000000000000000e+00 -6.000000000000000000e+00 1.000000000000000000e+01 1.200000000000000000e+01
-9.000000000000000000e+00 -1.300000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00
-5.000000000000000000e+00 -1.300000000000000000e+01 -5.000000000000000000e+00 -2.000000000000000000e+00
8.000000000000000000e+00 -8.000000000000000000e+00 9.000000000000000000e+00 -1.300000000000000000e+01
-9.000000000000000000e+00 -1.100000000000000000e+01 -9.000000000000000000e+00 0.000000000000000000e+00
1.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00 -2.000000000000000000e+00
7.000000000000000000e+00 -4.000000000000000000e+00 9.000000000000000000e+00 1.000000000000000000e+00
-2.000000000000000000e+00 1.000000000000000000e+00 -1.000000000000000000e+00 -4.000000000000000000e+00
1.100000000000000000e+01 -6.000000000000000000e+00 1.200000000000000000e+01 -1.100000000000000000e+01
-1.200000000000000000e+01 -9.000000000000000000e+00 -6.000000000000000000e+00 4.000000000000000000e+00
3.000000000000000000e+00 7.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01
5.000000000000000000e+00 5.000000000000000000e+00 1.000000000000000000e+01 8.000000000000000000e+00
0.000000000000000000e+00 -4.000000000000000000e+00 2.000000000000000000e+00 8.000000000000000000e+00
-9.000000000000000000e+00 1.200000000000000000e+01 -5.000000000000000000e+00 -1.300000000000000000e+01
0.000000000000000000e+00 7.000000000000000000e+00 2.000000000000000000e+00 1.200000000000000000e+01
-1.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 7.000000000000000000e+00
5.000000000000000000e+00 1.100000000000000000e+01 7.000000000000000000e+00 -9.000000000000000000e+00
3.000000000000000000e+00 5.000000000000000000e+00 6.000000000000000000e+00 -8.000000000000000000e+00
-1.300000000000000000e+01 -4.000000000000000000e+00 -8.000000000000000000e+00 9.000000000000000000e+00
-5.000000000000000000e+00 9.000000000000000000e+00 -3.000000000000000000e+00 -3.000000000000000000e+00
-4.000000000000000000e+00 -7.000000000000000000e+00 -3.000000000000000000e+00 -1.200000000000000000e+01
6.000000000000000000e+00 5.000000000000000000e+00 8.000000000000000000e+00 0.000000000000000000e+00
-7.000000000000000000e+00 6.000000000000000000e+00 -6.000000000000000000e+00 1.200000000000000000e+01
-1.300000000000000000e+01 6.000000000000000000e+00 -5.000000000000000000e+00 -2.000000000000000000e+00
1.000000000000000000e+00 -1.000000000000000000e+01 3.000000000000000000e+00 1.000000000000000000e+01
4.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 -4.000000000000000000e+00
-2.000000000000000000e+00 -2.000000000000000000e+00 2.000000000000000000e+00 -1.300000000000000000e+01
2.000000000000000000e+00 -1.200000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01
-2.000000000000000000e+00 -1.300000000000000000e+01 0.000000000000000000e+00 -6.000000000000000000e+00
4.000000000000000000e+00 1.000000000000000000e+00 9.000000000000000000e+00 3.000000000000000000e+00
-6.000000000000000000e+00 -1.000000000000000000e+01 -3.000000000000000000e+00 -5.000000000000000000e+00
-3.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 1.000000000000000000e+00
7.000000000000000000e+00 5.000000000000000000e+00 1.200000000000000000e+01 -1.100000000000000000e+01
4.000000000000000000e+00 -2.000000000000000000e+00 5.000000000000000000e+00 -7.000000000000000000e+00
-1.300000000000000000e+01 9.000000000000000000e+00 -9.000000000000000000e+00 -5.000000000000000000e+00
7.000000000000000000e+00 1.000000000000000000e+00 8.000000000000000000e+00 6.000000000000000000e+00
7.000000000000000000e+00 -8.000000000000000000e+00 7.000000000000000000e+00 6.000000000000000000e+00
-7.000000000000000000e+00 -4.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+00
-8.000000000000000000e+00 1.100000000000000000e+01 -7.000000000000000000e+00 -8.000000000000000000e+00
-1.300000000000000000e+01 6.000000000000000000e+00 -1.200000000000000000e+01 -8.000000000000000000e+00
2.000000000000000000e+00 4.000000000000000000e+00 3.000000000000000000e+00 9.000000000000000000e+00
1.000000000000000000e+01 -5.000000000000000000e+00 1.200000000000000000e+01 3.000000000000000000e+00
-6.000000000000000000e+00 -5.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00
8.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00 -8.000000000000000000e+00
2.000000000000000000e+00 -1.200000000000000000e+01 2.000000000000000000e+00 8.000000000000000000e+00
-1.100000000000000000e+01 -2.000000000000000000e+00 -1.000000000000000000e+01 3.000000000000000000e+00
-1.200000000000000000e+01 -1.300000000000000000e+01 -7.000000000000000000e+00 -9.000000000000000000e+00
-1.100000000000000000e+01 0.000000000000000000e+00 -1.000000000000000000e+01 -5.000000000000000000e+00
5.000000000000000000e+00 -3.000000000000000000e+00 1.100000000000000000e+01 8.000000000000000000e+00
-2.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 1.200000000000000000e+01
-1.000000000000000000e+00 -8.000000000000000000e+00 0.000000000000000000e+00 9.000000000000000000e+00
-1.300000000000000000e+01 -1.100000000000000000e+01 -1.200000000000000000e+01 -5.000000000000000000e+00
-1.000000000000000000e+01 -2.000000000000000000e+00 -1.000000000000000000e+01 1.100000000000000000e+01
-3.000000000000000000e+00 9.000000000000000000e+00 -2.000000000000000000e+00 -1.300000000000000000e+01
2.000000000000000000e+00 -3.000000000000000000e+00 3.000000000000000000e+00 2.000000000000000000e+00
-9.000000000000000000e+00 -1.300000000000000000e+01 -4.000000000000000000e+00 0.000000000000000000e+00
-4.000000000000000000e+00 6.000000000000000000e+00 -3.000000000000000000e+00 -1.000000000000000000e+01
-4.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00 -7.000000000000000000e+00
-6.000000000000000000e+00 -1.100000000000000000e+01 -4.000000000000000000e+00 9.000000000000000000e+00
6.000000000000000000e+00 -3.000000000000000000e+00 6.000000000000000000e+00 1.100000000000000000e+01
-1.300000000000000000e+01 1.100000000000000000e+01 -5.000000000000000000e+00 5.000000000000000000e+00
1.100000000000000000e+01 1.100000000000000000e+01 1.200000000000000000e+01 6.000000000000000000e+00
7.000000000000000000e+00 -5.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00
-1.000000000000000000e+00 1.200000000000000000e+01 0.000000000000000000e+00 7.000000000000000000e+00
-4.000000000000000000e+00 -8.000000000000000000e+00 -3.000000000000000000e+00 -2.000000000000000000e+00
-7.000000000000000000e+00 1.000000000000000000e+00 -6.000000000000000000e+00 7.000000000000000000e+00
-1.300000000000000000e+01 -1.200000000000000000e+01 -8.000000000000000000e+00 -1.300000000000000000e+01
-7.000000000000000000e+00 -2.000000000000000000e+00 -6.000000000000000000e+00 -8.000000000000000000e+00
-8.000000000000000000e+00 5.000000000000000000e+00 -6.000000000000000000e+00 -9.000000000000000000e+00
-5.000000000000000000e+00 -1.000000000000000000e+00 -4.000000000000000000e+00 5.000000000000000000e+00
-1.300000000000000000e+01 7.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+01
1.000000000000000000e+00 5.000000000000000000e+00 5.000000000000000000e+00 -1.300000000000000000e+01
1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+01 -1.300000000000000000e+01
9.000000000000000000e+00 1.200000000000000000e+01 1.000000000000000000e+01 -1.000000000000000000e+00
5.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+01 -9.000000000000000000e+00
-1.000000000000000000e+00 1.100000000000000000e+01 1.000000000000000000e+00 -1.300000000000000000e+01
-9.000000000000000000e+00 -3.000000000000000000e+00 -6.000000000000000000e+00 2.000000000000000000e+00
-1.000000000000000000e+00 -1.000000000000000000e+01 1.000000000000000000e+00 1.200000000000000000e+01
-1.300000000000000000e+01 1.000000000000000000e+00 -8.000000000000000000e+00 -1.000000000000000000e+01
8.000000000000000000e+00 -1.100000000000000000e+01 1.000000000000000000e+01 -6.000000000000000000e+00
2.000000000000000000e+00 -1.300000000000000000e+01 3.000000000000000000e+00 -6.000000000000000000e+00
7.000000000000000000e+00 -1.300000000000000000e+01 1.200000000000000000e+01 -9.000000000000000000e+00
-1.000000000000000000e+01 -1.000000000000000000e+01 -5.000000000000000000e+00 -7.000000000000000000e+00
-1.000000000000000000e+01 -8.000000000000000000e+00 -8.000000000000000000e+00 -1.300000000000000000e+01
4.000000000000000000e+00 -6.000000000000000000e+00 8.000000000000000000e+00 5.000000000000000000e+00
3.000000000000000000e+00 1.200000000000000000e+01 8.000000000000000000e+00 -1.300000000000000000e+01
-4.000000000000000000e+00 2.000000000000000000e+00 -3.000000000000000000e+00 -3.000000000000000000e+00
5.000000000000000000e+00 -1.300000000000000000e+01 1.000000000000000000e+01 -1.200000000000000000e+01
4.000000000000000000e+00 -1.300000000000000000e+01 5.000000000000000000e+00 -1.000000000000000000e+00
-9.000000000000000000e+00 9.000000000000000000e+00 -4.000000000000000000e+00 3.000000000000000000e+00
0.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 -9.000000000000000000e+00
-1.200000000000000000e+01 1.000000000000000000e+00 -6.000000000000000000e+00 1.000000000000000000e+00
3.000000000000000000e+00 2.000000000000000000e+00 4.000000000000000000e+00 -8.000000000000000000e+00
-1.000000000000000000e+01 -1.000000000000000000e+01 -1.000000000000000000e+01 9.000000000000000000e+00
8.000000000000000000e+00 -1.300000000000000000e+01 1.200000000000000000e+01 1.200000000000000000e+01
-8.000000000000000000e+00 -1.200000000000000000e+01 -6.000000000000000000e+00 -5.000000000000000000e+00
2.000000000000000000e+00 2.000000000000000000e+00 3.000000000000000000e+00 7.000000000000000000e+00
1.000000000000000000e+01 6.000000000000000000e+00 1.100000000000000000e+01 -8.000000000000000000e+00
6.000000000000000000e+00 8.000000000000000000e+00 8.000000000000000000e+00 -1.200000000000000000e+01
-7.000000000000000000e+00 1.000000000000000000e+01 -6.000000000000000000e+00 5.000000000000000000e+00
-3.000000000000000000e+00 -9.000000000000000000e+00 -3.000000000000000000e+00 9.000000000000000000e+00
-1.000000000000000000e+00 -1.300000000000000000e+01 -1.000000000000000000e+00 5.000000000000000000e+00
-3.000000000000000000e+00 -7.000000000000000000e+00 -3.000000000000000000e+00 4.000000000000000000e+00
-8.000000000000000000e+00 -2.000000000000000000e+00 -8.000000000000000000e+00 3.000000000000000000e+00
4.000000000000000000e+00 2.000000000000000000e+00 1.200000000000000000e+01 1.200000000000000000e+01
2.000000000000000000e+00 -5.000000000000000000e+00 3.000000000000000000e+00 1.100000000000000000e+01
6.000000000000000000e+00 -9.000000000000000000e+00 1.100000000000000000e+01 -1.300000000000000000e+01
3.000000000000000000e+00 -1.000000000000000000e+00 7.000000000000000000e+00 1.200000000000000000e+01
1.100000000000000000e+01 -1.000000000000000000e+00 1.200000000000000000e+01 4.000000000000000000e+00
-3.000000000000000000e+00 0.000000000000000000e+00 -3.000000000000000000e+00 6.000000000000000000e+00
4.000000000000000000e+00 -1.100000000000000000e+01 4.000000000000000000e+00 1.200000000000000000e+01
2.000000000000000000e+00 -4.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00
-1.000000000000000000e+01 -6.000000000000000000e+00 -8.000000000000000000e+00 1.000000000000000000e+00
-1.300000000000000000e+01 7.000000000000000000e+00 -1.100000000000000000e+01 1.000000000000000000e+00
-1.300000000000000000e+01 1.200000000000000000e+01 -1.100000000000000000e+01 -1.300000000000000000e+01
6.000000000000000000e+00 0.000000000000000000e+00 1.100000000000000000e+01 -1.300000000000000000e+01
0.000000000000000000e+00 -1.000000000000000000e+00 1.000000000000000000e+00 4.000000000000000000e+00
-1.300000000000000000e+01 3.000000000000000000e+00 -9.000000000000000000e+00 -2.000000000000000000e+00
-9.000000000000000000e+00 8.000000000000000000e+00 -6.000000000000000000e+00 -3.000000000000000000e+00
-1.300000000000000000e+01 -6.000000000000000000e+00 -8.000000000000000000e+00 -2.000000000000000000e+00
5.000000000000000000e+00 -9.000000000000000000e+00 8.000000000000000000e+00 1.000000000000000000e+01
2.000000000000000000e+00 7.000000000000000000e+00 3.000000000000000000e+00 -9.000000000000000000e+00
-1.000000000000000000e+00 -6.000000000000000000e+00 -1.000000000000000000e+00 -1.000000000000000000e+00
9.000000000000000000e+00 5.000000000000000000e+00 1.100000000000000000e+01 -2.000000000000000000e+00
1.100000000000000000e+01 -3.000000000000000000e+00 1.200000000000000000e+01 -8.000000000000000000e+00
3.000000000000000000e+00 0.000000000000000000e+00 3.000000000000000000e+00 5.000000000000000000e+00
-1.000000000000000000e+00 4.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+01
3.000000000000000000e+00 -6.000000000000000000e+00 4.000000000000000000e+00 5.000000000000000000e+00
-1.300000000000000000e+01 0.000000000000000000e+00 -1.000000000000000000e+01 5.000000000000000000e+00
5.000000000000000000e+00 8.000000000000000000e+00 1.200000000000000000e+01 1.100000000000000000e+01
8.000000000000000000e+00 9.000000000000000000e+00 9.000000000000000000e+00 -6.000000000000000000e+00
7.000000000000000000e+00 -4.000000000000000000e+00 8.000000000000000000e+00 -1.200000000000000000e+01
-1.000000000000000000e+01 4.000000000000000000e+00 -1.000000000000000000e+01 9.000000000000000000e+00
7.000000000000000000e+00 3.000000000000000000e+00 1.200000000000000000e+01 4.000000000000000000e+00
9.000000000000000000e+00 -7.000000000000000000e+00 1.000000000000000000e+01 -2.000000000000000000e+00
7.000000000000000000e+00 0.000000000000000000e+00 1.200000000000000000e+01 -2.000000000000000000e+00
-1.000000000000000000e+00 -6.000000000000000000e+00 0.000000000000000000e+00 -1.100000000000000000e+01
+22 -6
View File
@@ -1,9 +1,25 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_allclose
from nose.tools import raises
from skimage.draw import ellipsoid, ellipsoid_stats
@raises(ValueError)
def test_ellipsoid_sign_parameters1():
ellipsoid(-1, 2, 2)
@raises(ValueError)
def test_ellipsoid_sign_parameters2():
ellipsoid(0, 2, 2)
@raises(ValueError)
def test_ellipsoid_sign_parameters3():
ellipsoid(-3, -2, 2)
def test_ellipsoid_bool():
test = ellipsoid(2, 2, 2)[1:-1, 1:-1, 1:-1]
test_anisotropic = ellipsoid(2, 2, 4, spacing=(1., 1., 2.))
@@ -86,18 +102,18 @@ def test_ellipsoid_levelset():
def test_ellipsoid_stats():
# Test comparison values generated by Wolfram Alpha
vol, surf = ellipsoid_stats(6, 10, 16)
assert(round(1280 * np.pi, 4) == round(vol, 4))
assert(1383.28 == round(surf, 2))
assert_allclose(1280 * np.pi, vol, atol=1e-4)
assert_allclose(1383.28, surf, atol=1e-2)
# Test when a <= b <= c does not hold
vol, surf = ellipsoid_stats(16, 6, 10)
assert(round(1280 * np.pi, 4) == round(vol, 4))
assert(1383.28 == round(surf, 2))
assert_allclose(1280 * np.pi, vol, atol=1e-4)
assert_allclose(1383.28, surf, atol=1e-2)
# Larger test to ensure reliability over broad range
vol, surf = ellipsoid_stats(17, 27, 169)
assert(round(103428 * np.pi, 4) == round(vol, 4))
assert(37426.3 == round(surf, 1))
assert_allclose(103428 * np.pi, vol, atol=1e-4)
assert_allclose(37426.3, surf, atol=1e-1)
if __name__ == "__main__":
+1
View File
@@ -4,6 +4,7 @@ from .exposure import histogram, equalize, equalize_hist, \
from ._adapthist import equalize_adapthist
__all__ = ['histogram',
'equalize',
'equalize_hist',
+21 -9
View File
@@ -7,8 +7,16 @@ from skimage._shared.utils import deprecated
__all__ = ['histogram', 'cumulative_distribution', 'equalize',
'rescale_intensity', 'adjust_gamma',
'adjust_log', 'adjust_sigmoid']
'rescale_intensity', 'adjust_gamma', 'adjust_log', 'adjust_sigmoid']
DTYPE_RANGE = dtype_range.copy()
DTYPE_RANGE.update((d.__name__, limits) for d, limits in dtype_range.items())
DTYPE_RANGE.update({'uint10': (0, 2**10 - 1),
'uint12': (0, 2**12 - 1),
'uint14': (0, 2**14 - 1),
'bool': dtype_range[np.bool_],
'float': dtype_range[np.float64]})
def histogram(image, nbins=256):
@@ -143,14 +151,15 @@ def rescale_intensity(image, in_range=None, out_range=None):
----------
image : array
Image array.
in_range : 2-tuple (float, float)
in_range : 2-tuple (float, float) or str
Min and max *allowed* intensity values of input image. If None, the
*allowed* min/max values are set to the *actual* min/max values in the
input image.
out_range : 2-tuple (float, float)
input image. Intensity values outside this range are clipped.
If string, use data limits of dtype specified by the string.
out_range : 2-tuple (float, float) or str
Min and max intensity values of output image. If None, use the min/max
intensities of the image data type. See `skimage.util.dtype` for
details.
details. If string, use data limits of dtype specified by the string.
Returns
-------
@@ -201,11 +210,14 @@ def rescale_intensity(image, in_range=None, out_range=None):
if in_range is None:
imin = np.min(image)
imax = np.max(image)
elif in_range in DTYPE_RANGE:
imin, imax = DTYPE_RANGE[in_range]
else:
imin, imax = in_range
if out_range is None:
omin, omax = dtype_range[dtype]
if out_range is None or out_range in DTYPE_RANGE:
out_range = dtype if out_range is None else out_range
omin, omax = DTYPE_RANGE[out_range]
if imin >= 0:
omin = 0
else:
@@ -263,7 +275,7 @@ def adjust_gamma(image, gamma=1, gain=1):
dtype = image.dtype.type
if gamma < 0:
return "Gamma should be a non-negative real number"
raise ValueError("Gamma should be a non-negative real number.")
scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0])
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python
import os
from skimage._build import cython
base_path = os.path.abspath(os.path.dirname(__file__))
def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs
config = Configuration('exposure', parent_package, top_path)
config.add_data_dir('tests')
return config
if __name__ == '__main__':
from numpy.distutils.core import setup
setup(maintainer='scikit-image Developers',
author='scikit-image Developers',
maintainer_email='scikit-image@googlegroups.com',
description='Exposure corrections',
url='https://github.com/scikit-image/scikit-image',
license='SciPy License (BSD Style)',
**(configuration(top_path='').todict())
)
+42 -1
View File
@@ -2,7 +2,7 @@ import warnings
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
from numpy.testing import assert_array_equal
from numpy.testing import assert_array_equal, assert_raises
import skimage
from skimage import data
from skimage import exposure
@@ -44,6 +44,13 @@ def check_cdf_slope(cdf):
# Test rescale intensity
# ======================
uint10_max = 2**10 - 1
uint12_max = 2**12 - 1
uint14_max = 2**14 - 1
uint16_max = 2**16 - 1
def test_rescale_stretch():
image = np.array([51, 102, 153], dtype=np.uint8)
out = exposure.rescale_intensity(image)
@@ -76,6 +83,30 @@ def test_rescale_out_range():
assert_close(out, [0, 63, 127])
def test_rescale_named_in_range():
image = np.array([0, uint10_max, uint10_max + 100], dtype=np.uint16)
out = exposure.rescale_intensity(image, in_range='uint10')
assert_close(out, [0, uint16_max, uint16_max])
def test_rescale_named_out_range():
image = np.array([0, uint16_max], dtype=np.uint16)
out = exposure.rescale_intensity(image, out_range='uint10')
assert_close(out, [0, uint10_max])
def test_rescale_uint12_limits():
image = np.array([0, uint16_max], dtype=np.uint16)
out = exposure.rescale_intensity(image, out_range='uint12')
assert_close(out, [0, uint12_max])
def test_rescale_uint14_limits():
image = np.array([0, uint16_max], dtype=np.uint16)
out = exposure.rescale_intensity(image, out_range='uint14')
assert_close(out, [0, uint14_max])
# Test adaptive histogram equalization
# ====================================
@@ -230,6 +261,11 @@ def test_adjust_gamma_greater_one():
assert_array_equal(result, expected)
def test_adjust_gamma_neggative():
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
assert_raises(ValueError, exposure.adjust_gamma, image, -1)
# Test Logarithmic Correction
# ===========================
@@ -336,3 +372,8 @@ def test_adjust_inv_sigmoid_cutoff_half():
result = exposure.adjust_sigmoid(image, 0.5, 10, True)
assert_array_equal(result, expected)
def test_neggative():
image = np.arange(-10, 245, 4).reshape(8, 8).astype(np.double)
assert_raises(ValueError, exposure.adjust_gamma, image)
+24 -3
View File
@@ -4,9 +4,17 @@ from .texture import greycomatrix, greycoprops, local_binary_pattern
from .peak import peak_local_max
from .corner import (corner_kitchen_rosenfeld, corner_harris,
corner_shi_tomasi, corner_foerstner, corner_subpix,
corner_peaks)
from .corner_cy import corner_moravec
corner_peaks, corner_fast, structure_tensor,
structure_tensor_eigvals, hessian_matrix,
hessian_matrix_eigvals)
from .corner_cy import corner_moravec, corner_orientations
from .template import match_template
from .brief import BRIEF
from .censure import CENSURE
from .orb import ORB
from .match import match_descriptors
from .util import plot_matches
from .blob import blob_dog, blob_log
__all__ = ['daisy',
@@ -15,6 +23,10 @@ __all__ = ['daisy',
'greycoprops',
'local_binary_pattern',
'peak_local_max',
'structure_tensor',
'structure_tensor_eigvals',
'hessian_matrix',
'hessian_matrix_eigvals',
'corner_kitchen_rosenfeld',
'corner_harris',
'corner_shi_tomasi',
@@ -22,4 +34,13 @@ __all__ = ['daisy',
'corner_subpix',
'corner_peaks',
'corner_moravec',
'match_template']
'corner_fast',
'corner_orientations',
'match_template',
'BRIEF',
'CENSURE',
'ORB',
'match_descriptors',
'plot_matches',
'blob_dog',
'blob_log']
-229
View File
@@ -1,229 +0,0 @@
import numpy as np
from scipy.ndimage.filters import gaussian_filter
from ..util import img_as_float
from .util import _mask_border_keypoints, pairwise_hamming_distance
from ._brief_cy import _brief_loop
def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49,
sample_seed=1, variance=2):
"""**Experimental function**.
Extract BRIEF Descriptor about given keypoints for a given image.
Parameters
----------
image : 2D ndarray
Input image.
keypoints : (P, 2) ndarray
Array of keypoint locations in the format (row, col).
descriptor_size : int
Size of BRIEF descriptor about each keypoint. Sizes 128, 256 and 512
preferred by the authors. Default is 256.
mode : string
Probability distribution for sampling location of decision pixel-pairs
around keypoints. Default is 'normal' otherwise uniform.
patch_size : int
Length of the two dimensional square patch sampling region around
the keypoints. Default is 49.
sample_seed : int
Seed for sampling the decision pixel-pairs. From a square window with
length patch_size, pixel pairs are sampled using the `mode` parameter
to build the descriptors using intensity comparison. The value of
`sample_seed` should be the same for the images to be matched while
building the descriptors. Default is 1.
variance : float
Variance of the Gaussian Low Pass filter applied on the image to
alleviate noise sensitivity. Default is 2.
Returns
-------
descriptors : (Q, `descriptor_size`) ndarray of dtype bool
2D ndarray of binary descriptors of size `descriptor_size` about Q
keypoints after filtering out border keypoints with value at an index
(i, j) either being True or False representing the outcome
of Intensity comparison about ith keypoint on jth decision pixel-pair.
keypoints : (Q, 2) ndarray
Location i.e. (row, col) of keypoints after removing out those that
are near border.
References
----------
.. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha and Pascal Fua
"BRIEF : Binary robust independent elementary features",
http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf
Examples
--------
>> from skimage.feature import corner_peaks, corner_harris, \\
.. pairwise_hamming_distance, brief, match_keypoints_brief
>> square1 = np.zeros([8, 8], dtype=np.int32)
>> square1[2:6, 2:6] = 1
>> square1
array([[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
>> keypoints1
array([[2, 2],
[2, 5],
[5, 2],
[5, 5]])
>> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size=5)
>> keypoints1
array([[2, 2],
[2, 5],
[5, 2],
[5, 5]])
>> square2 = np.zeros([9, 9], dtype=np.int32)
>> square2[2:7, 2:7] = 1
>> square2
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
>> keypoints2
array([[2, 2],
[2, 6],
[6, 2],
[6, 6]])
>> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size=5)
>> keypoints2
array([[2, 2],
[2, 6],
[6, 2],
[6, 6]])
>> pairwise_hamming_distance(descriptors1, descriptors2)
array([[ 0.03125 , 0.3203125, 0.3671875, 0.6171875],
[ 0.3203125, 0.03125 , 0.640625 , 0.375 ],
[ 0.375 , 0.6328125, 0.0390625, 0.328125 ],
[ 0.625 , 0.3671875, 0.34375 , 0.0234375]])
>> match_keypoints_brief(keypoints1, descriptors1,
.. keypoints2, descriptors2)
array([[[ 2, 2],
[ 2, 2]],
[[ 2, 5],
[ 2, 6]],
[[ 5, 2],
[ 6, 2]],
[[ 5, 5],
[ 6, 6]]])
"""
np.random.seed(sample_seed)
image = np.squeeze(image)
if image.ndim != 2:
raise ValueError("Only 2-D gray-scale images supported.")
image = img_as_float(image)
# Gaussian Low pass filtering to alleviate noise
# sensitivity
image = gaussian_filter(image, variance)
image = np.ascontiguousarray(image)
keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C')
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
keypoints = keypoints[_mask_border_keypoints(image, keypoints, patch_size // 2)]
keypoints = np.ascontiguousarray(keypoints)
descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool,
order='C')
# Sampling pairs of decision pixels in patch_size x patch_size window
if mode == 'normal':
samples = (patch_size / 5.0) * np.random.randn(descriptor_size * 8)
samples = np.array(samples, dtype=np.int32)
samples = samples[(samples < (patch_size // 2))
& (samples > - (patch_size - 2) // 2)]
pos1 = samples[:descriptor_size * 2]
pos1 = pos1.reshape(descriptor_size, 2)
pos2 = samples[descriptor_size * 2:descriptor_size * 4]
pos2 = pos2.reshape(descriptor_size, 2)
else:
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(descriptor_size * 2, 2))
pos1, pos2 = np.split(samples, 2)
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
_brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2)
return descriptors, keypoints
def match_keypoints_brief(keypoints1, descriptors1, keypoints2,
descriptors2, threshold=0.15):
"""**Experimental function**.
Match keypoints described using BRIEF descriptors in one image to
those in second image.
Parameters
----------
keypoints1 : (M, 2) ndarray
M Keypoints from the first image described using skimage.feature.brief
descriptors1 : (M, P) ndarray
BRIEF descriptors of size P about M keypoints in the first image.
keypoints2 : (N, 2) ndarray
N Keypoints from the second image described using skimage.feature.brief
descriptors2 : (N, P) ndarray
BRIEF descriptors of size P about N keypoints in the second image.
threshold : float in range [0, 1]
Maximum allowable hamming distance between descriptors of two keypoints
in separate images to be regarded as a match. Default is 0.15.
Returns
-------
match_keypoints_brief : (Q, 2, 2) ndarray
Location of Q matched keypoint pairs from two images.
"""
if (keypoints1.shape[0] != descriptors1.shape[0]
or keypoints2.shape[0] != descriptors2.shape[0]):
raise ValueError("The number of keypoints and number of described "
"keypoints do not match. Make the optional parameter "
"return_keypoints True to get described keypoints.")
if descriptors1.shape[1] != descriptors2.shape[1]:
raise ValueError("Descriptor sizes for matching keypoints in both "
"the images should be equal.")
# Get hamming distances between keeypoints1 and keypoints2
distance = pairwise_hamming_distance(descriptors1, descriptors2)
temp = distance > threshold
row_check = np.any(~temp, axis=1)
matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)]
matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp)
matched_keypoint_pairs[:, 0, :] = keypoints1[row_check]
matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check]
return matched_keypoint_pairs
+4 -4
View File
@@ -94,15 +94,15 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8,
'''
# Validate image format.
if img.ndim > 2:
if img.ndim != 2:
raise ValueError('Only grey-level images are supported.')
if img.dtype.kind != 'f':
img = img_as_float(img)
img = img_as_float(img)
# Validate parameters.
if sigmas is not None and ring_radii is not None \
and len(sigmas) - 1 != len(ring_radii):
raise ValueError('len(sigmas)-1 != len(ring_radii)')
raise ValueError('`len(sigmas)-1 != len(ring_radii)`')
if ring_radii is not None:
rings = len(ring_radii)
radius = ring_radii[-1]
-226
View File
@@ -1,226 +0,0 @@
"""
Compute grey level co-occurrence matrices (GLCMs) and associated
properties to characterize image textures.
"""
import numpy as np
from ._texture import _glcm_loop
def greycomatrix(image, distances, angles, levels=256, symmetric=False,
normed=False):
"""Calculate the grey-level co-occurrence matrix.
A grey level co-occurence matrix is a histogram of co-occuring
greyscale values at a given offset over an image.
Parameters
----------
image : array_like of uint8
Integer typed input image. The image will be cast to uint8, so
the maximum value must be less than 256.
distances : array_like
List of pixel pair distance offsets.
angles : array_like
List of pixel pair angles in radians.
levels : int, optional
The input image should contain integers in [0, levels-1],
where levels indicate the number of grey-levels counted
(typically 256 for an 8-bit image). The maximum value is
256.
symmetric : bool, optional
If True, the output matrix `P[:, :, d, theta]` is symmetric. This
is accomplished by ignoring the order of value pairs, so both
(i, j) and (j, i) are accumulated when (i, j) is encountered
for a given offset. The default is False.
normed : bool, optional
If True, normalize each matrix `P[:, :, d, theta]` by dividing
by the total number of accumulated co-occurrences for the given
offset. The elements of the resulting matrix sum to 1. The
default is False.
Returns
-------
P : 4-D ndarray
The grey-level co-occurrence histogram. The value
`P[i,j,d,theta]` is the number of times that grey-level `j`
occurs at a distance `d` and at an angle `theta` from
grey-level `i`. If `normed` is `False`, the output is of
type uint32, otherwise it is float64.
References
----------
.. [1] The GLCM Tutorial Home Page,
http://www.fp.ucalgary.ca/mhallbey/tutorial.htm
.. [2] Pattern Recognition Engineering, Morton Nadler & Eric P.
Smith
.. [3] Wikipedia, http://en.wikipedia.org/wiki/Co-occurrence_matrix
Examples
--------
Compute 2 GLCMs: One for a 1-pixel offset to the right, and one
for a 1-pixel offset upwards.
>>> image = np.array([[0, 0, 1, 1],
... [0, 0, 1, 1],
... [0, 2, 2, 2],
... [2, 2, 3, 3]], dtype=np.uint8)
>>> result = greycomatrix(image, [1], [0, np.pi/2], levels=4)
>>> result[:, :, 0, 0]
array([[2, 2, 1, 0],
[0, 2, 0, 0],
[0, 0, 3, 1],
[0, 0, 0, 1]], dtype=uint32)
>>> result[:, :, 0, 1]
array([[3, 0, 2, 0],
[0, 2, 2, 0],
[0, 0, 1, 2],
[0, 0, 0, 0]], dtype=uint32)
"""
assert levels <= 256
image = np.ascontiguousarray(image)
assert image.ndim == 2
assert image.min() >= 0
assert image.max() < levels
image = image.astype(np.uint8)
distances = np.ascontiguousarray(distances, dtype=np.float64)
angles = np.ascontiguousarray(angles, dtype=np.float64)
assert distances.ndim == 1
assert angles.ndim == 1
P = np.zeros((levels, levels, len(distances), len(angles)),
dtype=np.uint32, order='C')
# count co-occurences
_glcm_loop(image, distances, angles, levels, P)
# make each GLMC symmetric
if symmetric:
Pt = np.transpose(P, (1, 0, 2, 3))
P = P + Pt
# normalize each GLMC
if normed:
P = P.astype(np.float64)
glcm_sums = np.apply_over_axes(np.sum, P, axes=(0, 1))
glcm_sums[glcm_sums == 0] = 1
P /= glcm_sums
return P
def greycoprops(P, prop='contrast'):
"""Calculate texture properties of a GLCM.
Compute a feature of a grey level co-occurrence matrix to serve as
a compact summary of the matrix. The properties are computed as
follows:
- 'contrast': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}(i-j)^2`
- 'dissimilarity': :math:`\\sum_{i,j=0}^{levels-1}P_{i,j}|i-j|`
- 'homogeneity': :math:`\\sum_{i,j=0}^{levels-1}\\frac{P_{i,j}}{1+(i-j)^2}`
- 'ASM': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}^2`
- 'energy': :math:`\\sqrt{ASM}`
- 'correlation':
.. math:: \\sum_{i,j=0}^{levels-1} P_{i,j}\\left[\\frac{(i-\\mu_i) \\
(j-\\mu_j)}{\\sqrt{(\\sigma_i^2)(\\sigma_j^2)}}\\right]
Parameters
----------
P : ndarray
Input array. `P` is the grey-level co-occurrence histogram
for which to compute the specified property. The value
`P[i,j,d,theta]` is the number of times that grey-level j
occurs at a distance d and at an angle theta from
grey-level i.
prop : {'contrast', 'dissimilarity', 'homogeneity', 'energy', \
'correlation', 'ASM'}, optional
The property of the GLCM to compute. The default is 'contrast'.
Returns
-------
results : 2-D ndarray
2-dimensional array. `results[d, a]` is the property 'prop' for
the d'th distance and the a'th angle.
References
----------
.. [1] The GLCM Tutorial Home Page,
http://www.fp.ucalgary.ca/mhallbey/tutorial.htm
Examples
--------
Compute the contrast for GLCMs with distances [1, 2] and angles
[0 degrees, 90 degrees]
>>> image = np.array([[0, 0, 1, 1],
... [0, 0, 1, 1],
... [0, 2, 2, 2],
... [2, 2, 3, 3]], dtype=np.uint8)
>>> g = greycomatrix(image, [1, 2], [0, np.pi/2], levels=4,
... normed=True, symmetric=True)
>>> contrast = greycoprops(g, 'contrast')
>>> contrast
array([[ 0.58333333, 1. ],
[ 1.25 , 2.75 ]])
"""
assert P.ndim == 4
(num_level, num_level2, num_dist, num_angle) = P.shape
assert num_level == num_level2
assert num_dist > 0
assert num_angle > 0
# create weights for specified property
I, J = np.ogrid[0:num_level, 0:num_level]
if prop == 'contrast':
weights = (I - J)**2
elif prop == 'dissimilarity':
weights = np.abs(I - J)
elif prop == 'homogeneity':
weights = 1. / (1. + (I - J)**2)
elif prop in ['ASM', 'energy', 'correlation']:
pass
else:
raise ValueError('%s is an invalid property' % (prop))
# compute property for each GLCM
if prop == 'energy':
asm = np.apply_over_axes(np.sum, (P**2), axes=(0, 1))[0, 0]
results = np.sqrt(asm)
elif prop == 'ASM':
results = np.apply_over_axes(np.sum, (P**2), axes=(0, 1))[0, 0]
elif prop == 'correlation':
results = np.zeros((num_dist, num_angle), dtype=np.float64)
I = np.array(range(num_level)).reshape((num_level, 1, 1, 1))
J = np.array(range(num_level)).reshape((1, num_level, 1, 1))
diff_i = I - np.apply_over_axes(np.sum, (I * P), axes=(0, 1))[0, 0]
diff_j = J - np.apply_over_axes(np.sum, (J * P), axes=(0, 1))[0, 0]
std_i = np.sqrt(np.apply_over_axes(np.sum, (P * (diff_i)**2),
axes=(0, 1))[0, 0])
std_j = np.sqrt(np.apply_over_axes(np.sum, (P * (diff_j)**2),
axes=(0, 1))[0, 0])
cov = np.apply_over_axes(np.sum, (P * (diff_i * diff_j)),
axes=(0, 1))[0, 0]
# handle the special case of standard deviations near zero
mask_0 = std_i < 1e-15
mask_0[std_j < 1e-15] = True
results[mask_0] = 1
# handle the standard case
mask_1 = mask_0 == False
results[mask_1] = cov[mask_1] / (std_i[mask_1] * std_j[mask_1])
elif prop in ['contrast', 'dissimilarity', 'homogeneity']:
weights = weights.reshape((num_level, num_level, 1, 1))
results = np.apply_over_axes(np.sum, (P * weights), axes=(0, 1))[0, 0]
return results
-97
View File
@@ -1,97 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
"""
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 numpy as np
from scipy.signal import fftconvolve
cimport numpy as cnp
from libc.math cimport sqrt, fabs
from skimage._shared.transform cimport integrate
from skimage.transform import integral
def match_template(cnp.ndarray[float, ndim=2, mode="c"] image,
cnp.ndarray[float, ndim=2, mode="c"] template):
cdef float[:, ::1] corr
cdef float[:, ::1] image_sat
cdef float[:, ::1] image_sqr_sat
cdef float template_mean = np.mean(template)
cdef float template_ssd
cdef float inv_area
cdef Py_ssize_t r, c, r_end, c_end
cdef Py_ssize_t template_rows = template.shape[0]
cdef Py_ssize_t template_cols = template.shape[1]
cdef float den, window_sqr_sum, window_mean_sqr, window_sum
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)
# move window through convolution results, normalizing in the process
for r in range(corr.shape[0]):
for c in range(corr.shape[1]):
# subtract 1 because `i_end` and `c_end` are used for indexing into
# summed-area table, instead of slicing windows of the image.
r_end = r + template_rows - 1
c_end = c + template_cols - 1
window_sum = integrate(image_sat, r, c, r_end, c_end)
window_mean_sqr = window_sum * window_sum * inv_area
window_sqr_sum = integrate(image_sqr_sat, r, c, r_end, c_end)
if window_sqr_sum <= window_mean_sqr:
corr[r, c] = 0
continue
den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd)
corr[r, c] /= den
return np.asarray(corr)
+300
View File
@@ -0,0 +1,300 @@
import numpy as np
from scipy.ndimage.filters import gaussian_filter, gaussian_laplace
import itertools as itt
import math
from math import sqrt, hypot, log
from numpy import arccos
from skimage.util import img_as_float
from .peak import peak_local_max
# This basic blob detection algorithm is based on:
# http://www.cs.utah.edu/~jfishbau/advimproc/project1/ (04.04.2013)
# Theory behind: http://en.wikipedia.org/wiki/Blob_detection (04.04.2013)
def _blob_overlap(blob1, blob2):
"""Finds the overlapping area fraction between two blobs.
Returns a float representing fraction of overlapped area.
Parameters
----------
blob1 : sequence
A sequence of ``(y,x,sigma)``, where ``x,y`` are coordinates of blob
and sigma is the standard deviation of the Gaussian kernel which
detected the blob.
blob2 : sequence
A sequence of ``(y,x,sigma)``, where ``x,y`` are coordinates of blob
and sigma is the standard deviation of the Gaussian kernel which
detected the blob.
Returns
-------
f : float
Fraction of overlapped area.
"""
root2 = sqrt(2)
# extent of the blob is given by sqrt(2)*scale
r1 = blob1[2] * root2
r2 = blob2[2] * root2
d = hypot(blob1[0] - blob2[0], blob1[1] - blob2[1])
if d > r1 + r2:
return 0
# one blob is inside the other, the smaller blob must die
if d <= abs(r1 - r2):
return 1
acos1 = arccos((d ** 2 + r1 ** 2 - r2 ** 2) / (2 * d * r1))
acos2 = arccos((d ** 2 + r2 ** 2 - r1 ** 2) / (2 * d * r2))
a = -d + r2 + r1
b = d - r2 + r1
c = d + r2 - r1
d = d + r2 + r1
area = r1 ** 2 * acos1 + r2 ** 2 * acos2 - 0.5 * sqrt(abs(a * b * c * d))
return area / (math.pi * (min(r1, r2) ** 2))
def _prune_blobs(blobs_array, overlap):
"""Eliminated blobs with area overlap.
Parameters
----------
blobs_array : ndarray
A 2d array with each row representing 3 values, ``(y,x,sigma)``
where ``(y,x)`` are coordinates of the blob and ``sigma`` is the
standard deviation of the Gaussian kernel which detected the blob.
overlap : float
A value between 0 and 1. If the fraction of area overlapping for 2
blobs is greater than `overlap` the smaller blob is eliminated.
Returns
-------
A : ndarray
`array` with overlapping blobs removed.
"""
# iterating again might eliminate more blobs, but one iteration suffices
# for most cases
for blob1, blob2 in itt.combinations(blobs_array, 2):
if _blob_overlap(blob1, blob2) > overlap:
if blob1[2] > blob2[2]:
blob2[2] = -1
else:
blob1[2] = -1
# return blobs_array[blobs_array[:, 2] > 0]
return np.array([b for b in blobs_array if b[2] > 0])
def blob_dog(image, min_sigma=1, max_sigma=50, sigma_ratio=1.6, threshold=2.0,
overlap=.5,):
"""Finds blobs in the given grayscale image.
Blobs are found using the Difference of Gaussian (DoG) method [1]_.
For each blob found, the method returns its coordinates and the standard
deviation of the Gaussian kernel that detected the blob.
Parameters
----------
image : ndarray
Input grayscale image, blobs are assumed to be light on dark
background (white on black).
min_sigma : float, optional
The minimum standard deviation for Gaussian Kernel. Keep this low to
detect smaller blobs.
max_sigma : float, optional
The maximum standard deviation for Gaussian Kernel. Keep this high to
detect larger blobs.
sigma_ratio : float, optional
The ratio between the standard deviation of Gaussian Kernels used for
computing the Difference of Gaussians
threshold : float, optional.
The absolute lower bound for scale space maxima. Local maxima smaller
than thresh are ignored. Reduce this to detect blobs with less
intensities.
overlap : float, optional
A value between 0 and 1. If the area of two blobs overlaps by a
fraction greater than `threshold`, the smaller blob is eliminated.
Returns
-------
A : (n, 3) ndarray
A 2d array with each row representing 3 values, ``(y,x,sigma)``
where ``(y,x)`` are coordinates of the blob and ``sigma`` is the
standard deviation of the Gaussian kernel which detected the blob.
References
----------
.. [1] http://en.wikipedia.org/wiki/Blob_detection#The_difference_of_Gaussians_approach
Examples
--------
>>> from skimage import data, feature
>>> feature.blob_dog(data.coins(), threshold=.5, max_sigma=40)
array([[ 45, 336, 16],
[ 52, 155, 16],
[ 52, 216, 16],
[ 54, 42, 16],
[ 54, 276, 10],
[ 58, 100, 10],
[120, 272, 16],
[124, 337, 10],
[125, 45, 16],
[125, 208, 10],
[127, 102, 10],
[128, 154, 10],
[185, 347, 16],
[193, 213, 16],
[194, 277, 16],
[195, 102, 16],
[196, 43, 10],
[198, 155, 10],
[260, 46, 16],
[261, 173, 16],
[263, 245, 16],
[263, 302, 16],
[267, 115, 10],
[267, 359, 16]])
Notes
-----
The radius of each blob is approximately :math:`\sqrt{2}sigma`.
"""
if image.ndim != 2:
raise ValueError("'image' must be a grayscale ")
image = img_as_float(image)
# k such that min_sigma*(sigma_ratio**k) > max_sigma
k = int(log(float(max_sigma) / min_sigma, sigma_ratio)) + 1
# a geometric progression of standard deviations for gaussian kernels
sigma_list = np.array([min_sigma * (sigma_ratio ** i)
for i in range(k + 1)])
gaussian_images = [gaussian_filter(image, s) for s in sigma_list]
# computing difference between two successive Gaussian blurred images
# multiplying with standard deviation provides scale invariance
dog_images = [(gaussian_images[i] - gaussian_images[i + 1])
* sigma_list[i] for i in range(k)]
image_cube = np.dstack(dog_images)
# local_maxima = get_local_maxima(image_cube, threshold)
local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
footprint=np.ones((3, 3, 3)),
threshold_rel=0.0,
exclude_border=False)
# Convert the last index to its corresponding scale value
local_maxima[:, 2] = sigma_list[local_maxima[:, 2]]
return _prune_blobs(local_maxima, overlap)
def blob_log(image, min_sigma=1, max_sigma=50, num_sigma=10, threshold=.2,
overlap=.5, log_scale=False):
"""Finds blobs in the given grayscale image.
Blobs are found using the Laplacian of Gaussian (LoG) method [1]_.
For each blob found, the method returns its coordinates and the standard
deviation of the Gaussian kernel that detected the blob.
Parameters
----------
image : ndarray
Input grayscale image, blobs are assumed to be light on dark
background (white on black).
min_sigma : float, optional
The minimum standard deviation for Gaussian Kernel. Keep this low to
detect smaller blobs.
max_sigma : float, optional
The maximum standard deviation for Gaussian Kernel. Keep this high to
detect larger blobs.
num_sigma : int, optional
The number of intermediate values of standard deviations to consider
between `min_sigma` and `max_sigma`.
threshold : float, optional.
The absolute lower bound for scale space maxima. Local maxima smaller
than thresh are ignored. Reduce this to detect blobs with less
intensities.
overlap : float, optional
A value between 0 and 1. If the area of two blobs overlaps by a
fraction greater than `threshold`, the smaller blob is eliminated.
log_scale : bool, optional
If set intermediate values of standard deviations are interpolated
using a logarithmic scale to the base `10`. If not, linear
interpolation is used.
Returns
-------
A : (n, 3) ndarray
A 2d array with each row representing 3 values, ``(y,x,sigma)``
where ``(y,x)`` are coordinates of the blob and ``sigma`` is the
standard deviation of the Gaussian kernel which detected the blob.
References
----------
.. [1] http://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian
Examples
--------
>>> from skimage import data, feature, exposure
>>> img = data.coins()
>>> img = exposure.equalize_hist(img) # improves detection
>>> feature.blob_log(img, threshold = .3)
array([[113, 323, 1],
[121, 272, 17],
[124, 336, 11],
[126, 46, 11],
[126, 208, 11],
[127, 102, 11],
[128, 154, 11],
[185, 344, 17],
[194, 213, 17],
[194, 276, 17],
[197, 44, 11],
[198, 103, 11],
[198, 155, 11],
[260, 174, 17],
[263, 244, 17],
[263, 302, 17],
[266, 115, 11]])
Notes
-----
The radius of each blob is approximately :math:`\sqrt{2}sigma`.
"""
if image.ndim != 2:
raise ValueError("'image' must be a grayscale ")
image = img_as_float(image)
if log_scale:
start, stop = log(min_sigma, 10), log(max_sigma, 10)
sigma_list = np.logspace(start, stop, num_sigma)
else:
sigma_list = np.linspace(min_sigma, max_sigma, num_sigma)
#computing gaussian laplace
#s**2 provides scale invariance
gl_images = [-gaussian_laplace(image, s) * s ** 2 for s in sigma_list]
image_cube = np.dstack(gl_images)
local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
footprint=np.ones((3, 3, 3)),
threshold_rel=0.0,
exclude_border=False)
# Convert the last index to its corresponding scale value
local_maxima[:, 2] = sigma_list[local_maxima[:, 2]]
return _prune_blobs(local_maxima, overlap)
+181
View File
@@ -0,0 +1,181 @@
import numpy as np
from scipy.ndimage.filters import gaussian_filter
from .util import (DescriptorExtractor, _mask_border_keypoints,
_prepare_grayscale_input_2D)
from .brief_cy import _brief_loop
class BRIEF(DescriptorExtractor):
"""BRIEF binary descriptor extractor.
BRIEF (Binary Robust Independent Elementary Features) is an efficient
feature point descriptor. It is highly discriminative even when using
relatively few bits and is computed using simple intensity difference
tests.
For each keypoint, intensity comparisons are carried out for a specifically
distributed number N of pixel-pairs resulting in a binary descriptor of
length N. For binary descriptors the Hamming distance can be used for
feature matching, which leads to lower computational cost in comparison to
the L2 norm.
Parameters
----------
descriptor_size : int, optional
Size of BRIEF descriptor for each keypoint. Sizes 128, 256 and 512
recommended by the authors. Default is 256.
patch_size : int, optional
Length of the two dimensional square patch sampling region around
the keypoints. Default is 49.
mode : {'normal', 'uniform'}, optional
Probability distribution for sampling location of decision pixel-pairs
around keypoints.
sample_seed : int, optional
Seed for the random sampling of the decision pixel-pairs. From a square
window with length `patch_size`, pixel pairs are sampled using the
`mode` parameter to build the descriptors using intensity comparison.
The value of `sample_seed` must be the same for the images to be
matched while building the descriptors.
sigma : float, optional
Standard deviation of the Gaussian low-pass filter applied to the image
to alleviate noise sensitivity, which is strongly recommended to obtain
discriminative and good descriptors.
Attributes
----------
descriptors : (Q, `descriptor_size`) array of dtype bool
2D ndarray of binary descriptors of size `descriptor_size` for Q
keypoints after filtering out border keypoints with value at an
index ``(i, j)`` either being ``True`` or ``False`` representing
the outcome of the intensity comparison for i-th keypoint on j-th
decision pixel-pair. It is ``Q == np.sum(mask)``.
mask : (N, ) array of dtype bool
Mask indicating whether a keypoint has been filtered out
(``False``) or is described in the `descriptors` array (``True``).
Examples
--------
>>> from skimage.feature import (corner_harris, corner_peaks, BRIEF,
... match_descriptors)
>>> import numpy as np
>>> square1 = np.zeros((8, 8), dtype=np.int32)
>>> square1[2:6, 2:6] = 1
>>> square1
array([[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>>> square2 = np.zeros((9, 9), dtype=np.int32)
>>> square2[2:7, 2:7] = 1
>>> square2
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
>>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
>>> extractor = BRIEF(patch_size=5)
>>> extractor.extract(square1, keypoints1)
>>> descriptors1 = extractor.descriptors
>>> extractor.extract(square2, keypoints2)
>>> descriptors2 = extractor.descriptors
>>> matches = match_descriptors(descriptors1, descriptors2)
>>> matches
array([[0, 0],
[1, 1],
[2, 2],
[3, 3]])
>>> keypoints1[matches[:, 0]]
array([[2, 2],
[2, 5],
[5, 2],
[5, 5]])
>>> keypoints2[matches[:, 1]]
array([[2, 2],
[2, 6],
[6, 2],
[6, 6]])
"""
def __init__(self, descriptor_size=256, patch_size=49,
mode='normal', sigma=1, sample_seed=1):
mode = mode.lower()
if mode not in ('normal', 'uniform'):
raise ValueError("`mode` must be 'normal' or 'uniform'.")
self.descriptor_size = descriptor_size
self.patch_size = patch_size
self.mode = mode
self.sigma = sigma
self.sample_seed = sample_seed
self.descriptors = None
self.mask = None
def extract(self, image, keypoints):
"""Extract BRIEF binary descriptors for given keypoints in image.
Parameters
----------
image : 2D array
Input image.
keypoints : (N, 2) array
Keypoint coordinates as ``(row, col)``.
"""
np.random.seed(self.sample_seed)
image = _prepare_grayscale_input_2D(image)
# Gaussian low-pass filtering to alleviate noise sensitivity
image = np.ascontiguousarray(gaussian_filter(image, self.sigma))
# Sampling pairs of decision pixels in patch_size x patch_size window
desc_size = self.descriptor_size
patch_size = self.patch_size
if self.mode == 'normal':
samples = (patch_size / 5.0) * np.random.randn(desc_size * 8)
samples = np.array(samples, dtype=np.int32)
samples = samples[(samples < (patch_size // 2))
& (samples > - (patch_size - 2) // 2)]
pos1 = samples[:desc_size * 2].reshape(desc_size, 2)
pos2 = samples[desc_size * 2:desc_size * 4].reshape(desc_size, 2)
elif self.mode == 'uniform':
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(desc_size * 2, 2))
samples = np.array(samples, dtype=np.int32)
pos1, pos2 = np.split(samples, 2)
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
self.mask = _mask_border_keypoints(image.shape, keypoints,
patch_size // 2)
keypoints = np.array(keypoints[self.mask, :], dtype=np.intp,
order='C', copy=False)
self.descriptors = np.zeros((keypoints.shape[0], desc_size),
dtype=bool, order='C')
_brief_loop(image, self.descriptors.view(np.uint8), keypoints,
pos1, pos2)
@@ -6,7 +6,7 @@
cimport numpy as cnp
def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors,
def _brief_loop(double[:, ::1] image, unsigned char[:, ::1] descriptors,
Py_ssize_t[:, ::1] keypoints,
int[:, ::1] pos0, int[:, ::1] pos1):
+139 -87
View File
@@ -1,9 +1,10 @@
import numpy as np
from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve
from skimage.feature.util import FeatureDetector, _prepare_grayscale_input_2D
from skimage.transform import integral_image
from skimage.feature.corner import _compute_auto_correlation
from skimage.util import img_as_float
from skimage.feature import structure_tensor
from skimage.morphology import octagon, star
from skimage.feature.util import _mask_border_keypoints
@@ -36,7 +37,7 @@ def _filter_image(image, min_scale, max_scale, mode):
# make response[:, :, i] contiguous memory block
item_size = response.itemsize
response.strides = (item_size * response.shape[0], item_size,
response.strides = (item_size * response.shape[1], item_size,
item_size * response.shape[0] * response.shape[1])
integral_img = integral_image(image)
@@ -65,19 +66,19 @@ def _filter_image(image, min_scale, max_scale, mode):
mo, no = OCTAGON_OUTER_SHAPE[min_scale + i - 1]
mi, ni = OCTAGON_INNER_SHAPE[min_scale + i - 1]
response[:, :, i] = convolve(image,
_octagon_filter_kernel(mo, no, mi, ni))
_octagon_kernel(mo, no, mi, ni))
elif mode == 'star':
for i in range(max_scale - min_scale + 1):
m = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][0]]
n = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][1]]
response[:, :, i] = convolve(image, _star_filter_kernel(m, n))
response[:, :, i] = convolve(image, _star_kernel(m, n))
return response
def _octagon_filter_kernel(mo, no, mi, ni):
def _octagon_kernel(mo, no, mi, ni):
outer = (mo + 2 * no)**2 - 2 * no * (no + 1)
inner = (mi + 2 * ni)**2 - 2 * ni * (ni + 1)
outer_weight = 1.0 / (outer - inner)
@@ -91,7 +92,7 @@ def _octagon_filter_kernel(mo, no, mi, ni):
return bfilter
def _star_filter_kernel(m, n):
def _star_kernel(m, n):
c = m + m // 2 - n - n // 2
outer_star = star(m)
inner_star = np.zeros_like(outer_star)
@@ -104,29 +105,25 @@ def _star_filter_kernel(m, n):
def _suppress_lines(feature_mask, image, sigma, line_threshold):
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
feature_mask[(Axx + Ayy) * (Axx + Ayy)
> line_threshold * (Axx * Ayy - Axy * Axy)] = False
Axx, Axy, Ayy = structure_tensor(image, sigma)
feature_mask[(Axx + Ayy) ** 2
> line_threshold * (Axx * Ayy - Axy ** 2)] = False
def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB',
non_max_threshold=0.15, line_threshold=10):
"""**Experimental function**.
Extracts CenSurE keypoints along with the corresponding scale using
either Difference of Boxes, Octagon or STAR bi-level filter.
class CENSURE(FeatureDetector):
Parameters
----------
image : 2D ndarray
Input image.
min_scale : int
"""CENSURE keypoint detector.
min_scale : int, optional
Minimum scale to extract keypoints from.
max_scale : int
max_scale : int, optional
Maximum scale to extract keypoints from. The keypoints will be
extracted from all the scales except the first and the last i.e.
from the scales in the range [min_scale + 1, max_scale - 1].
mode : {'DoB', 'Octagon', 'STAR'}
from the scales in the range [min_scale + 1, max_scale - 1]. The filter
sizes for different scales is such that the two adjacent scales
comprise of an octave.
mode : {'DoB', 'Octagon', 'STAR'}, optional
Type of bi-level filter used to get the scales of the input image.
Possible values are 'DoB', 'Octagon' and 'STAR'. The three modes
represent the shape of the bi-level filters i.e. box(square), octagon
@@ -135,24 +132,24 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB',
weights being uniformly negative in both the inner octagon while
uniformly positive in the difference region. Use STAR and Octagon for
better features and DoB for better performance.
non_max_threshold : float
non_max_threshold : float, optional
Threshold value used to suppress maximas and minimas with a weak
magnitude response obtained after Non-Maximal Suppression.
line_threshold : float
line_threshold : float, optional
Threshold for rejecting interest points which have ratio of principal
curvatures greater than this value.
Returns
-------
Attributes
----------
keypoints : (N, 2) array
Location of the extracted keypoints in the ``(row, col)`` format.
scales : (N, 1) array
The corresponding scale of the N extracted keypoints.
Keypoint coordinates as ``(row, col)``.
scales : (N, ) array
Corresponding scales.
References
----------
.. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas
"CenSurE: Center Surround Extremas for Realtime Feature
"CENSURE: Center Surround Extremas for Realtime Feature
Detection and Matching",
http://link.springer.com/content/pdf/10.1007%2F978-3-540-88693-8_8.pdf
@@ -161,74 +158,129 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB',
Descriptors in the Context of Robot Navigation"
http://www.jamris.org/01_2013/saveas.php?QUEST=JAMRIS_No01_2013_P_11-20.pdf
Examples
--------
>>> from skimage.data import lena
>>> from skimage.color import rgb2gray
>>> from skimage.feature import CENSURE
>>> img = rgb2gray(lena()[100:300, 100:300])
>>> censure = CENSURE()
>>> censure.detect(img)
>>> censure.keypoints
array([[ 71, 148],
[ 77, 186],
[ 78, 189],
[ 89, 174],
[127, 134],
[131, 133],
[134, 125],
[137, 125],
[149, 36],
[162, 165],
[168, 167],
[170, 5],
[171, 29],
[179, 20],
[194, 65]])
>>> censure.scales
array([2, 4, 2, 3, 4, 2, 2, 3, 4, 6, 3, 2, 3, 4, 2])
"""
# (1) First we generate the required scales on the input grayscale image
# using a bi-level filter and stack them up in `filter_response`.
# (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on the
# filter_response to suppress points that are neither minima or maxima in
# 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray `feature_mask`
# containing all the minimas and maximas in `filter_response` as True.
# (3) Then we suppress all the points in the `feature_mask` for which the
# corresponding point in the image at a particular scale has the ratio of
# principal curvatures greater than `line_threshold`.
# (4) Finally, we remove the border keypoints and return the keypoints
# along with its corresponding scale.
def __init__(self, min_scale=1, max_scale=7, mode='DoB',
non_max_threshold=0.15, line_threshold=10):
image = np.squeeze(image)
if image.ndim != 2:
raise ValueError("Only 2-D gray-scale images supported.")
mode = mode.lower()
if mode not in ('dob', 'octagon', 'star'):
raise ValueError("`mode` must be one of 'DoB', 'Octagon', 'STAR'.")
mode = mode.lower()
if mode not in ('dob', 'octagon', 'star'):
raise ValueError('Mode must be one of "DoB", "Octagon", "STAR".')
if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2:
raise ValueError('The scales must be >= 1 and the number of '
'scales should be >= 3.')
if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2:
raise ValueError('The scales must be >= 1 and the number of scales '
'should be >= 3.')
self.min_scale = min_scale
self.max_scale = max_scale
self.mode = mode
self.non_max_threshold = non_max_threshold
self.line_threshold = line_threshold
image = img_as_float(image)
image = np.ascontiguousarray(image)
self.keypoints = None
self.scales = None
# Generating all the scales
filter_response = _filter_image(image, min_scale, max_scale, mode)
def detect(self, image):
"""Detect CENSURE keypoints along with the corresponding scale.
# Suppressing points that are neither minima or maxima in their 3 x 3 x 3
# neighbourhood to zero
minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response
maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response
Parameters
----------
image : 2D ndarray
Input image.
feature_mask = minimas | maximas
feature_mask[filter_response < non_max_threshold] = False
"""
for i in range(1, max_scale - min_scale):
# sigma = (window_size - 1) / 6.0, so the window covers > 99% of the
# kernel's distribution
# window_size = 7 + 2 * (min_scale - 1 + i)
# Hence sigma = 1 + (min_scale - 1 + i)/ 3.0
_suppress_lines(feature_mask[:, :, i], image,
(1 + (min_scale + i - 1) / 3.0), line_threshold)
# (1) First we generate the required scales on the input grayscale
# image using a bi-level filter and stack them up in `filter_response`.
rows, cols, scales = np.nonzero(feature_mask[..., 1:max_scale - min_scale])
keypoints = np.column_stack([rows, cols])
scales = scales + min_scale + 1
# (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on
# the filter_response to suppress points that are neither minima or
# maxima in 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray
# `feature_mask` containing all the minimas and maximas in
# `filter_response` as True.
# (3) Then we suppress all the points in the `feature_mask` for which
# the corresponding point in the image at a particular scale has the
# ratio of principal curvatures greater than `line_threshold`.
# (4) Finally, we remove the border keypoints and return the keypoints
# along with its corresponding scale.
if mode == 'dob':
return keypoints, scales
num_scales = self.max_scale - self.min_scale
cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool)
image = np.ascontiguousarray(_prepare_grayscale_input_2D(image))
if mode == 'octagon':
for i in range(min_scale + 1, max_scale):
c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \
+ OCTAGON_OUTER_SHAPE[i - 1][1]
cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \
& (scales == i)
elif mode == 'star':
for i in range(min_scale + 1, max_scale):
c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \
+ STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2
cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \
& (scales == i)
# Generating all the scales
filter_response = _filter_image(image, self.min_scale, self.max_scale,
self.mode)
return keypoints[cumulative_mask], scales[cumulative_mask]
# Suppressing points that are neither minima or maxima in their
# 3 x 3 x 3 neighborhood to zero
minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response
maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response
feature_mask = minimas | maximas
feature_mask[filter_response < self.non_max_threshold] = False
for i in range(1, num_scales):
# sigma = (window_size - 1) / 6.0, so the window covers > 99% of
# the kernel's distribution
# window_size = 7 + 2 * (min_scale - 1 + i)
# Hence sigma = 1 + (min_scale - 1 + i)/ 3.0
_suppress_lines(feature_mask[:, :, i], image,
(1 + (self.min_scale + i - 1) / 3.0),
self.line_threshold)
rows, cols, scales = np.nonzero(feature_mask[..., 1:num_scales])
keypoints = np.column_stack([rows, cols])
scales = scales + self.min_scale + 1
if self.mode == 'dob':
self.keypoints = keypoints
self.scales = scales
return
cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool)
if self.mode == 'octagon':
for i in range(self.min_scale + 1, self.max_scale):
c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \
+ OCTAGON_OUTER_SHAPE[i - 1][1]
cumulative_mask |= (
_mask_border_keypoints(image.shape, keypoints, c)
& (scales == i))
elif self.mode == 'star':
for i in range(self.min_scale + 1, self.max_scale):
c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \
+ STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2
cumulative_mask |= (
_mask_border_keypoints(image.shape, keypoints, c)
& (scales == i))
self.keypoints = keypoints[cumulative_mask]
self.scales = scales[cumulative_mask]
+287 -33
View File
@@ -1,18 +1,26 @@
import numpy as np
from scipy import ndimage
from scipy import stats
from skimage.color import rgb2grey
from skimage.util import img_as_float, pad
from skimage.feature import peak_local_max
from skimage.feature.util import _prepare_grayscale_input_2D
from skimage.feature.corner_cy import _corner_fast
def _compute_derivatives(image):
def _compute_derivatives(image, mode='constant', cval=0):
"""Compute derivatives in x and y direction using the Sobel operator.
Parameters
----------
image : ndarray
Input image.
mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
How to handle values outside the image borders.
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Returns
-------
@@ -23,14 +31,82 @@ def _compute_derivatives(image):
"""
imy = ndimage.sobel(image, axis=0, mode='constant', cval=0)
imx = ndimage.sobel(image, axis=1, mode='constant', cval=0)
imy = ndimage.sobel(image, axis=0, mode=mode, cval=cval)
imx = ndimage.sobel(image, axis=1, mode=mode, cval=cval)
return imx, imy
def _compute_auto_correlation(image, sigma):
"""Compute auto-correlation matrix using sum of squared differences.
def structure_tensor(image, sigma=1, mode='constant', cval=0):
"""Compute structure tensor using sum of squared differences.
The structure tensor A is defined as::
A = [Axx Axy]
[Axy Ayy]
which is approximated by the weighted sum of squared differences in a local
window around each pixel in the image.
Parameters
----------
image : ndarray
Input image.
sigma : float
Standard deviation used for the Gaussian kernel, which is used as a
weighting function for the local summation of squared differences.
mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
How to handle values outside the image borders.
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Returns
-------
Axx : ndarray
Element of the structure tensor for each pixel in the input image.
Axy : ndarray
Element of the structure tensor for each pixel in the input image.
Ayy : ndarray
Element of the structure tensor for each pixel in the input image.
Examples
--------
>>> from skimage.feature import structure_tensor
>>> square = np.zeros((5, 5))
>>> square[2, 2] = 1
>>> Axx, Axy, Ayy = structure_tensor(square, sigma=0.1)
>>> Axx
array([[ 0., 0., 0., 0., 0.],
[ 0., 1., 0., 1., 0.],
[ 0., 4., 0., 4., 0.],
[ 0., 1., 0., 1., 0.],
[ 0., 0., 0., 0., 0.]])
"""
image = _prepare_grayscale_input_2D(image)
imx, imy = _compute_derivatives(image, mode=mode, cval=cval)
# structure tensore
Axx = ndimage.gaussian_filter(imx * imx, sigma, mode=mode, cval=cval)
Axy = ndimage.gaussian_filter(imx * imy, sigma, mode=mode, cval=cval)
Ayy = ndimage.gaussian_filter(imy * imy, sigma, mode=mode, cval=cval)
return Axx, Axy, Ayy
def hessian_matrix(image, sigma=1, mode='constant', cval=0):
"""Compute Hessian matrix.
The Hessian matrix is defined as::
H = [Hxx Hxy]
[Hxy Hyy]
which is computed by convolving the image with the second derivatives
of the Gaussian kernel in the respective x- and y-directions.
Parameters
----------
@@ -39,32 +115,142 @@ def _compute_auto_correlation(image, sigma):
sigma : float
Standard deviation used for the Gaussian kernel, which is used as
weighting function for the auto-correlation matrix.
mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
How to handle values outside the image borders.
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Returns
-------
Axx : ndarray
Element of the auto-correlation matrix for each pixel in input image.
Axy : ndarray
Element of the auto-correlation matrix for each pixel in input image.
Ayy : ndarray
Element of the auto-correlation matrix for each pixel in input image.
Hxx : ndarray
Element of the Hessian matrix for each pixel in the input image.
Hxy : ndarray
Element of the Hessian matrix for each pixel in the input image.
Hyy : ndarray
Element of the Hessian matrix for each pixel in the input image.
Examples
--------
>>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals
>>> square = np.zeros((5, 5))
>>> square[2, 2] = 1
>>> Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1)
>>> Hxx
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
"""
if image.ndim == 3:
image = img_as_float(rgb2grey(image))
image = _prepare_grayscale_input_2D(image)
imx, imy = _compute_derivatives(image)
# window extent to the left and right, which covers > 99% of the normal
# distribution
window_ext = max(1, np.ceil(3 * sigma))
# structure tensore
Axx = ndimage.gaussian_filter(imx * imx, sigma, mode='constant', cval=0)
Axy = ndimage.gaussian_filter(imx * imy, sigma, mode='constant', cval=0)
Ayy = ndimage.gaussian_filter(imy * imy, sigma, mode='constant', cval=0)
ky, kx = np.mgrid[-window_ext:window_ext + 1, -window_ext:window_ext + 1]
return Axx, Axy, Ayy
# second derivative Gaussian kernels
gaussian_exp = np.exp(-(kx ** 2 + ky ** 2) / (2 * sigma ** 2))
kernel_xx = 1 / (2 * np.pi * sigma ** 4) * (kx ** 2 / sigma ** 2 - 1)
kernel_xx *= gaussian_exp
kernel_xx /= kernel_xx.sum()
kernel_xy = 1 / (2 * np.pi * sigma ** 6) * (kx * ky)
kernel_xy *= gaussian_exp
kernel_xy /= kernel_xx.sum()
kernel_yy = kernel_xx.transpose()
Hxx = ndimage.convolve(image, kernel_xx, mode=mode, cval=cval)
Hxy = ndimage.convolve(image, kernel_xy, mode=mode, cval=cval)
Hyy = ndimage.convolve(image, kernel_yy, mode=mode, cval=cval)
return Hxx, Hxy, Hyy
def corner_kitchen_rosenfeld(image):
def _image_orthogonal_matrix22_eigvals(M00, M01, M11):
l1 = (M00 + M11) / 2 + np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
l2 = (M00 + M11) / 2 - np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
return l1, l2
def structure_tensor_eigvals(Axx, Axy, Ayy):
"""Compute Eigen values of structure tensor.
Parameters
----------
Axx : ndarray
Element of the structure tensor for each pixel in the input image.
Axy : ndarray
Element of the structure tensor for each pixel in the input image.
Ayy : ndarray
Element of the structure tensor for each pixel in the input image.
Returns
-------
l1 : ndarray
Larger eigen value for each input matrix.
l2 : ndarray
Smaller eigen value for each input matrix.
Examples
--------
>>> from skimage.feature import structure_tensor, structure_tensor_eigvals
>>> square = np.zeros((5, 5))
>>> square[2, 2] = 1
>>> Axx, Axy, Ayy = structure_tensor(square, sigma=0.1)
>>> structure_tensor_eigvals(Axx, Axy, Ayy)[0]
array([[ 0., 0., 0., 0., 0.],
[ 0., 2., 4., 2., 0.],
[ 0., 4., 0., 4., 0.],
[ 0., 2., 4., 2., 0.],
[ 0., 0., 0., 0., 0.]])
"""
return _image_orthogonal_matrix22_eigvals(Axx, Axy, Ayy)
def hessian_matrix_eigvals(Hxx, Hxy, Hyy):
"""Compute Eigen values of Hessian matrix.
Parameters
----------
Hxx : ndarray
Element of the Hessian matrix for each pixel in the input image.
Hxy : ndarray
Element of the Hessian matrix for each pixel in the input image.
Hyy : ndarray
Element of the Hessian matrix for each pixel in the input image.
Returns
-------
l1 : ndarray
Larger eigen value for each input matrix.
l2 : ndarray
Smaller eigen value for each input matrix.
Examples
--------
>>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals
>>> square = np.zeros((5, 5))
>>> square[2, 2] = 1
>>> Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1)
>>> hessian_matrix_eigvals(Hxx, Hxy, Hyy)[0]
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
"""
return _image_orthogonal_matrix22_eigvals(Hyy, Hxy, Hyy)
def corner_kitchen_rosenfeld(image, mode='constant', cval=0):
"""Compute Kitchen and Rosenfeld corner measure response image.
The corner measure is calculated as follows::
@@ -79,6 +265,11 @@ def corner_kitchen_rosenfeld(image):
----------
image : ndarray
Input image.
mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
How to handle values outside the image borders.
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Returns
-------
@@ -87,9 +278,9 @@ def corner_kitchen_rosenfeld(image):
"""
imx, imy = _compute_derivatives(image)
imxx, imxy = _compute_derivatives(imx)
imyx, imyy = _compute_derivatives(imy)
imx, imy = _compute_derivatives(image, mode=mode, cval=cval)
imxx, imxy = _compute_derivatives(imx, mode=mode, cval=cval)
imyx, imyy = _compute_derivatives(imy, mode=mode, cval=cval)
numerator = (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)
denominator = (imx**2 + imy**2)
@@ -147,9 +338,9 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
Examples
--------
>>> from skimage.feature import corner_harris, corner_peaks
>>> square = np.zeros([10, 10], dtype=int)
>>> square = np.zeros([10, 10])
>>> square[2:8, 2:8] = 1
>>> square
>>> square.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
@@ -168,7 +359,7 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
Axx, Axy, Ayy = structure_tensor(image, sigma)
# determinant
detA = Axx * Ayy - Axy**2
@@ -217,9 +408,9 @@ def corner_shi_tomasi(image, sigma=1):
Examples
--------
>>> from skimage.feature import corner_shi_tomasi, corner_peaks
>>> square = np.zeros([10, 10], dtype=int)
>>> square = np.zeros([10, 10])
>>> square[2:8, 2:8] = 1
>>> square
>>> square.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
@@ -238,7 +429,7 @@ def corner_shi_tomasi(image, sigma=1):
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
Axx, Axy, Ayy = structure_tensor(image, sigma)
# minimum eigenvalue of A
response = ((Axx + Ayy) - np.sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2
@@ -308,7 +499,7 @@ def corner_foerstner(image, sigma=1):
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
Axx, Axy, Ayy = structure_tensor(image, sigma)
# determinant
detA = Axx * Ayy - Axy**2
@@ -326,6 +517,69 @@ def corner_foerstner(image, sigma=1):
return w, q
def corner_fast(image, n=12, threshold=0.15):
"""Extract FAST corners for a given image.
Parameters
----------
image : 2D ndarray
Input image.
n : int
Minimum number of consecutive pixels out of 16 pixels on the circle
that should all be either brighter or darker w.r.t testpixel.
A point c on the circle is darker w.r.t test pixel p if
`Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also
stands for the n in `FAST-n` corner detector.
threshold : float
Threshold used in deciding whether the pixels on the circle are
brighter, darker or similar w.r.t. the test pixel. Decrease the
threshold when more corners are desired and vice-versa.
Returns
-------
response : ndarray
FAST corner response image.
References
----------
.. [1] Edward Rosten and Tom Drummond
"Machine Learning for high-speed corner detection",
http://www.edwardrosten.com/work/rosten_2006_machine.pdf
.. [2] Wikipedia, "Features from accelerated segment test",
https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test
Examples
--------
>>> from skimage.feature import corner_fast, corner_peaks
>>> square = np.zeros((12, 12))
>>> square[3:9, 3:9] = 1
>>> square.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
>>> corner_peaks(corner_fast(square, 9), min_distance=1)
array([[3, 3],
[3, 8],
[8, 3],
[8, 8]])
"""
image = _prepare_grayscale_input_2D(image)
image = np.ascontiguousarray(image)
response = _corner_fast(image, n, threshold)
return response
def corner_subpix(image, corners, window_size=11, alpha=0.99):
"""Determine subpixel position of corners.
@@ -354,10 +608,10 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99):
Examples
--------
>>> from skimage.feature import corner_harris, corner_peaks, corner_subpix
>>> img = np.zeros((10, 10), dtype=int)
>>> img = np.zeros((10, 10))
>>> img[:5, :5] = 1
>>> img[5:, 5:] = 1
>>> img
>>> img.astype(int)
array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
@@ -408,7 +662,7 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99):
maxx = x0 + wext + 2
window = image[miny:maxy, minx:maxx]
winx, winy = _compute_derivatives(window)
winx, winy = _compute_derivatives(window, mode='constant', cval=0)
# compute gradient suares and remove border
winx_winx = (winx * winx)[1:-1, 1:-1]
+213 -20
View File
@@ -5,9 +5,12 @@
import numpy as np
cimport numpy as cnp
from libc.float cimport DBL_MAX
from libc.math cimport atan2
from skimage.util import img_as_float, pad
from skimage.color import rgb2grey
from skimage.util import img_as_float
from .util import _prepare_grayscale_input_2D
def corner_moravec(image, Py_ssize_t window_size=1):
@@ -30,30 +33,30 @@ def corner_moravec(image, Py_ssize_t window_size=1):
References
----------
..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/moravec.htm
..[2] http://en.wikipedia.org/wiki/Corner_detection
.. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/moravec.htm
.. [2] http://en.wikipedia.org/wiki/Corner_detection
Examples
--------
>>> from skimage.feature import corner_moravec, peak_local_max
>>> from skimage.feature import corner_moravec
>>> square = np.zeros([7, 7])
>>> square[3, 3] = 1
>>> square
array([[ 0., 0., 0., 0., 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., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]])
>>> corner_moravec(square)
array([[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 2., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]])
>>> square.astype(int)
array([[0, 0, 0, 0, 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, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
>>> corner_moravec(square).astype(int)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 2, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
"""
cdef Py_ssize_t rows = image.shape[0]
@@ -80,3 +83,193 @@ def corner_moravec(image, Py_ssize_t window_size=1):
out[r, c] = min_msum
return np.asarray(out)
cdef inline double _corner_fast_response(double curr_pixel,
double* circle_intensities,
char* bins, char state, char n):
cdef char consecutive_count = 0
cdef double curr_response
cdef Py_ssize_t l, m
for l in range(15 + n):
if bins[l % 16] == state:
consecutive_count += 1
if consecutive_count == n:
curr_response = 0
for m in range(16):
curr_response += abs(circle_intensities[m] - curr_pixel)
return curr_response
else:
consecutive_count = 0
return 0
def _corner_fast(double[:, ::1] image, char n, double threshold):
cdef Py_ssize_t rows = image.shape[0]
cdef Py_ssize_t cols = image.shape[1]
cdef Py_ssize_t i, j, k
cdef char speed_sum_b, speed_sum_d
cdef double curr_pixel
cdef double lower_threshold, upper_threshold
cdef double[:, ::1] corner_response = np.zeros((rows, cols),
dtype=np.double)
cdef char *rp = [0, 1, 2, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1]
cdef char *cp = [3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -2, -1, 0, 1, 2, 3]
cdef char bins[16]
cdef double circle_intensities[16]
cdef double curr_response
for i in range(3, rows - 3):
for j in range(3, cols - 3):
curr_pixel = image[i, j]
lower_threshold = curr_pixel - threshold
upper_threshold = curr_pixel + threshold
for k in range(16):
circle_intensities[k] = image[i + rp[k], j + cp[k]]
if circle_intensities[k] > upper_threshold:
# Brighter pixel
bins[k] = 'b'
elif circle_intensities[k] < lower_threshold:
# Darker pixel
bins[k] = 'd'
else:
# Similar pixel
bins[k] = 's'
# High speed test for n >= 12
if n >= 12:
speed_sum_b = 0
speed_sum_d = 0
for k in range(0, 16, 4):
if bins[k] == 'b':
speed_sum_b += 1
elif bins[k] == 'd':
speed_sum_d += 1
if speed_sum_d < 3 and speed_sum_b < 3:
continue
# Test for bright pixels
curr_response = \
_corner_fast_response(curr_pixel, circle_intensities,
bins, 'b', n)
# Test for dark pixels
if curr_response == 0:
curr_response = \
_corner_fast_response(curr_pixel, circle_intensities,
bins, 'd', n)
corner_response[i, j] = curr_response
return np.asarray(corner_response)
def corner_orientations(image, Py_ssize_t[:, :] corners, mask):
"""Compute the orientation of corners.
The orientation of corners is computed using the first order central moment
i.e. the center of mass approach. The corner orientation is the angle of
the vector from the corner coordinate to the intensity centroid in the
local neighborhood around the corner calculated using first order central
moment.
Parameters
----------
image : 2D array
Input grayscale image.
corners : (N, 2) array
Corner coordinates as ``(row, col)``.
mask : 2D array
Mask defining the local neighborhood of the corner used for the
calculation of the central moment.
Returns
-------
orientations : (N, 1) array
Orientations of corners in the range [-pi, pi].
References
----------
.. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
"ORB : An efficient alternative to SIFT and SURF"
http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
.. [2] Paul L. Rosin, "Measuring Corner Properties"
http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf
Examples
--------
>>> from skimage.morphology import octagon
>>> from skimage.feature import (corner_fast, corner_peaks,
... corner_orientations)
>>> square = np.zeros((12, 12))
>>> square[3:9, 3:9] = 1
>>> square.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
>>> corners = corner_peaks(corner_fast(square, 9), min_distance=1)
>>> corners
array([[3, 3],
[3, 8],
[8, 3],
[8, 8]])
>>> orientations = corner_orientations(square, corners, octagon(3, 2))
>>> np.rad2deg(orientations)
array([ 45., 135., -45., -135.])
"""
image = _prepare_grayscale_input_2D(image)
if mask.shape[0] % 2 != 1 or mask.shape[1] % 2 != 1:
raise ValueError("Size of mask must be uneven.")
cdef unsigned char[:, ::1] cmask = np.ascontiguousarray(mask != 0,
dtype=np.uint8)
cdef Py_ssize_t i, r, c, r0, c0
cdef Py_ssize_t mrows = mask.shape[0]
cdef Py_ssize_t mcols = mask.shape[1]
cdef Py_ssize_t mrows2 = (mrows - 1) / 2
cdef Py_ssize_t mcols2 = (mcols - 1) / 2
cdef double[:, :] cimage = pad(image, (mrows2, mcols2), mode='constant',
constant_values=0)
cdef double[:] orientations = np.zeros(corners.shape[0], dtype=np.double)
cdef double curr_pixel
cdef double m01, m10, m01_tmp
for i in range(corners.shape[0]):
r0 = corners[i, 0]
c0 = corners[i, 1]
m01 = 0
m10 = 0
for r in range(mrows):
m01_tmp = 0
for c in range(mcols):
if cmask[r, c]:
curr_pixel = cimage[r0 + r, c0 + c]
m10 += curr_pixel * (c - mcols2)
m01_tmp += curr_pixel
m01 += m01_tmp * (r - mrows2)
orientations[i] = atan2(m01, m10)
return np.asarray(orientations)
+70
View File
@@ -0,0 +1,70 @@
import numpy as np
from scipy.spatial.distance import cdist
def match_descriptors(descriptors1, descriptors2, metric=None, p=2,
max_distance=np.inf, cross_check=True):
"""Brute-force matching of descriptors.
For each descriptor in the first set this matcher finds the closest
descriptor in the second set (and vice-versa in the case of enabled
cross-checking).
Parameters
----------
descriptors1 : (M, P) array
Binary descriptors of size P about M keypoints in the first image.
descriptors2 : (N, P) array
Binary descriptors of size P about N keypoints in the second image.
metric : {'euclidean', 'cityblock', 'minkowski', 'hamming', ...}
The metric to compute the distance between two descriptors. See
`scipy.spatial.distance.cdist` for all possible types. The hamming
distance should be used for binary descriptors. By default the L2-norm
is used for all descriptors of dtype float or double and the Hamming
distance is used for binary descriptors automatically.
p : int
The p-norm to apply for ``metric='minkowski'``.
max_distance : float
Maximum allowed distance between descriptors of two keypoints
in separate images to be regarded as a match.
cross_check : bool
If True, the matched keypoints are returned after cross checking i.e. a
matched pair (keypoint1, keypoint2) is returned if keypoint2 is the
best match for keypoint1 in second image and keypoint1 is the best
match for keypoint2 in first image.
Returns
-------
matches : (Q, 2) array
Indices of corresponding matches in first and second set of
descriptors, where ``matches[:, 0]`` denote the indices in the first
and ``matches[:, 1]`` the indices in the second set of descriptors.
"""
if descriptors1.shape[1] != descriptors2.shape[1]:
raise ValueError("Descriptor length must equal.")
if metric is None:
if np.issubdtype(descriptors1.dtype, np.bool):
metric = 'hamming'
else:
metric = 'euclidean'
distances = cdist(descriptors1, descriptors2, metric=metric, p=p)
indices1 = np.arange(descriptors1.shape[0])
indices2 = np.argmin(distances, axis=1)
if cross_check:
matches1 = np.argmin(distances, axis=0)
mask = indices1 == matches1[indices2]
indices1 = indices1[mask]
indices2 = indices2[mask]
matches = np.column_stack((indices1, indices2))
if max_distance < np.inf:
matches = matches[distances[indices1, indices2] < max_distance]
return matches
+336
View File
@@ -0,0 +1,336 @@
import numpy as np
from skimage.feature.util import (FeatureDetector, DescriptorExtractor,
_mask_border_keypoints,
_prepare_grayscale_input_2D)
from skimage.feature import (corner_fast, corner_orientations, corner_peaks,
corner_harris)
from skimage.transform import pyramid_gaussian
from .orb_cy import _orb_loop
OFAST_MASK = np.zeros((31, 31))
OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3]
for i in range(-15, 16):
for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1):
OFAST_MASK[15 + j, 15 + i] = 1
class ORB(FeatureDetector, DescriptorExtractor):
"""Oriented FAST and rotated BRIEF feature detector and binary descriptor
extractor.
Parameters
----------
n_keypoints : int, optional
Number of keypoints to be returned. The function will return the best
`n_keypoints` according to the Harris corner response if more than
`n_keypoints` are detected. If not, then all the detected keypoints
are returned.
fast_n : int, optional
The `n` parameter in `skimage.feature.corner_fast`. Minimum number of
consecutive pixels out of 16 pixels on the circle that should all be
either brighter or darker w.r.t test-pixel. A point c on the circle is
darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if
``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner
detector.
fast_threshold : float, optional
The ``threshold`` parameter in ``feature.corner_fast``. Threshold used
to decide whether the pixels on the circle are brighter, darker or
similar w.r.t. the test pixel. Decrease the threshold when more
corners are desired and vice-versa.
harris_k : float, optional
The `k` parameter in `skimage.feature.corner_harris`. Sensitivity
factor to separate corners from edges, typically in range ``[0, 0.2]``.
Small values of `k` result in detection of sharp corners.
downscale : float, optional
Downscale factor for the image pyramid. Default value 1.2 is chosen so
that there are more dense scales which enable robust scale invariance
for a subsequent feature description.
n_scales : int, optional
Maximum number of scales from the bottom of the image pyramid to
extract the features from.
Attributes
----------
keypoints : (N, 2) array
Keypoint coordinates as ``(row, col)``.
scales : (N, ) array
Corresponding scales.
orientations : (N, ) array
Corresponding orientations in radians.
responses : (N, ) array
Corresponding Harris corner responses.
descriptors : (Q, `descriptor_size`) array of dtype bool
2D array of binary descriptors of size `descriptor_size` for Q
keypoints after filtering out border keypoints with value at an
index ``(i, j)`` either being ``True`` or ``False`` representing
the outcome of the intensity comparison for i-th keypoint on j-th
decision pixel-pair. It is ``Q == np.sum(mask)``.
References
----------
.. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
"ORB: An efficient alternative to SIFT and SURF"
http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
Examples
--------
>>> from skimage.feature import ORB, match_descriptors
>>> img1 = np.zeros((100, 100))
>>> img2 = np.zeros_like(img1)
>>> np.random.seed(1)
>>> square = np.random.rand(20, 20)
>>> img1[40:60, 40:60] = square
>>> img2[53:73, 53:73] = square
>>> detector_extractor1 = ORB(n_keypoints=5)
>>> detector_extractor2 = ORB(n_keypoints=5)
>>> detector_extractor1.detect_and_extract(img1)
>>> detector_extractor2.detect_and_extract(img2)
>>> matches = match_descriptors(detector_extractor1.descriptors,
... detector_extractor2.descriptors)
>>> matches
array([[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4]])
>>> detector_extractor1.keypoints[matches[:, 0]]
array([[ 42., 40.],
[ 47., 58.],
[ 44., 40.],
[ 59., 42.],
[ 45., 44.]])
>>> detector_extractor2.keypoints[matches[:, 1]]
array([[ 55., 53.],
[ 60., 71.],
[ 57., 53.],
[ 72., 55.],
[ 58., 57.]])
"""
def __init__(self, downscale=1.2, n_scales=8,
n_keypoints=500, fast_n=9, fast_threshold=0.08,
harris_k=0.04):
self.downscale = downscale
self.n_scales = n_scales
self.n_keypoints = n_keypoints
self.fast_n = fast_n
self.fast_threshold = fast_threshold
self.harris_k = harris_k
self.keypoints = None
self.scales = None
self.responses = None
self.orientations = None
self.descriptors = None
def _build_pyramid(self, image):
image = _prepare_grayscale_input_2D(image)
return list(pyramid_gaussian(image, self.n_scales - 1, self.downscale))
def _detect_octave(self, octave_image):
# Extract keypoints for current octave
fast_response = corner_fast(octave_image, self.fast_n,
self.fast_threshold)
keypoints = corner_peaks(fast_response, min_distance=1)
if len(keypoints) == 0:
return (np.zeros((0, 2), dtype=np.double),
np.zeros((0, ), dtype=np.double),
np.zeros((0, ), dtype=np.double))
mask = _mask_border_keypoints(octave_image.shape, keypoints,
distance=16)
keypoints = keypoints[mask]
orientations = corner_orientations(octave_image, keypoints,
OFAST_MASK)
harris_response = corner_harris(octave_image, method='k',
k=self.harris_k)
responses = harris_response[keypoints[:, 0], keypoints[:, 1]]
return keypoints, orientations, responses
def detect(self, image):
"""Detect oriented FAST keypoints along with the corresponding scale.
Parameters
----------
image : 2D array
Input image.
"""
pyramid = self._build_pyramid(image)
keypoints_list = []
orientations_list = []
scales_list = []
responses_list = []
for octave in range(len(pyramid)):
octave_image = np.ascontiguousarray(pyramid[octave])
keypoints, orientations, responses = \
self._detect_octave(octave_image)
keypoints_list.append(keypoints * self.downscale ** octave)
orientations_list.append(orientations)
scales_list.append(self.downscale ** octave
* np.ones(keypoints.shape[0], dtype=np.intp))
responses_list.append(responses)
keypoints = np.vstack(keypoints_list)
orientations = np.hstack(orientations_list)
scales = np.hstack(scales_list)
responses = np.hstack(responses_list)
if keypoints.shape[0] < self.n_keypoints:
self.keypoints = keypoints
self.scales = scales
self.orientations = orientations
self.responses = responses
else:
# Choose best n_keypoints according to Harris corner response
best_indices = responses.argsort()[::-1][:self.n_keypoints]
self.keypoints = keypoints[best_indices]
self.scales = scales[best_indices]
self.orientations = orientations[best_indices]
self.responses = responses[best_indices]
def _extract_octave(self, octave_image, keypoints, orientations):
mask = _mask_border_keypoints(octave_image.shape, keypoints,
distance=20)
keypoints = np.array(keypoints[mask], dtype=np.intp, order='C',
copy=False)
orientations = np.array(orientations[mask], dtype=np.double, order='C',
copy=False)
descriptors = _orb_loop(octave_image, keypoints, orientations)
return descriptors, mask
def extract(self, image, keypoints, scales, orientations):
"""Extract rBRIEF binary descriptors for given keypoints in image.
Note that the keypoints must be extracted using the same `downscale`
and `n_scales` parameters. Additionally, if you want to extract both
keypoints and descriptors you should use the faster
`detect_and_extract`.
Parameters
----------
image : 2D array
Input image.
keypoints : (N, 2) array
Keypoint coordinates as ``(row, col)``.
scales : (N, ) array
Corresponding scales.
orientations : (N, ) array
Corresponding orientations in radians.
"""
pyramid = self._build_pyramid(image)
descriptors_list = []
mask_list = []
# Determine octaves from scales
octaves = (np.log(scales) / np.log(self.downscale)).astype(np.intp)
for octave in range(len(pyramid)):
# Mask for all keypoints in current octave
octave_mask = octaves == octave
if np.sum(octave_mask) > 0:
octave_image = np.ascontiguousarray(pyramid[octave])
octave_keypoints = keypoints[octave_mask]
octave_keypoints /= self.downscale ** octave
octave_orientations = orientations[octave_mask]
descriptors, mask = self._extract_octave(octave_image,
octave_keypoints,
octave_orientations)
descriptors_list.append(descriptors)
mask_list.append(mask)
self.descriptors = np.vstack(descriptors_list).view(np.bool)
self.mask_ = np.hstack(mask_list)
def detect_and_extract(self, image):
"""Detect oriented FAST keypoints and extract rBRIEF descriptors.
Note that this is faster than first calling `detect` and then
`extract`.
Parameters
----------
image : 2D array
Input image.
"""
pyramid = self._build_pyramid(image)
keypoints_list = []
responses_list = []
scales_list = []
orientations_list = []
descriptors_list = []
for octave in range(len(pyramid)):
octave_image = np.ascontiguousarray(pyramid[octave])
keypoints, orientations, responses = \
self._detect_octave(octave_image)
if len(keypoints) == 0:
keypoints_list.append(keypoints)
responses_list.append(responses)
descriptors_list.append(np.zeros((0, 256), dtype=np.bool))
continue
descriptors, mask = self._extract_octave(octave_image, keypoints,
orientations)
keypoints_list.append(keypoints[mask] * self.downscale ** octave)
responses_list.append(responses[mask])
orientations_list.append(orientations[mask])
scales_list.append(self.downscale ** octave
* np.ones(keypoints.shape[0], dtype=np.intp))
descriptors_list.append(descriptors)
keypoints = np.vstack(keypoints_list)
responses = np.hstack(responses_list)
scales = np.hstack(scales_list)
orientations = np.hstack(orientations_list)
descriptors = np.vstack(descriptors_list).view(np.bool)
if keypoints.shape[0] < self.n_keypoints:
self.keypoints = keypoints
self.scales = scales
self.orientations = orientations
self.responses = responses
self.descriptors = descriptors
else:
# Choose best n_keypoints according to Harris corner response
best_indices = responses.argsort()[::-1][:self.n_keypoints]
self.keypoints = keypoints[best_indices]
self.scales = scales[best_indices]
self.orientations = orientations[best_indices]
self.responses = responses[best_indices]
self.descriptors = descriptors[best_indices]
+56
View File
@@ -0,0 +1,56 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
import os
import numpy as np
from skimage import data_dir
cimport numpy as cnp
from libc.math cimport sin, cos
from skimage._shared.interpolation cimport round
POS = np.loadtxt(os.path.join(data_dir, "orb_descriptor_positions.txt"),
dtype=np.int8)
POS0 = np.ascontiguousarray(POS[:, :2])
POS1 = np.ascontiguousarray(POS[:, 2:])
def _orb_loop(double[:, ::1] image, Py_ssize_t[:, ::1] keypoints,
double[:] orientations):
cdef Py_ssize_t i, d, kr, kc, pr0, pr1, pc0, pc1, spr0, spc0, spr1, spc1
cdef int[:, ::1] steered_pos0, steered_pos1
cdef double angle
cdef char[:, ::1] descriptors = np.zeros((keypoints.shape[0],
POS.shape[0]), dtype=np.uint8)
cdef char[:, ::1] cpos0 = POS0
cdef char[:, ::1] cpos1 = POS1
for i in range(descriptors.shape[0]):
angle = orientations[i]
sin_a = sin(angle)
cos_a = cos(angle)
kr = keypoints[i, 0]
kc = keypoints[i, 1]
for j in range(descriptors.shape[1]):
pr0 = cpos0[j, 0]
pc0 = cpos0[j, 1]
pr1 = cpos1[j, 0]
pc1 = cpos1[j, 1]
spr0 = <Py_ssize_t>round(sin_a * pr0 + cos_a * pc0)
spc0 = <Py_ssize_t>round(cos_a * pr0 - sin_a * pc0)
spr1 = <Py_ssize_t>round(sin_a * pr1 + cos_a * pc1)
spc1 = <Py_ssize_t>round(cos_a * pr1 - sin_a * pc1)
if image[kr + spr0, kc + spc0] < image[kr + spr1, kc + spc1]:
descriptors[i, j] = True
return np.asarray(descriptors)
+13 -8
View File
@@ -49,9 +49,9 @@ def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1,
Returns
-------
output : (N, 2) array or ndarray of bools
output : ndarray or ndarray of bools
* If `indices = True` : (row, column) coordinates of peaks.
* If `indices = True` : (row, column, ...) coordinates of peaks.
* If `indices = False` : Boolean array shaped like `image`, with peaks
represented by True values.
@@ -65,10 +65,10 @@ def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1,
Examples
--------
>>> im = np.zeros((7, 7))
>>> im[3, 4] = 1
>>> im[3, 2] = 1.5
>>> im
>>> img1 = np.zeros((7, 7))
>>> img1[3, 4] = 1
>>> img1[3, 2] = 1.5
>>> img1
array([[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
@@ -77,13 +77,18 @@ def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1,
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ]])
>>> peak_local_max(im, min_distance=1)
>>> peak_local_max(img1, min_distance=1)
array([[3, 2],
[3, 4]])
>>> peak_local_max(im, min_distance=2)
>>> peak_local_max(img1, min_distance=2)
array([[3, 2]])
>>> img2 = np.zeros((20, 20, 20))
>>> img2[10, 10, 10] = 1
>>> peak_local_max(img2, exclude_border=False)
array([[10, 10, 10]])
"""
out = np.zeros_like(image, dtype=np.bool)
# In the case of labels, recursively build and return an output
+5 -5
View File
@@ -14,20 +14,20 @@ def configuration(parent_package='', top_path=None):
cython(['corner_cy.pyx'], working_path=base_path)
cython(['censure_cy.pyx'], working_path=base_path)
cython(['_brief_cy.pyx'], working_path=base_path)
cython(['orb_cy.pyx'], working_path=base_path)
cython(['brief_cy.pyx'], working_path=base_path)
cython(['_texture.pyx'], working_path=base_path)
cython(['_template.pyx'], working_path=base_path)
config.add_extension('corner_cy', sources=['corner_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('censure_cy', sources=['censure_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_brief_cy', sources=['_brief_cy.c'],
config.add_extension('orb_cy', sources=['orb_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('brief_cy', sources=['brief_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_texture', sources=['_texture.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('_template', sources=['_template.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
return config
+146 -56
View File
@@ -1,81 +1,171 @@
"""template.py - Template matching
"""
import numpy as np
from . import _template
from scipy.signal import fftconvolve
from skimage.util import pad
def match_template(image, template, pad_input=False):
"""Match a template to an image using normalized correlation.
def _window_sum_2d(image, window_shape):
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.
window_sum = np.cumsum(image, axis=0)
window_sum = (window_sum[window_shape[0]:-1]
- window_sum[:-window_shape[0]-1])
window_sum = np.cumsum(window_sum, axis=1)
window_sum = (window_sum[:, window_shape[1]:-1]
- window_sum[:, :-window_shape[1]-1])
return window_sum
def _window_sum_3d(image, window_shape):
window_sum = _window_sum_2d(image, window_shape)
window_sum = np.cumsum(window_sum, axis=2)
window_sum = (window_sum[:, :, window_shape[2]:-1]
- window_sum[:, :, :-window_shape[2]-1])
return window_sum
def match_template(image, template, pad_input=False, mode='constant',
constant_values=0):
"""Match a template to a 2-D or 3-D image using normalized correlation.
The output is an array with values between -1.0 and 1.0. The value at a
given position corresponds to the correlation coefficient between the image
and the template.
For `pad_input=True` matches correspond to the center and otherwise to the
top-left corner of the template. To find the best match you must search for
peaks in the response (output) image.
Parameters
----------
image : array_like
Image to process.
template : array_like
Template to locate.
image : (M, N[, D]) array
2-D or 3-D input image.
template : (m, n[, d]) array
Template to locate. It must be `(m <= M, n <= N[, d <= D])`.
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.
If True, pad `image` 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.
mode : see `numpy.pad`, optional
Padding mode.
constant_values : see `numpy.pad`, optional
Constant values used in conjunction with ``mode='constant'``.
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`.
output : array
Response image with correlation coefficients.
References
----------
.. [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.
Examples
--------
>>> template = np.zeros((3, 3))
>>> template[1, 1] = 1
>>> print(template)
[[ 0. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 0.]]
>>> template
array([[ 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.]]
>>> image
array([[ 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. ]]
>>> np.round(result, 3)
array([[ 1. , -0.125, 0. , 0. ],
[-0.125, -0.125, 0. , 0. ],
[ 0. , 0. , 0.125, 0.125],
[ 0. , 0. , 0.125, -1. ]], dtype=float32)
>>> 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]]
>>> np.round(result, 3)
array([[-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]], dtype=float32)
"""
if image.ndim not in (2, 3) or template.ndim not in (2, 3):
raise ValueError("Only 2- and 3-D images supported.")
if image.ndim < template.ndim:
raise ValueError("Dimensionality of template must be less than or "
"equal to the dimensionality of image.")
if np.any(np.less(image.shape, template.shape)):
raise ValueError("Image must be larger than template.")
image = np.ascontiguousarray(image, dtype=np.float32)
template = np.ascontiguousarray(template, dtype=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
image_shape = image.shape
image = np.array(image, dtype=np.float32, copy=False)
pad_width = tuple((width, width) for width in template.shape)
if mode == 'constant':
image = pad(image, pad_width=pad_width, mode=mode,
constant_values=constant_values)
else:
image = pad(image, pad_width=pad_width, mode=mode)
# Use special case for 2-D images for much better performance in
# computation of integral images
if image.ndim == 2:
image_window_sum = _window_sum_2d(image, template.shape)
image_window_sum2 = _window_sum_2d(image**2, template.shape)
elif image.ndim == 3:
image_window_sum = _window_sum_3d(image, template.shape)
image_window_sum2 = _window_sum_3d(image**2, template.shape)
template_volume = np.prod(template.shape)
template_ssd = np.sum((template - template.mean())**2)
if image.ndim == 2:
xcorr = fftconvolve(image, template[::-1, ::-1],
mode="valid")[1:-1, 1:-1]
elif image.ndim == 3:
xcorr = fftconvolve(image, template[::-1, ::-1, ::-1],
mode="valid")[1:-1, 1:-1, 1:-1]
nom = xcorr - image_window_sum * (template.sum() / template_volume)
denom = image_window_sum2
np.multiply(image_window_sum, image_window_sum, out=image_window_sum)
np.divide(image_window_sum, template_volume, out=image_window_sum)
denom -= image_window_sum
denom *= template_ssd
np.maximum(denom, 0, out=denom) # sqrt of negative number not allowed
np.sqrt(denom, out=denom)
response = np.zeros_like(xcorr, dtype=np.float32)
# avoid zero-division
mask = denom > np.finfo(np.float32).eps
response[mask] = nom[mask] / denom[mask]
slices = []
for i in range(template.ndim):
if pad_input:
d0 = (template.shape[i] - 1) // 2
d1 = d0 + image_shape[i]
else:
d0 = template.shape[i] - 1
d1 = d0 + image_shape[i] - template.shape[i] + 1
slices.append(slice(d0, d1))
return response[slices]
-83
View File
@@ -1,83 +0,0 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage import data
from skimage import transform as tf
from skimage.color import rgb2gray
from skimage.feature import (brief, match_keypoints_brief, corner_peaks,
corner_harris)
def test_brief_color_image_unsupported_error():
"""Brief descriptors can be evaluated on gray-scale images only."""
img = np.zeros((20, 20, 3))
keypoints = [[7, 5], [11, 13]]
assert_raises(ValueError, brief, img, keypoints)
def test_match_keypoints_brief_lena_translation():
"""Test matched keypoints between lena image and its translated version."""
img = data.lena()
img = rgb2gray(img)
img.shape
tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20))
translated_img = tf.warp(img, tform)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512)
keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5)
descriptors2, keypoints2 = brief(translated_img, keypoints2,
descriptor_size=512)
matched_keypoints = match_keypoints_brief(keypoints1, descriptors1,
keypoints2, descriptors2,
threshold=0.10)
assert_array_equal(matched_keypoints[:, 0, :], matched_keypoints[:, 1, :] +
[20, 15])
def test_match_keypoints_brief_lena_rotation():
"""Verify matched keypoints result between lena image and its rotated
version with the expected keypoint pairs."""
img = data.lena()
img = rgb2gray(img)
img.shape
tform = tf.SimilarityTransform(scale=1, rotation=0.10, translation=(0, 0))
rotated_img = tf.warp(img, tform)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512)
keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5)
descriptors2, keypoints2 = brief(rotated_img, keypoints2,
descriptor_size=512)
matched_keypoints = match_keypoints_brief(keypoints1, descriptors1,
keypoints2, descriptors2,
threshold=0.07)
expected = np.array([[[263, 272],
[234, 298]],
[[271, 120],
[258, 146]],
[[323, 164],
[305, 195]],
[[414, 70],
[405, 111]],
[[435, 181],
[415, 223]],
[[454, 176],
[435, 221]]])
assert_array_equal(matched_keypoints, expected)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
-89
View File
@@ -1,89 +0,0 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage.data import moon
from skimage.feature import keypoints_censure
def test_keypoints_censure_color_image_unsupported_error():
"""Censure keypoints can be extracted from gray-scale images only."""
img = np.zeros((20, 20, 3))
assert_raises(ValueError, keypoints_censure, img)
def test_keypoints_censure_mode_validity_error():
"""Mode argument in keypoints_censure can be either DoB, Octagon or
STAR."""
img = np.zeros((20, 20))
assert_raises(ValueError, keypoints_censure, img, mode='dummy')
def test_keypoints_censure_scale_range_error():
"""Difference between the the max_scale and min_scale parameters in
keypoints_censure should be greater than or equal to two."""
img = np.zeros((20, 20))
assert_raises(ValueError, keypoints_censure, img, min_scale=1, max_scale=2)
def test_keypoints_censure_moon_image_dob():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for DoB filter."""
img = moon()
actual_kp_dob, actual_scale = keypoints_censure(img, 1, 7, 'DoB', 0.15)
expected_kp_dob = np.array([[ 21, 497],
[ 36, 46],
[119, 350],
[185, 177],
[287, 250],
[357, 239],
[463, 116],
[464, 132],
[467, 260]])
expected_scale = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2])
assert_array_equal(expected_kp_dob, actual_kp_dob)
assert_array_equal(expected_scale, actual_scale)
def test_keypoints_censure_moon_image_octagon():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for Octagon filter."""
img = moon()
actual_kp_octagon, actual_scale = keypoints_censure(img, 1, 7, 'Octagon',
0.15)
expected_kp_octagon = np.array([[ 21, 496],
[ 35, 46],
[287, 250],
[356, 239],
[463, 116]])
expected_scale = np.array([3, 4, 2, 2, 2])
assert_array_equal(expected_kp_octagon, actual_kp_octagon)
assert_array_equal(expected_scale, actual_scale)
def test_keypoints_censure_moon_image_star():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for STAR filter."""
img = moon()
actual_kp_star, actual_scale = keypoints_censure(img, 1, 7, 'STAR', 0.15)
expected_kp_star = np.array([[ 21, 497],
[ 36, 46],
[117, 356],
[185, 177],
[260, 227],
[287, 250],
[357, 239],
[451, 281],
[463, 116],
[467, 260]])
expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2])
assert_array_equal(expected_kp_star, actual_kp_star)
assert_array_equal(expected_scale, actual_scale)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+81
View File
@@ -0,0 +1,81 @@
import numpy as np
from skimage.draw import circle
from skimage.feature import blob_dog, blob_log
import math
def test_blob_dog():
r2 = math.sqrt(2)
img = np.ones((512, 512))
xs, ys = circle(400, 130, 5)
img[xs, ys] = 255
xs, ys = circle(100, 300, 25)
img[xs, ys] = 255
xs, ys = circle(200, 350, 45)
img[xs, ys] = 255
blobs = blob_dog(img, min_sigma=5, max_sigma=50)
radius = lambda x: r2*x[2]
s = sorted(blobs, key=radius)
thresh = 5
b = s[0]
assert abs(b[0] - 400) <= thresh
assert abs(b[1] - 130) <= thresh
assert abs(radius(b) - 5) <= thresh
b = s[1]
assert abs(b[0] - 100) <= thresh
assert abs(b[1] - 300) <= thresh
assert abs(radius(b) - 25) <= thresh
b = s[2]
assert abs(b[0] - 200) <= thresh
assert abs(b[1] - 350) <= thresh
assert abs(radius(b) - 45) <= thresh
def test_blob_log():
r2 = math.sqrt(2)
img = np.ones((512, 512))
xs, ys = circle(400, 130, 5)
img[xs, ys] = 255
xs, ys = circle(160, 50, 15)
img[xs, ys] = 255
xs, ys = circle(100, 300, 25)
img[xs, ys] = 255
xs, ys = circle(200, 350, 30)
img[xs, ys] = 255
blobs = blob_log(img, min_sigma=5, max_sigma=20, threshold=1)
radius = lambda x: r2*x[2]
s = sorted(blobs, key=radius)
thresh = 3
b = s[0]
assert abs(b[0] - 400) <= thresh
assert abs(b[1] - 130) <= thresh
assert abs(radius(b) - 5) <= thresh
b = s[1]
assert abs(b[0] - 160) <= thresh
assert abs(b[1] - 50) <= thresh
assert abs(radius(b) - 15) <= thresh
b = s[2]
assert abs(b[0] - 100) <= thresh
assert abs(b[1] - 300) <= thresh
assert abs(radius(b) - 25) <= thresh
b = s[3]
assert abs(b[0] - 200) <= thresh
assert abs(b[1] - 350) <= thresh
assert abs(radius(b) - 30) <= thresh
+77
View File
@@ -0,0 +1,77 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage import data
from skimage import transform as tf
from skimage.color import rgb2gray
from skimage.feature import BRIEF, corner_peaks, corner_harris
def test_color_image_unsupported_error():
"""Brief descriptors can be evaluated on gray-scale images only."""
img = np.zeros((20, 20, 3))
keypoints = np.asarray([[7, 5], [11, 13]])
assert_raises(ValueError, BRIEF().extract, img, keypoints)
def test_normal_mode():
"""Verify the computed BRIEF descriptors with expected for normal mode."""
img = rgb2gray(data.lena())
keypoints = corner_peaks(corner_harris(img), min_distance=5)
extractor = BRIEF(descriptor_size=8, sigma=2)
extractor.extract(img, keypoints[:8])
expected = np.array([[ True, False, True, False, True, True, False, False],
[False, False, False, False, True, False, False, False],
[ True, True, True, True, True, True, True, True],
[ True, False, True, True, False, True, False, True],
[False, True, True, True, True, True, True, True],
[ True, False, False, False, False, True, False, True],
[False, True, True, True, False, False, True, False],
[False, False, False, False, True, False, False, False]], dtype=bool)
assert_array_equal(extractor.descriptors, expected)
def test_uniform_mode():
"""Verify the computed BRIEF descriptors with expected for uniform mode."""
img = rgb2gray(data.lena())
keypoints = corner_peaks(corner_harris(img), min_distance=5)
extractor = BRIEF(descriptor_size=8, sigma=2, mode='uniform')
extractor.extract(img, keypoints[:8])
expected = np.array([[ True, False, True, False, False, True, False, False],
[False, True, False, False, True, True, True, True],
[ True, False, False, False, False, False, False, False],
[False, True, True, False, False, False, True, False],
[False, False, False, False, False, False, True, False],
[False, True, False, False, True, False, False, False],
[False, False, True, True, False, False, True, True],
[ True, True, False, False, False, False, False, False]], dtype=bool)
assert_array_equal(extractor.descriptors, expected)
def test_unsupported_mode():
assert_raises(ValueError, BRIEF, mode='foobar')
def test_border():
img = np.zeros((100, 100))
keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]])
extractor = BRIEF(patch_size=41)
extractor.extract(img, keypoints)
assert extractor.descriptors.shape[0] == 3
assert_array_equal(extractor.mask, (False, True, True, True))
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+97
View File
@@ -0,0 +1,97 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage.data import moon
from skimage.feature import CENSURE
img = moon()
def test_censure_on_rectangular_images():
"""Censure feature detector should work on 2D image of any shape."""
rect_image = np.random.random((300, 200))
square_image = np.random.random((200, 200))
CENSURE().detect((square_image))
CENSURE().detect((rect_image))
def test_keypoints_censure_color_image_unsupported_error():
"""Censure keypoints can be extracted from gray-scale images only."""
assert_raises(ValueError, CENSURE().detect, np.zeros((20, 20, 3)))
def test_keypoints_censure_mode_validity_error():
"""Mode argument in keypoints_censure can be either DoB, Octagon or
STAR."""
assert_raises(ValueError, CENSURE, mode='dummy')
def test_keypoints_censure_scale_range_error():
"""Difference between the the max_scale and min_scale parameters in
keypoints_censure should be greater than or equal to two."""
assert_raises(ValueError, CENSURE, min_scale=1, max_scale=2)
def test_keypoints_censure_moon_image_dob():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for DoB filter."""
detector = CENSURE()
detector.detect(img)
expected_keypoints = np.array([[ 21, 497],
[ 36, 46],
[119, 350],
[185, 177],
[287, 250],
[357, 239],
[463, 116],
[464, 132],
[467, 260]])
expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2])
assert_array_equal(expected_keypoints, detector.keypoints)
assert_array_equal(expected_scales, detector.scales)
def test_keypoints_censure_moon_image_octagon():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for Octagon filter."""
detector = CENSURE(mode='octagon')
detector.detect(img)
expected_keypoints = np.array([[ 21, 496],
[ 35, 46],
[287, 250],
[356, 239],
[463, 116]])
expected_scales = np.array([3, 4, 2, 2, 2])
assert_array_equal(expected_keypoints, detector.keypoints)
assert_array_equal(expected_scales, detector.scales)
def test_keypoints_censure_moon_image_star():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for STAR filter."""
detector = CENSURE(mode='star')
detector.detect(img)
expected_keypoints = np.array([[ 21, 497],
[ 36, 46],
[117, 356],
[185, 177],
[260, 227],
[287, 250],
[357, 239],
[451, 281],
[463, 116],
[467, 260]])
expected_scales = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2])
assert_array_equal(expected_keypoints, detector.keypoints)
assert_array_equal(expected_scales, detector.scales)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+152 -7
View File
@@ -1,12 +1,94 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_almost_equal
from numpy.testing import (assert_array_equal, assert_raises,
assert_almost_equal)
from skimage import data
from skimage import img_as_float
from skimage.color import rgb2gray
from skimage.morphology import octagon
from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi,
corner_subpix, peak_local_max, corner_peaks,
corner_kitchen_rosenfeld, corner_foerstner)
corner_kitchen_rosenfeld, corner_foerstner,
corner_fast, corner_orientations,
structure_tensor, structure_tensor_eigvals,
hessian_matrix, hessian_matrix_eigvals)
def test_structure_tensor():
square = np.zeros((5, 5))
square[2, 2] = 1
Axx, Axy, Ayy = structure_tensor(square, sigma=0.1)
assert_array_equal(Axx, np.array([[ 0, 0, 0, 0, 0],
[ 0, 1, 0, 1, 0],
[ 0, 4, 0, 4, 0],
[ 0, 1, 0, 1, 0],
[ 0, 0, 0, 0, 0]]))
assert_array_equal(Axy, np.array([[ 0, 0, 0, 0, 0],
[ 0, 1, 0, -1, 0],
[ 0, 0, 0, -0, 0],
[ 0, -1, -0, 1, 0],
[ 0, 0, 0, 0, 0]]))
assert_array_equal(Ayy, np.array([[ 0, 0, 0, 0, 0],
[ 0, 1, 4, 1, 0],
[ 0, 0, 0, 0, 0],
[ 0, 1, 4, 1, 0],
[ 0, 0, 0, 0, 0]]))
def test_hessian_matrix():
square = np.zeros((5, 5))
square[2, 2] = 1
Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1)
assert_array_equal(Hxx, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
assert_array_equal(Hxy, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
assert_array_equal(Hyy, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
def test_structure_tensor_eigvals():
square = np.zeros((5, 5))
square[2, 2] = 1
Axx, Axy, Ayy = structure_tensor(square, sigma=0.1)
l1, l2 = structure_tensor_eigvals(Axx, Axy, Ayy)
assert_array_equal(l1, np.array([[0, 0, 0, 0, 0],
[0, 2, 4, 2, 0],
[0, 4, 0, 4, 0],
[0, 2, 4, 2, 0],
[0, 0, 0, 0, 0]]))
assert_array_equal(l2, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
def test_hessian_matrix_eigvals():
square = np.zeros((5, 5))
square[2, 2] = 1
Hxx, Hxy, Hyy = hessian_matrix(square, sigma=0.1)
l1, l2 = hessian_matrix_eigvals(Hxx, Hxy, Hyy)
assert_array_equal(l1, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
assert_array_equal(l2, np.array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]))
def test_square_image():
@@ -19,7 +101,11 @@ def test_square_image():
assert len(results) == 57
# Harris
results = peak_local_max(corner_harris(im))
results = peak_local_max(corner_harris(im, method='k'))
# interest at corner
assert len(results) == 1
results = peak_local_max(corner_harris(im, method='eps'))
# interest at corner
assert len(results) == 1
@@ -41,7 +127,9 @@ def test_noisy_square_image():
assert results.any()
# Harris
results = peak_local_max(corner_harris(im, sigma=1.5))
results = peak_local_max(corner_harris(im, sigma=1.5, method='k'))
assert len(results) == 1
results = peak_local_max(corner_harris(im, sigma=1.5, method='eps'))
assert len(results) == 1
# Shi-Tomasi
@@ -94,8 +182,8 @@ def test_rotated_lena():
def test_subpix():
img = np.zeros((50, 50))
img[:25,:25] = 255
img[25:,25:] = 255
img[:25, :25] = 255
img[25:, 25:] = 255
corner = peak_local_max(corner_harris(img), num_peaks=1)
subpix = corner_subpix(img, corner)
assert_array_equal(subpix[0], (24.5, 24.5))
@@ -122,7 +210,7 @@ def test_num_peaks():
peak_local_max returns exactly the right amount of peaks. Test
is run on Lena in order to produce a sufficient number of corners"""
lena_corners = corner_harris(data.lena())
lena_corners = corner_harris(rgb2gray(data.lena()))
for i in range(20):
n = np.random.random_integers(20)
@@ -140,6 +228,10 @@ def test_corner_peaks():
corners = corner_peaks(response, exclude_border=False, min_distance=0)
assert len(corners) == 4
corners = corner_peaks(response, exclude_border=False, min_distance=0,
indices=False)
assert np.sum(corners) == 4
def test_blank_image_nans():
"""Some of the corner detectors had a weakness in terms of returning
@@ -156,6 +248,59 @@ def test_blank_image_nans():
assert np.all(np.isfinite(response))
def test_corner_fast_image_unsupported_error():
img = np.zeros((20, 20, 3))
assert_raises(ValueError, corner_fast, img)
def test_corner_fast_lena():
img = rgb2gray(data.lena())
expected = np.array([[ 67, 157],
[204, 261],
[247, 146],
[269, 111],
[318, 158],
[386, 73],
[413, 70],
[435, 180],
[455, 177],
[461, 160]])
actual = corner_peaks(corner_fast(img, 12, 0.3))
assert_array_equal(actual, expected)
def test_corner_orientations_image_unsupported_error():
img = np.zeros((20, 20, 3))
assert_raises(ValueError, corner_orientations, img,
np.asarray([[7, 7]]), np.ones((3, 3)))
def test_corner_orientations_even_shape_error():
img = np.zeros((20, 20))
assert_raises(ValueError, corner_orientations, img,
np.asarray([[7, 7]]), np.ones((4, 4)))
def test_corner_orientations_lena():
img = rgb2gray(data.lena())
corners = corner_peaks(corner_fast(img, 11, 0.35))
expected = np.array([-1.9195897 , -3.03159624, -1.05991162, -2.89573739,
-2.61607644, 2.98660159])
actual = corner_orientations(img, corners, octagon(3, 2))
assert_almost_equal(actual, expected)
def test_corner_orientations_square():
square = np.zeros((12, 12))
square[3:9, 3:9] = 1
corners = corner_peaks(corner_fast(square, 9), min_distance=1)
actual_orientations = corner_orientations(square, corners, octagon(3, 2))
actual_orientations_degrees = np.rad2deg(actual_orientations)
expected_orientations_degree = np.array([ 45., 135., -45., -135.])
assert_array_equal(actual_orientations_degrees,
expected_orientations_degree)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+8 -1
View File
@@ -45,8 +45,15 @@ def test_descs_shape():
assert(descs.shape[1] == ceil((img.shape[1] - radius * 2) / float(step)))
def test_daisy_sigmas_and_radii():
img = img_as_float(data.lena()[:64, :64].mean(axis=2))
sigmas = [1, 2, 3]
radii = [1, 2]
daisy(img, sigmas=sigmas, ring_radii=radii)
def test_daisy_incompatible_sigmas_and_radii():
img = img_as_float(data.lena()[:128, :128].mean(axis=2))
img = img_as_float(data.lena()[:64, :64].mean(axis=2))
sigmas = [1, 2]
radii = [1, 2]
assert_raises(ValueError, daisy, img, sigmas=sigmas, ring_radii=radii)
+120
View File
@@ -0,0 +1,120 @@
import numpy as np
from numpy.testing import assert_equal, assert_raises
from skimage import data
from skimage import transform as tf
from skimage.color import rgb2gray
from skimage.feature import (BRIEF, match_descriptors,
corner_peaks, corner_harris)
def test_binary_descriptors_unequal_descriptor_sizes_error():
"""Sizes of descriptors of keypoints to be matched should be equal."""
descs1 = np.array([[True, True, False, True],
[False, True, False, True]])
descs2 = np.array([[True, False, False, True, False],
[False, True, True, True, False]])
assert_raises(ValueError, match_descriptors, descs1, descs2)
def test_binary_descriptors():
descs1 = np.array([[True, True, False, True, True],
[False, True, False, True, True]])
descs2 = np.array([[True, False, False, True, False],
[False, False, True, True, True]])
matches = match_descriptors(descs1, descs2)
assert_equal(matches, [[0, 0], [1, 1]])
def test_binary_descriptors_lena_rotation_crosscheck_false():
"""Verify matched keypoints and their corresponding masks results between
lena image and its rotated version with the expected keypoint pairs with
cross_check disabled."""
img = data.lena()
img = rgb2gray(img)
tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
rotated_img = tf.warp(img, tform)
extractor = BRIEF(descriptor_size=512)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
extractor.extract(img, keypoints1)
descriptors1 = extractor.descriptors
keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5)
extractor.extract(rotated_img, keypoints2)
descriptors2 = extractor.descriptors
matches = match_descriptors(descriptors1, descriptors2, cross_check=False)
exp_matches1 = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46])
exp_matches2 = np.array([33, 0, 35, 7, 1, 35, 3, 2, 3, 6, 4, 9,
11, 10, 28, 7, 8, 5, 31, 14, 13, 15, 21, 16,
16, 13, 17, 18, 19, 21, 22, 23, 0, 24, 1, 24,
23, 0, 26, 27, 25, 34, 28, 14, 29, 30, 21])
assert_equal(matches[:, 0], exp_matches1)
assert_equal(matches[:, 1], exp_matches2)
def test_binary_descriptors_lena_rotation_crosscheck_true():
"""Verify matched keypoints and their corresponding masks results between
lena image and its rotated version with the expected keypoint pairs with
cross_check enabled."""
img = data.lena()
img = rgb2gray(img)
tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
rotated_img = tf.warp(img, tform)
extractor = BRIEF(descriptor_size=512)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
extractor.extract(img, keypoints1)
descriptors1 = extractor.descriptors
keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5)
extractor.extract(rotated_img, keypoints2)
descriptors2 = extractor.descriptors
matches = match_descriptors(descriptors1, descriptors2, cross_check=True)
exp_matches1 = np.array([ 0, 1, 2, 4, 6, 7, 9, 10, 11, 12, 13, 15,
16, 17, 19, 20, 21, 24, 26, 27, 28, 29, 30, 35,
36, 38, 39, 40, 42, 44, 45])
exp_matches2 = np.array([33, 0, 35, 1, 3, 2, 6, 4, 9, 11, 10, 7,
8, 5, 14, 13, 15, 16, 17, 18, 19, 21, 22, 24,
23, 26, 27, 25, 28, 29, 30])
assert_equal(matches[:, 0], exp_matches1)
assert_equal(matches[:, 1], exp_matches2)
def test_max_distance():
descs1 = np.zeros((10, 128))
descs2 = np.zeros((15, 128))
descs1[0, :] = 1
matches = match_descriptors(descs1, descs2, metric='euclidean',
max_distance=0.1, cross_check=False)
assert len(matches) == 9
matches = match_descriptors(descs1, descs2, metric='euclidean',
max_distance=np.sqrt(128.1),
cross_check=False)
assert len(matches) == 10
matches = match_descriptors(descs1, descs2, metric='euclidean',
max_distance=0.1,
cross_check=True)
assert_equal(matches, [[1, 0]])
matches = match_descriptors(descs1, descs2, metric='euclidean',
max_distance=np.sqrt(128.1),
cross_check=True)
assert_equal(matches, [[1, 0]])
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+115
View File
@@ -0,0 +1,115 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_almost_equal
from skimage.feature import ORB
from skimage.data import lena
from skimage.color import rgb2gray
img = rgb2gray(lena())
def test_keypoints_orb_desired_no_of_keypoints():
detector_extractor = ORB(n_keypoints=10, fast_n=12, fast_threshold=0.20)
detector_extractor.detect(img)
exp_rows = np.array([ 435. , 435.6 , 376. , 455. , 434.88, 269. ,
375.6 , 310.8 , 413. , 311.04])
exp_cols = np.array([ 180. , 180. , 156. , 176. , 180. , 111. ,
156. , 172.8, 70. , 172.8])
exp_scales = np.array([ 1. , 1.2 , 1. , 1. , 1.44 , 1. ,
1.2 , 1.2 , 1. , 1.728])
exp_orientations = np.array([-175.64733392, -167.94842949, -148.98350192,
-142.03599837, -176.08535837, -53.08162354,
-150.89208271, 97.7693776 , -173.4479964 ,
38.66312042])
exp_response = np.array([ 0.96770745, 0.81027306, 0.72376257,
0.5626413 , 0.5097993 , 0.44351774,
0.39154173, 0.39084861, 0.39063076,
0.37602487])
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
assert_almost_equal(exp_scales, detector_extractor.scales)
assert_almost_equal(exp_response, detector_extractor.responses)
assert_almost_equal(exp_orientations,
np.rad2deg(detector_extractor.orientations), 5)
detector_extractor.detect_and_extract(img)
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
def test_keypoints_orb_less_than_desired_no_of_keypoints():
img = rgb2gray(lena())
detector_extractor = ORB(n_keypoints=15, fast_n=12,
fast_threshold=0.33, downscale=2, n_scales=2)
detector_extractor.detect(img)
exp_rows = np.array([ 67., 247., 269., 413., 435., 230., 264.,
330., 372.])
exp_cols = np.array([ 157., 146., 111., 70., 180., 136., 336.,
148., 156.])
exp_scales = np.array([ 1., 1., 1., 1., 1., 2., 2., 2., 2.])
exp_orientations = np.array([-105.76503839, -96.28973044, -53.08162354,
-173.4479964 , -175.64733392, -106.07927215,
-163.40016243, 75.80865813, -154.73195911])
exp_response = np.array([ 0.13197835, 0.24931321, 0.44351774,
0.39063076, 0.96770745, 0.04935129,
0.21431068, 0.15826555, 0.42403573])
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
assert_almost_equal(exp_scales, detector_extractor.scales)
assert_almost_equal(exp_response, detector_extractor.responses)
assert_almost_equal(exp_orientations,
np.rad2deg(detector_extractor.orientations), 5)
detector_extractor.detect_and_extract(img)
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
def test_descriptor_orb():
detector_extractor = ORB(fast_n=12, fast_threshold=0.20)
exp_descriptors = np.array([[ True, False, True, True, False, False, False, False, False, False],
[False, False, True, True, False, True, True, False, True, True],
[ True, False, False, False, True, False, True, True, True, False],
[ True, False, False, True, False, True, True, False, False, False],
[False, True, True, True, False, False, False, True, True, False],
[False, False, False, False, False, True, False, True, True, True],
[False, True, True, True, True, False, False, True, False, True],
[ True, True, True, False, True, True, True, True, False, False],
[ True, True, False, True, True, True, True, False, False, False],
[ True, False, False, False, False, True, False, False, True, True],
[ True, False, False, False, True, True, True, False, False, False],
[False, False, True, False, True, False, False, True, False, False],
[False, False, True, True, False, False, False, False, False, True],
[ True, True, False, False, False, True, True, True, True, True],
[ True, True, True, False, False, True, False, True, True, False],
[False, True, True, False, False, True, True, True, True, True],
[ True, True, True, False, False, False, False, True, True, True],
[False, False, False, False, True, False, False, True, True, False],
[False, True, False, False, True, False, False, False, True, True],
[ True, False, True, False, False, False, True, True, False, False]], dtype=bool)
detector_extractor.detect(img)
detector_extractor.extract(img, detector_extractor.keypoints,
detector_extractor.scales,
detector_extractor.orientations)
assert_array_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
detector_extractor.detect_and_extract(img)
assert_array_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+26 -1
View File
@@ -1,5 +1,6 @@
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
from numpy.testing import (assert_array_almost_equal as assert_close,
assert_equal)
import scipy.ndimage
from skimage.feature import peak
@@ -266,6 +267,30 @@ def test_disk():
assert np.all(result)
def test_3D():
image = np.zeros((30, 30, 30))
image[15, 15, 15] = 1
image[5, 5, 5] = 1
assert_equal(peak.peak_local_max(image), [[15, 15, 15]])
assert_equal(peak.peak_local_max(image, min_distance=6), [[15, 15, 15]])
assert_equal(peak.peak_local_max(image, exclude_border=False),
[[5, 5, 5], [15, 15, 15]])
assert_equal(peak.peak_local_max(image, min_distance=5),
[[5, 5, 5], [15, 15, 15]])
def test_4D():
image = np.zeros((30, 30, 30, 30))
image[15, 15, 15, 15] = 1
image[5, 5, 5, 5] = 1
assert_equal(peak.peak_local_max(image), [[15, 15, 15, 15]])
assert_equal(peak.peak_local_max(image, min_distance=6), [[15, 15, 15, 15]])
assert_equal(peak.peak_local_max(image, exclude_border=False),
[[5, 5, 5, 5], [15, 15, 15, 15]])
assert_equal(peak.peak_local_max(image, min_distance=5),
[[5, 5, 5, 5], [15, 15, 15, 15]])
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+58 -6
View File
@@ -1,5 +1,5 @@
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
from numpy.testing import assert_almost_equal, assert_equal, assert_raises
from skimage.morphology import diamond
from skimage.feature import match_template, peak_local_max
@@ -31,7 +31,7 @@ def test_template():
positions = positions[np.argsort(positions[:, 0])]
for xy_target, xy in zip(target_positions, positions):
yield assert_close, xy, xy_target
yield assert_almost_equal, xy, xy_target
def test_normalization():
@@ -88,7 +88,7 @@ def test_no_nans():
def test_switched_arguments():
image = np.ones((5, 5))
template = np.ones((3, 3))
np.testing.assert_raises(ValueError, match_template, template, image)
assert_raises(ValueError, match_template, template, image)
def test_pad_input():
@@ -108,14 +108,66 @@ def test_pad_input():
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)
result = match_template(image, template, pad_input=True,
constant_values=image.mean())
# 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))
assert_equal(j, (12, 0))
i, j = np.unravel_index(sorted_result[-2:], result.shape)
assert_close(j, (18, 6))
assert_equal(j, (18, 6))
def test_3d():
np.random.seed(1)
template = np.random.rand(3, 3, 3)
image = np.zeros((12, 12, 12))
image[3:6, 5:8, 4:7] = template
result = match_template(image, template)
assert_equal(result.shape, (10, 10, 10))
assert_equal(np.unravel_index(result.argmax(), result.shape), (3, 5, 4))
def test_3d_pad_input():
np.random.seed(1)
template = np.random.rand(3, 3, 3)
image = np.zeros((12, 12, 12))
image[3:6, 5:8, 4:7] = template
result = match_template(image, template, pad_input=True)
assert_equal(result.shape, (12, 12, 12))
assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 6, 5))
def test_padding_reflect():
template = diamond(2)
image = np.zeros((10, 10))
image[2:7, :3] = template[:, -3:]
result = match_template(image, template, pad_input=True,
mode='reflect')
assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 0))
def test_wrong_input():
image = np.ones((5, 5, 1))
template = np.ones((3, 3))
assert_raises(ValueError, match_template, template, image)
image = np.ones((5, 5))
template = np.ones((3, 3, 2))
assert_raises(ValueError, match_template, template, image)
image = np.ones((5, 5, 3, 3))
template = np.ones((3, 3, 2))
assert_raises(ValueError, match_template, template, image)
if __name__ == "__main__":
+63 -22
View File
@@ -1,30 +1,71 @@
import numpy as np
from numpy.testing import assert_array_equal
from skimage.feature.util import pairwise_hamming_distance
import matplotlib.pyplot as plt
from numpy.testing import assert_equal, assert_raises
from skimage.feature.util import (FeatureDetector, DescriptorExtractor,
_prepare_grayscale_input_2D,
_mask_border_keypoints, plot_matches)
def test_pairwise_hamming_distance_range():
"""Values of all the pairwise hamming distances should be in the range
[0, 1]."""
a = np.random.random_sample((10, 50)) > 0.5
b = np.random.random_sample((20, 50)) > 0.5
dist = pairwise_hamming_distance(a, b)
assert np.all((0 <= dist) & (dist <= 1))
def test_feature_detector():
assert_raises(NotImplementedError, FeatureDetector().detect, None)
def test_pairwise_hamming_distance_value():
"""The result of pairwise_hamming_distance of two fixed sets of boolean
vectors should be same as expected."""
np.random.seed(10)
a = np.random.random_sample((4, 100)) > 0.5
np.random.seed(20)
b = np.random.random_sample((3, 100)) > 0.5
result = pairwise_hamming_distance(a, b)
expected = np.array([[0.5 , 0.49, 0.44],
[0.44, 0.53, 0.52],
[0.4 , 0.55, 0.5 ],
[0.47, 0.48, 0.57]])
assert_array_equal(result, expected)
def test_descriptor_extractor():
assert_raises(NotImplementedError, DescriptorExtractor().extract,
None, None)
def test_prepare_grayscale_input_2D():
assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 3, 3)))
assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 1)))
assert_raises(ValueError, _prepare_grayscale_input_2D, np.zeros((3, 1, 1)))
img = _prepare_grayscale_input_2D(np.zeros((3, 3)))
img = _prepare_grayscale_input_2D(np.zeros((3, 3, 1)))
img = _prepare_grayscale_input_2D(np.zeros((1, 3, 3)))
def test_mask_border_keypoints():
keypoints = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]])
assert_equal(_mask_border_keypoints((10, 10), keypoints, 0),
[1, 1, 1, 1, 1])
assert_equal(_mask_border_keypoints((10, 10), keypoints, 2),
[0, 0, 1, 1, 1])
assert_equal(_mask_border_keypoints((4, 4), keypoints, 2),
[0, 0, 1, 0, 0])
assert_equal(_mask_border_keypoints((10, 10), keypoints, 5),
[0, 0, 0, 0, 0])
assert_equal(_mask_border_keypoints((10, 10), keypoints, 4),
[0, 0, 0, 0, 1])
def test_plot_matches():
fig, ax = plt.subplots(nrows=1, ncols=1)
shapes = (((10, 10), (10, 10)),
((10, 10), (12, 10)),
((10, 10), (10, 12)),
((10, 10), (12, 12)),
((12, 10), (10, 10)),
((10, 12), (10, 10)),
((12, 12), (10, 10)))
keypoints1 = 10 * np.random.rand(10, 2)
keypoints2 = 10 * np.random.rand(10, 2)
idxs1 = np.random.randint(10, size=10)
idxs2 = np.random.randint(10, size=10)
matches = np.column_stack((idxs1, idxs2))
for shape1, shape2 in shapes:
img1 = np.zeros(shape1)
img2 = np.zeros(shape2)
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches)
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches,
only_matches=True)
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches,
keypoints_color='r')
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches,
matches_color='r')
if __name__ == '__main__':
+146 -23
View File
@@ -1,38 +1,161 @@
import numpy as np
from skimage.util import img_as_float
def _mask_border_keypoints(image, keypoints, dist):
"""Removes keypoints that are within dist pixels from the image border."""
width = image.shape[0]
height = image.shape[1]
class FeatureDetector(object):
keypoints_filtering_mask = ((dist - 1 < keypoints[:, 0]) &
(keypoints[:, 0] < width - dist + 1) &
(dist - 1 < keypoints[:, 1]) &
(keypoints[:, 1] < height - dist + 1))
def __init__(self):
self.keypoints_ = np.array([])
return keypoints_filtering_mask
def detect(self, image):
"""Detect keypoints in image.
Parameters
----------
image : 2D array
Input image.
"""
raise NotImplementedError()
def pairwise_hamming_distance(array1, array2):
"""**Experimental function**.
class DescriptorExtractor(object):
Calculate hamming dissimilarity measure between two sets of
vectors.
def __init__(self):
self.descriptors_ = np.array([])
def extract(self, image, keypoints):
"""Extract feature descriptors in image for given keypoints.
Parameters
----------
image : 2D array
Input image.
keypoints : (N, 2) array
Keypoint locations as ``(row, col)``.
"""
raise NotImplementedError()
def plot_matches(ax, image1, image2, keypoints1, keypoints2, matches,
keypoints_color='k', matches_color=None, only_matches=False):
"""Plot matched features.
Parameters
----------
array1 : (P1, D) array
P1 vectors of size D.
array2 : (P2, D) array
P2 vectors of size D.
ax : matplotlib.axes.Axes
Matches and image are drawn in this ax.
image1 : (N, M [, 3]) array
First grayscale or color image.
image2 : (N, M [, 3]) array
Second grayscale or color image.
keypoints1 : (K1, 2) array
First keypoint coordinates as ``(row, col)``.
keypoints2 : (K2, 2) array
Second keypoint coordinates as ``(row, col)``.
matches : (Q, 2) array
Indices of corresponding matches in first and second set of
descriptors, where ``matches[:, 0]`` denote the indices in the first
and ``matches[:, 1]`` the indices in the second set of descriptors.
keypoints_color : matplotlib color, optional
Color for keypoint locations.
matches_color : matplotlib color, optional
Color for lines which connect keypoint matches. By default the
color is chosen randomly.
only_matches : bool, optional
Whether to only plot matches and not plot the keypoint locations.
"""
image1 = img_as_float(image1)
image2 = img_as_float(image2)
new_shape1 = list(image1.shape)
new_shape2 = list(image2.shape)
if image1.shape[0] < image2.shape[0]:
new_shape1[0] = image2.shape[0]
elif image1.shape[0] > image2.shape[0]:
new_shape2[0] = image1.shape[0]
if image1.shape[1] < image2.shape[1]:
new_shape1[1] = image2.shape[1]
elif image1.shape[1] > image2.shape[1]:
new_shape2[1] = image1.shape[1]
if new_shape1 != image1.shape:
new_image1 = np.zeros(new_shape1, dtype=image1.dtype)
new_image1[:image1.shape[0], :image1.shape[1]] = image1
image1 = new_image1
if new_shape2 != image2.shape:
new_image2 = np.zeros(new_shape2, dtype=image2.dtype)
new_image2[:image2.shape[0], :image2.shape[1]] = image2
image2 = new_image2
image = np.concatenate([image1, image2], axis=1)
offset = image1.shape
if not only_matches:
ax.scatter(keypoints1[:, 1], keypoints1[:, 0],
facecolors='none', edgecolors=keypoints_color)
ax.scatter(keypoints2[:, 1] + offset[1], keypoints2[:, 0],
facecolors='none', edgecolors=keypoints_color)
ax.imshow(image)
ax.axis((0, 2 * offset[1], offset[0], 0))
for i in range(matches.shape[0]):
idx1 = matches[i, 0]
idx2 = matches[i, 1]
if matches_color is None:
color = np.random.rand(3, 1)
else:
color = matches_color
ax.plot((keypoints1[idx1, 1], keypoints2[idx2, 1] + offset[1]),
(keypoints1[idx1, 0], keypoints2[idx2, 0]),
'-', color=color)
def _prepare_grayscale_input_2D(image):
image = np.squeeze(image)
if image.ndim != 2:
raise ValueError("Only 2-D gray-scale images supported.")
return img_as_float(image)
def _mask_border_keypoints(image_shape, keypoints, distance):
"""Mask coordinates that are within certain distance from the image border.
Parameters
----------
image_shape : (2, ) array_like
Shape of the image as ``(rows, cols)``.
keypoints : (N, 2) array
Keypoint coordinates as ``(rows, cols)``.
distance : int
Image border distance.
Returns
-------
distance : (P1, P2) array of dtype float
2D ndarray with value at an index (i, j) representing the hamming
distance in the range [0, 1] between ith vector in array1 and jth
vector in array2.
mask : (N, ) bool array
Mask indicating if pixels are within the image (``True``) or in the
border region of the image (``False``).
"""
distance = (array1[:, None] != array2[None]).mean(axis=2)
return distance
rows = image_shape[0]
cols = image_shape[1]
mask = (((distance - 1) < keypoints[:, 0])
& (keypoints[:, 0] < (rows - distance + 1))
& ((distance - 1) < keypoints[:, 1])
& (keypoints[:, 1] < (cols - distance + 1)))
return mask
+15 -4
View File
@@ -5,14 +5,23 @@ from ._canny import canny
from .edges import (sobel, hsobel, vsobel, scharr, hscharr, vscharr, prewitt,
hprewitt, vprewitt, roberts, roberts_positive_diagonal,
roberts_negative_diagonal)
from ._denoise import denoise_tv_chambolle
from ._denoise_cy import denoise_bilateral, denoise_tv_bregman
from ._rank_order import rank_order
from ._gabor import gabor_kernel, gabor_filter
from .thresholding import threshold_otsu, threshold_adaptive
from .thresholding import (threshold_adaptive, threshold_otsu, threshold_yen,
threshold_isodata)
from . import rank
from skimage._shared.utils import deprecated
from skimage import restoration
denoise_bilateral = deprecated('skimage.restoration.denoise_bilateral')\
(restoration.denoise_bilateral)
denoise_tv_bregman = deprecated('skimage.restoration.denoise_tv_bregman')\
(restoration.denoise_tv_bregman)
denoise_tv_chambolle = deprecated('skimage.restoration.denoise_tv_chambolle')\
(restoration.denoise_tv_chambolle)
__all__ = ['inverse',
'wiener',
'LPIFilter2D',
@@ -37,6 +46,8 @@ __all__ = ['inverse',
'rank_order',
'gabor_kernel',
'gabor_filter',
'threshold_otsu',
'threshold_adaptive',
'threshold_otsu',
'threshold_yen',
'threshold_isodata',
'rank']
+7 -4
View File
@@ -1,11 +1,11 @@
from .generic import (autolevel, bottomhat, equalize, gradient, maximum, mean,
subtract_mean, median, minimum, modal, enhance_contrast,
pop, threshold, tophat, noise_filter, entropy, otsu)
pop, threshold, tophat, noise_filter, entropy, otsu, sum)
from ._percentile import (autolevel_percentile, gradient_percentile,
mean_percentile, subtract_mean_percentile,
enhance_contrast_percentile, percentile,
pop_percentile, threshold_percentile)
from .bilateral import mean_bilateral, pop_bilateral
pop_percentile, sum_percentile, threshold_percentile)
from .bilateral import mean_bilateral, pop_bilateral, sum_bilateral
from skimage._shared.utils import deprecated
@@ -51,12 +51,15 @@ __all__ = ['autolevel',
'pop',
'pop_percentile',
'pop_bilateral',
'sum',
'sum_bilateral',
'sum_percentile',
'threshold',
'threshold_percentile',
'tophat',
'noise_filter',
'entropy',
'otsu'
'otsu',
'percentile',
# Deprecated
'percentile_autolevel',
+121 -75
View File
@@ -50,17 +50,19 @@ def autolevel_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local autolevel of an image.
Autolevel is computed on the given structuring element. Only levels between
percentiles [p0, p1] are used.
This filter locally stretches the histogram of greyvalues to cover the
entire range of values from "white" to "black".
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -74,7 +76,7 @@ def autolevel_percentile(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -86,19 +88,18 @@ def autolevel_percentile(image, selem, out=None, mask=None, shift_x=False,
def gradient_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local gradient of an image.
"""Return local gradient of an image (i.e. local maximum - local minimum).
gradient is computed on the given structuring element. Only
levels between percentiles [p0, p1] are used.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -112,7 +113,7 @@ def gradient_percentile(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -124,19 +125,18 @@ def gradient_percentile(image, selem, out=None, mask=None, shift_x=False,
def mean_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local mean of an image.
"""Return local mean of an image.
Mean is computed on the given structuring element. Only levels between
percentiles [p0, p1] are used.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -150,7 +150,7 @@ def mean_percentile(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -162,19 +162,18 @@ def mean_percentile(image, selem, out=None, mask=None, shift_x=False,
def subtract_mean_percentile(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=0, p1=1):
"""Return greyscale local subtract_mean of an image.
"""Return image subtracted from its local mean.
subtract_mean is computed on the given structuring element. Only levels
between percentiles [p0, p1] are used.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -188,7 +187,7 @@ def subtract_mean_percentile(image, selem, out=None, mask=None,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -200,19 +199,22 @@ def subtract_mean_percentile(image, selem, out=None, mask=None,
def enhance_contrast_percentile(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=0, p1=1):
"""Return greyscale local enhance_contrast of an image.
"""Enhance contrast of an image.
enhance_contrast is computed on the given structuring element. Only levels
between percentiles [p0, p1] are used.
This replaces each pixel by the local maximum if the pixel greyvalue is
closer to the local maximum than the local minimum. Otherwise it is
replaced by the local minimum.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -226,7 +228,7 @@ def enhance_contrast_percentile(image, selem, out=None, mask=None,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -238,19 +240,21 @@ def enhance_contrast_percentile(image, selem, out=None, mask=None,
def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
p0=0):
"""Return greyscale local percentile of an image.
"""Return local percentile of an image.
percentile is computed on the given structuring element. Returns the value
of the p0 lower percentile of the neighborhood value distribution.
Returns the value of the p0 lower percentile of the local greyvalue
distribution.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -263,7 +267,7 @@ def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -275,19 +279,21 @@ def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
def pop_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local pop of an image.
"""Return the local number (population) of pixels.
pop is computed on the given structuring element. Only levels between
percentiles [p0, p1] are used.
The number of pixels is defined as the number of pixels which are included
in the structuring element and the mask.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -301,7 +307,7 @@ def pop_percentile(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
"""
@@ -311,23 +317,63 @@ def pop_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=shift_y, p0=p0, p1=p1)
def threshold_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0):
"""Return greyscale local threshold of an image.
def sum_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return the local sum of pixels.
threshold is computed on the given structuring element. Returns
thresholded image such that pixels having a higher value than the the p0
percentile of the neighborhood value distribution are set to 2^nbit-1
(e.g. 255 for 8bit image).
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Note that the sum may overflow depending on the data type of the input
array.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
p0, p1 : float in [0, ..., 1]
Define the [p0, p1] percentile interval to be considered for computing
the value.
Returns
-------
out : 2-D array (same dtype as input image)
Output image.
"""
return _apply(percentile_cy._sum,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def threshold_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0):
"""Local threshold of an image.
The resulting binary mask is True if the greyvalue of the center pixel is
greater than the local mean.
Only greyvalues between percentiles [p0, p1] are considered in the filter.
Parameters
----------
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -338,7 +384,7 @@ def threshold_percentile(image, selem, out=None, mask=None, shift_x=False,
p0 : float in [0, ..., 1]
Set the percentile value.
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
local threshold : ndarray (same dtype as input)
The result of the local threshold.
+95 -33
View File
@@ -30,7 +30,7 @@ from . import bilateral_cy
from .generic import _handle_input
__all__ = ['mean_bilateral', 'pop_bilateral']
__all__ = ['mean_bilateral', 'pop_bilateral', 'sum_bilateral']
def _apply(func, image, selem, out, mask, shift_x, shift_y, s0, s1,
@@ -53,21 +53,22 @@ def mean_bilateral(image, selem, out=None, mask=None, shift_x=False,
pixels based on their spatial closeness and radiometric similarity.
Spatial closeness is measured by considering only the local pixel
neighborhood given by a structuring element (selem).
neighborhood given by a structuring element.
Radiometric similarity is defined by the greylevel interval [g-s0, g+s1]
where g is the current pixel greylevel. Only pixels belonging to the
structuring element AND having a greylevel inside this interval are
averaged. Return greyscale local bilateral_mean of an image.
where g is the current pixel greylevel.
Only pixels belonging to the structuring element and having a greylevel
inside this interval are averaged.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -81,22 +82,20 @@ def mean_bilateral(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
See also
--------
skimage.filter.denoise_bilateral for a gaussian bilateral filter.
skimage.filter.denoise_bilateral for a Gaussian bilateral filter.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import bilateral_mean
>>> # Load test image / cast to uint16
>>> ima = data.camera().astype(np.uint16)
>>> # bilateral filtering of cameraman image using a flat kernel
>>> bilat_ima = bilateral_mean(ima, disk(20), s0=10,s1=10)
>>> from skimage.filter.rank import mean_bilateral
>>> img = data.camera().astype(np.uint16)
>>> bilat_img = mean_bilateral(img, disk(20), s0=10,s1=10)
"""
@@ -106,18 +105,22 @@ def mean_bilateral(image, selem, out=None, mask=None, shift_x=False,
def pop_bilateral(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, s0=10, s1=10):
"""Return the number (population) of pixels actually inside the bilateral
neighborhood, i.e. being inside the structuring element AND having a gray
level inside the interval [g-s0, g+s1].
"""Return the local number (population) of pixels.
The number of pixels is defined as the number of pixels which are included
in the structuring element and the mask. Additionally the must have a
greylevel inside the interval [g-s0, g+s1] where g is the greyvalue of the
center pixel.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -131,20 +134,19 @@ def pop_bilateral(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> # Local mean
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> ima16 = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint16)
>>> rank.bilateral_pop(ima16, square(3), s0=10,s1=10)
>>> img = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint16)
>>> rank.pop_bilateral(img, square(3), s0=10, s1=10)
array([[3, 4, 3, 4, 3],
[4, 4, 6, 4, 4],
[3, 6, 9, 6, 3],
@@ -155,3 +157,63 @@ def pop_bilateral(image, selem, out=None, mask=None, shift_x=False,
return _apply(bilateral_cy._pop, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y, s0=s0, s1=s1)
def sum_bilateral(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, s0=10, s1=10):
"""Apply a flat kernel bilateral filter.
This is an edge-preserving and noise reducing denoising filter. It averages
pixels based on their spatial closeness and radiometric similarity.
Spatial closeness is measured by considering only the local pixel
neighborhood given by a structuring element (selem).
Radiometric similarity is defined by the greylevel interval [g-s0, g+s1]
where g is the current pixel greylevel.
Only pixels belonging to the structuring element AND having a greylevel
inside this interval are summed.
Note that the sum may overflow depending on the data type of the input
array.
Parameters
----------
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
s0, s1 : int
Define the [s0, s1] interval around the greyvalue of the center pixel
to be considered for computing the value.
Returns
-------
out : 2-D array (same dtype as input image)
Output image.
See also
--------
skimage.filter.denoise_bilateral for a Gaussian bilateral filter.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import sum_bilateral
>>> img = data.camera().astype(np.uint16)
>>> bilat_img = sum_bilateral(img, disk(10), s0=10, s1=10)
"""
return _apply(bilateral_cy._sum, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y, s0=s0, s1=s1)
+31
View File
@@ -47,6 +47,27 @@ cdef inline double _kernel_pop(Py_ssize_t* histo, double pop, dtype_t g,
else:
return 0
cdef inline double _kernel_sum(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t bilat_pop = 0
cdef Py_ssize_t sum = 0
if pop:
for i in range(max_bin):
if (g > (i - s0)) and (g < (i + s1)):
bilat_pop += histo[i]
sum += histo[i] * i
if bilat_pop:
return sum
else:
return 0
else:
return 0
def _mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
@@ -68,3 +89,13 @@ def _pop(dtype_t[:, ::1] image,
_core(_kernel_pop[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, s0, s1, max_bin)
def _sum(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t s0, Py_ssize_t s1,
Py_ssize_t max_bin):
_core(_kernel_sum[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, s0, s1, max_bin)
+276 -162
View File
@@ -77,16 +77,19 @@ def _apply(func, image, selem, out, mask, shift_x, shift_y, out_dtype=None):
def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Autolevel image using local histogram.
"""Auto-level image using local histogram.
This filter locally stretches the histogram of greyvalues to cover the
entire range of values from "white" to "black".
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -97,7 +100,7 @@ def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
@@ -105,10 +108,8 @@ def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import autolevel
>>> # Load test image
>>> ima = data.camera()
>>> # Stretch image contrast locally
>>> auto = autolevel(ima, disk(20))
>>> img = data.camera()
>>> auto = autolevel(img, disk(5))
"""
@@ -117,17 +118,20 @@ def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def bottomhat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns greyscale local bottomhat of an image.
"""Local bottom-hat of an image.
This filter computes the morphological closing of the image and then
subtracts the result from the original image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : 2-D array
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -137,8 +141,16 @@ def bottomhat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
bottomhat : ndarray (same dtype as input image)
The result of the local bottomhat.
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import bottomhat
>>> img = data.camera()
>>> out = bottomhat(img, disk(5))
"""
@@ -151,12 +163,12 @@ def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -167,7 +179,7 @@ def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
@@ -175,10 +187,8 @@ def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import equalize
>>> # Load test image
>>> ima = data.camera()
>>> # Local equalization
>>> equ = equalize(ima, disk(20))
>>> img = data.camera()
>>> equ = equalize(img, disk(5))
"""
@@ -187,18 +197,16 @@ def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def gradient(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local gradient of an image (i.e. local maximum - local
minimum).
"""Return local gradient of an image (i.e. local maximum - local minimum).
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -209,9 +217,17 @@ def gradient(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import gradient
>>> img = data.camera()
>>> out = gradient(img, disk(5))
"""
return _apply(generic_cy._gradient, image, selem,
@@ -219,17 +235,16 @@ def gradient(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local maximum of an image.
"""Return local maximum of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -240,7 +255,7 @@ def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
See also
@@ -249,8 +264,16 @@ def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Notes
-----
* the lower algorithm complexity makes the rank.maximum() more efficient
for larger images and structuring elements
The lower algorithm complexity makes the `skimage.filter.rank.maximum`
more efficient for larger images and structuring elements.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import maximum
>>> img = data.camera()
>>> out = maximum(img, disk(5))
"""
@@ -259,16 +282,16 @@ def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def mean(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mean of an image.
"""Return local mean of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -279,7 +302,7 @@ def mean(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
@@ -287,10 +310,8 @@ def mean(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import mean
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = mean(ima, disk(20))
>>> img = data.camera()
>>> avg = mean(img, disk(5))
"""
@@ -304,12 +325,12 @@ def subtract_mean(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -320,9 +341,17 @@ def subtract_mean(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import subtract_mean
>>> img = data.camera()
>>> out = subtract_mean(img, disk(5))
"""
return _apply(generic_cy._subtract_mean, image, selem,
@@ -330,16 +359,16 @@ def subtract_mean(image, selem, out=None, mask=None, shift_x=False,
def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local median of an image.
"""Return local median of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -350,7 +379,7 @@ def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
@@ -358,10 +387,8 @@ def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import median
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = median(ima, disk(20))
>>> img = data.camera()
>>> med = median(img, disk(5))
"""
@@ -370,16 +397,16 @@ def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local minimum of an image.
"""Return local minimum of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -390,7 +417,7 @@ def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
See also
@@ -399,8 +426,16 @@ def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Notes
-----
* the lower algorithm complexity makes the rank.minimum() more efficient
for larger images and structuring elements
The lower algorithm complexity makes the `skimage.filter.rank.minimum` more
efficient for larger images and structuring elements.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import minimum
>>> img = data.camera()
>>> out = minimum(img, disk(5))
"""
@@ -409,16 +444,18 @@ def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def modal(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mode of an image.
"""Return local mode of an image.
The mode is the value that appears most often in the local histogram.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -429,9 +466,17 @@ def modal(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import modal
>>> img = data.camera()
>>> out = modal(img, disk(5))
"""
return _apply(generic_cy._modal, image, selem,
@@ -440,18 +485,20 @@ def modal(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def enhance_contrast(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Enhance an image replacing each pixel by the local maximum if pixel
greylevel is closest to maximimum than local minimum OR local minimum
otherwise.
"""Enhance contrast of an image.
This replaces each pixel by the local maximum if the pixel greyvalue is
closer to the local maximum than the local minimum. Otherwise it is
replaced by the local minimum.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -462,7 +509,7 @@ def enhance_contrast(image, selem, out=None, mask=None, shift_x=False,
Returns
Output image.
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
The result of the local enhance_contrast.
Examples
@@ -470,10 +517,8 @@ def enhance_contrast(image, selem, out=None, mask=None, shift_x=False,
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import enhance_contrast
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = enhance_contrast(ima, disk(20))
>>> img = data.camera()
>>> out = enhance_contrast(img, disk(5))
"""
@@ -482,17 +527,19 @@ def enhance_contrast(image, selem, out=None, mask=None, shift_x=False,
def pop(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return the number (population) of pixels actually inside the
neighborhood.
"""Return the local number (population) of pixels.
The number of pixels is defined as the number of pixels which are included
in the structuring element and the mask.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -503,20 +550,19 @@ def pop(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> # Local mean
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
>>> img = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> rank.pop(ima, square(3))
>>> rank.pop(img, square(3))
array([[4, 6, 6, 6, 4],
[6, 9, 9, 9, 6],
[6, 9, 9, 9, 6],
@@ -529,17 +575,20 @@ def pop(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
mask=mask, shift_x=shift_x, shift_y=shift_y)
def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local threshold of an image.
def sum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return the local sum of pixels.
Note that the sum may overflow depending on the data type of the input
array.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -550,20 +599,68 @@ def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> img = np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> rank.sum(img, square(3))
array([[1, 2, 3, 2, 1],
[2, 4, 6, 4, 2],
[3, 6, 9, 6, 3],
[2, 4, 6, 4, 2],
[1, 2, 3, 2, 1]], dtype=uint8)
"""
return _apply(generic_cy._sum, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Local threshold of an image.
The resulting binary mask is True if the greyvalue of the center pixel is
greater than the local mean.
Parameters
----------
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> # Local threshold
>>> from skimage.morphology import square
>>> from skimage.filter.rank import threshold
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
>>> img = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> threshold(ima, square(3))
>>> threshold(img, square(3))
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
@@ -577,16 +674,19 @@ def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def tophat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local tophat of an image.
"""Local top-hat of an image.
This filter computes the morphological opening of the image and then
subtracts the result from the original image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -597,9 +697,17 @@ def tophat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import tophat
>>> img = data.camera()
>>> out = tophat(img, disk(5))
"""
return _apply(generic_cy._tophat, image, selem,
@@ -608,16 +716,16 @@ def tophat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def noise_filter(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Returns the noise feature as described in [Hashimoto12]_
"""Noise feature as described in [Hashimoto12]_.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -633,9 +741,17 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import noise_filter
>>> img = data.camera()
>>> out = noise_filter(img, disk(5))
"""
# ensure that the central pixel in the structuring element is empty
@@ -650,18 +766,19 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False,
def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the entropy [1]_ computed locally. Entropy is computed
using base 2 logarithm i.e. the filter returns the minimum number of
bits needed to encode local greylevel distribution.
"""Local entropy [1]_.
The entropy is computed using base 2 logarithm i.e. the filter returns the
minimum number of bits needed to encode the local greylevel distribution.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
image : 2-D array (uint8, uint16)
Input image.
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
out : 2-D array (same dtype as input)
If None, a new array is allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
@@ -677,16 +794,15 @@ def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
References
----------
.. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory)>
.. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory)
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import entropy
>>> from skimage.morphology import disk
>>> a8 = data.camera()
>>> ent8 = entropy(a8, disk(5))
>>> img = data.camera()
>>> ent = entropy(img, disk(5))
"""
@@ -696,13 +812,13 @@ def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the Otsu's threshold value for each pixel.
"""Local Otsu's threshold value for each pixel.
Parameters
----------
image : ndarray
Image array (uint8 array).
selem : ndarray
selem : 2-D array
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
@@ -716,7 +832,7 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Returns
-------
out : ndarray (same dtype as input image)
out : 2-D array (same dtype as input image)
Output image.
References
@@ -725,14 +841,12 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import otsu
>>> from skimage.morphology import disk
>>> # defining a 8-bit test images
>>> a8 = data.camera()
>>> loc_otsu = otsu(a8, disk(5))
>>> thresh_image = a8 >= loc_otsu
>>> img = data.camera()
>>> local_otsu = otsu(img, disk(5))
>>> thresh_image = img >= local_otsu
"""
+25
View File
@@ -222,6 +222,22 @@ cdef inline double _kernel_pop(Py_ssize_t* histo, double pop, dtype_t g,
return pop
cdef inline double _kernel_sum(Py_ssize_t* histo, double pop,dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t sum = 0
if pop:
for i in range(max_bin):
sum += histo[i] * i
return sum
else:
return 0
cdef inline double _kernel_threshold(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
@@ -455,6 +471,15 @@ def _pop(dtype_t[:, ::1] image,
_core(_kernel_pop[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _sum(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_sum[dtype_t], image, selem, mask,
out, shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _threshold(dtype_t[:, ::1] image,
char[:, ::1] selem,
+32
View File
@@ -90,6 +90,29 @@ cdef inline double _kernel_mean(Py_ssize_t* histo, double pop, dtype_t g,
else:
return 0
cdef inline double _kernel_sum(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, sum, sum_g, n
if pop:
sum = 0
sum_g = 0
n = 0
for i in range(max_bin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
sum_g += histo[i] * i
if n > 0:
return sum_g
else:
return 0
else:
return 0
cdef inline double _kernel_subtract_mean(Py_ssize_t* histo, double pop,
dtype_t g,
@@ -245,6 +268,15 @@ def _mean(dtype_t[:, ::1] image,
_core(_kernel_mean[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _sum(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_sum[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _subtract_mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
+42
View File
@@ -498,6 +498,48 @@ def test_percentile_median():
img_max = rank.median(img16, selem=selem)
assert_array_equal(img_p0, img_max)
def test_sum():
# check the number of valid pixels in the neighborhood
image8 = np.array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=np.uint8)
image16 = 400*np.array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=np.uint16)
elem = np.ones((3, 3), dtype=np.uint8)
out8 = np.empty_like(image8)
out16 = np.empty_like(image16)
mask = np.ones(image8.shape, dtype=np.uint8)
r = np.array([[1, 2, 3, 2, 1],
[2, 4, 6, 4, 2],
[3, 6, 9, 6, 3],
[2, 4, 6, 4, 2],
[1, 2, 3, 2, 1]], dtype=np.uint8)
rank.sum(image=image8, selem=elem, out=out8, mask=mask)
assert_array_equal(r, out8)
rank.sum_percentile(image=image8, selem=elem, out=out8, mask=mask,p0=.0,p1=1.)
assert_array_equal(r, out8)
rank.sum_bilateral(image=image8, selem=elem, out=out8, mask=mask,s0=255,s1=255)
assert_array_equal(r, out8)
r = 400* np.array([[1, 2, 3, 2, 1],
[2, 4, 6, 4, 2],
[3, 6, 9, 6, 3],
[2, 4, 6, 4, 2],
[1, 2, 3, 2, 1]], dtype=np.uint16)
rank.sum(image=image16, selem=elem, out=out16, mask=mask)
assert_array_equal(r, out16)
rank.sum_percentile(image=image16, selem=elem, out=out16, mask=mask,p0=.0,p1=1.)
assert_array_equal(r, out16)
rank.sum_bilateral(image=image16, selem=elem, out=out16, mask=mask,s0=1000,s1=1000)
assert_array_equal(r, out16)
if __name__ == "__main__":
run_module_suite()
-3
View File
@@ -14,7 +14,6 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('rank/tests')
cython(['_ctmf.pyx'], working_path=base_path)
cython(['_denoise_cy.pyx'], working_path=base_path)
cython(['rank/core_cy.pyx'], working_path=base_path)
cython(['rank/generic_cy.pyx'], working_path=base_path)
cython(['rank/percentile_cy.pyx'], working_path=base_path)
@@ -22,8 +21,6 @@ def configuration(parent_package='', top_path=None):
config.add_extension('_ctmf', sources=['_ctmf.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_denoise_cy', sources=['_denoise_cy.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('rank.core_cy', sources=['rank/core_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('rank.generic_cy', sources=['rank/generic_cy.c'],
+54 -2
View File
@@ -5,7 +5,8 @@ import skimage
from skimage import data
from skimage.filter.thresholding import (threshold_adaptive,
threshold_otsu,
threshold_yen)
threshold_yen,
threshold_isodata)
class TestSimpleImage():
@@ -43,10 +44,29 @@ class TestSimpleImage():
assert threshold_yen(image) == 127
def test_yen_binary(self):
image = np.zeros([2,256], dtype='uint8')
image = np.zeros([2,256], dtype=np.uint8)
image[0] = 255
assert threshold_yen(image) < 1
def test_yen_blank_zero(self):
image = np.zeros((5, 5), dtype=np.uint8)
assert threshold_yen(image) == 0
def test_yen_blank_max(self):
image = np.empty((5, 5), dtype=np.uint8)
image.fill(255)
assert threshold_yen(image) == 255
def test_isodata(self):
assert threshold_isodata(self.image) == 2
def test_isodata_blank_zero(self):
image = np.zeros((5, 5), dtype=np.uint8)
assert threshold_isodata(image) == 0
def test_isodata_linspace(self):
assert -63.8 < threshold_isodata(np.linspace(-127, 0, 256)) < -63.6
def test_threshold_adaptive_generic(self):
def func(arr):
return arr.sum() / arr.shape[0]
@@ -114,6 +134,11 @@ def test_otsu_lena_image():
assert 140 < threshold_otsu(lena) < 142
def test_yen_camera_image():
camera = skimage.img_as_ubyte(data.camera())
assert 197 < threshold_yen(camera) < 199
def test_yen_coins_image():
coins = skimage.img_as_ubyte(data.coins())
assert 109 < threshold_yen(coins) < 111
@@ -124,5 +149,32 @@ def test_yen_coins_image_as_float():
assert 0.43 < threshold_yen(coins) < 0.44
def test_isodata_camera_image():
camera = skimage.img_as_ubyte(data.camera())
assert threshold_isodata(camera) == 88
def test_isodata_coins_image():
coins = skimage.img_as_ubyte(data.coins())
assert threshold_isodata(coins) == 107
def test_isodata_moon_image():
moon = skimage.img_as_ubyte(data.moon())
assert threshold_isodata(moon) == 87
def test_isodata_moon_image_negative_int():
moon = skimage.img_as_ubyte(data.moon()).astype(np.int32)
moon -= 100
assert threshold_isodata(moon) == -13
def test_isodata_moon_image_negative_float():
moon = skimage.img_as_ubyte(data.moon()).astype(np.float64)
moon -= 100
assert -13 < threshold_isodata(moon) < -12
if __name__ == '__main__':
np.testing.run_module_suite()
+81 -9
View File
@@ -1,4 +1,7 @@
__all__ = ['threshold_adaptive', 'threshold_otsu', 'threshold_yen']
__all__ = ['threshold_adaptive',
'threshold_otsu',
'threshold_yen',
'threshold_isodata']
import numpy as np
import scipy.ndimage
@@ -129,7 +132,7 @@ def threshold_otsu(image, nbins=256):
# Clip ends to align class 1 and class 2 variables:
# The last value of `weight1`/`mean1` should pair with zero values in
# `weight2`/`mean2`, which do not exist.
variance12 = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:])**2
variance12 = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2
idx = np.argmax(variance12)
threshold = bin_centers[:-1][idx]
@@ -172,15 +175,84 @@ def threshold_yen(image, nbins=256):
>>> binary = image <= thresh
"""
hist, bin_centers = histogram(image, nbins)
norm_histo = hist.astype(float) / hist.sum() # Probability mass function
P1 = np.cumsum(norm_histo) # Cumulative normalized histogram
P1_sq = np.cumsum(norm_histo ** 2)
# On blank images (e.g. filled with 0) with int dtype, `histogram()`
# returns `bin_centers` containing only one value. Speed up with it.
if bin_centers.size == 1:
return bin_centers[0]
# Calculate probability mass function
pmf = hist.astype(np.float32) / hist.sum()
P1 = np.cumsum(pmf) # Cumulative normalized histogram
P1_sq = np.cumsum(pmf ** 2)
# Get cumsum calculated from end of squared array:
P2_sq = np.cumsum(norm_histo[::-1] ** 2)[::-1]
P2_sq = np.cumsum(pmf[::-1] ** 2)[::-1]
# P2_sq indexes is shifted +1. I assume, with P1[:-1] it's help avoid '-inf'
# in crit. ImageJ Yen implementation replaces those values by zero.
crit = np.log(((P1_sq[:-1] * P2_sq[1:]) ** -1) * \
crit = np.log(((P1_sq[:-1] * P2_sq[1:]) ** -1) *
(P1[:-1] * (1.0 - P1[:-1])) ** 2)
max_crit = np.argmax(crit)
threshold = bin_centers[:-1][max_crit]
return bin_centers[crit.argmax()]
def threshold_isodata(image, nbins=256):
"""Return threshold value based on ISODATA method.
Histogram-based threshold, known as Ridler-Calvard method or intermeans.
Parameters
----------
image : array
Input image.
nbins : int, optional
Number of bins used to calculate histogram. This value is ignored for
integer arrays.
Returns
-------
threshold : float or int, corresponding input array dtype.
Upper threshold value. All pixels intensities that less or equal of
this value assumed as background.
References
----------
.. [1] Ridler, TW & Calvard, S (1978), "Picture thresholding using an
iterative selection method"
.. [2] IEEE Transactions on Systems, Man and Cybernetics 8: 630-632,
http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=4310039
.. [3] Sezgin M. and Sankur B. (2004) "Survey over Image Thresholding
Techniques and Quantitative Performance Evaluation" Journal of
Electronic Imaging, 13(1): 146-165,
http://www.busim.ee.boun.edu.tr/~sankur/SankurFolder/Threshold_survey.pdf
.. [4] ImageJ AutoThresholder code,
http://fiji.sc/wiki/index.php/Auto_Threshold
Examples
--------
>>> from skimage.data import coins
>>> image = coins()
>>> thresh = threshold_isodata(image)
>>> binary = image > thresh
"""
hist, bin_centers = histogram(image, nbins)
# On blank images (e.g. filled with 0) with int dtype, `histogram()`
# returns `bin_centers` containing only one value. Speed up with it.
if bin_centers.size == 1:
return bin_centers[0]
# It is not necessary to calculate the probability mass function here,
# because the l and h fractions already include the normalization.
pmf = hist.astype(np.float32) # / hist.sum()
cpmfl = np.cumsum(pmf, dtype=np.float32)
cpmfh = np.cumsum(pmf[::-1], dtype=np.float32)[::-1]
binnums = np.arange(pmf.size, dtype=np.uint8)
# l and h contain average value of pixels in sum of bins, calculated
# from lower to higher and from higher to lower respectively.
l = np.ma.divide(np.cumsum(pmf * binnums, dtype=np.float32), cpmfl)
h = np.ma.divide(
np.cumsum((pmf[::-1] * binnums[::-1]), dtype=np.float32)[::-1],
cpmfh)
allmean = (l + h) / 2.0
threshold = bin_centers[np.nonzero(allmean.round() == binnums)[0][0]]
# This implementation returns threshold where
# `background <= threshold < foreground`.
return threshold
+3 -1
View File
@@ -1,7 +1,9 @@
from .spath import shortest_path
from .mcp import MCP, MCP_Geometric, route_through_array
from .mcp import MCP, MCP_Geometric, MCP_Connect, MCP_Flexible, route_through_array
__all__ = ['shortest_path',
'MCP',
'MCP_Geometric',
'MCP_Connect',
'MCP_Flexible',
'route_through_array']
+23 -9
View File
@@ -9,22 +9,36 @@ cimport numpy as cnp
ctypedef heap.BOOL_T BOOL_T
ctypedef unsigned char DIM_T
ctypedef cnp.float64_t FLOAT_T
ctypedef cnp.intp_t INDEX_T
ctypedef cnp.int8_t EDGE_T
ctypedef cnp.int8_t OFFSET_T
ctypedef cnp.int16_t OFFSETS_INDEX_T
cdef class MCP:
cdef heap.FastUpdateBinaryHeap costs_heap
cdef object costs_shape
cdef object _starts
cdef object _ends
cdef DIM_T dim
cdef object flat_costs
cdef object flat_cumulative_costs
cdef object traceback_offsets
cdef object flat_pos_edge_map
cdef object flat_neg_edge_map
cdef readonly object offsets
cdef object flat_offsets
cdef object offset_lengths
cdef BOOL_T dirty
cdef BOOL_T use_start_cost
# if use_start_cost is true, the cost of the starting element is added to
# the cost of the path. Set to true by default in the base class...
# Arrays used during front propagation
cdef FLOAT_T [:] flat_costs
cdef FLOAT_T [:] flat_cumulative_costs
cdef OFFSETS_INDEX_T [:] traceback_offsets
cdef EDGE_T [:,:] flat_pos_edge_map
cdef EDGE_T [:,:] flat_neg_edge_map
cdef OFFSET_T [:,:] offsets
cdef INDEX_T [:] flat_offsets
cdef FLOAT_T [:] offset_lengths
# Methods
cpdef int goal_reached(self, INDEX_T index, FLOAT_T cumcost)
cdef FLOAT_T _travel_cost(self, FLOAT_T old_cost, FLOAT_T new_cost, FLOAT_T offset_length)
cdef void _examine_neighbor(self, INDEX_T index, INDEX_T new_index, FLOAT_T offset_length)
cdef void _update_node(self, INDEX_T index, INDEX_T new_index, FLOAT_T offset_length)
+380 -99
View File
@@ -5,6 +5,7 @@ for use with data on a n-dimensional lattice.
Original author: Zachary Pincus
Inspired by code from Almar Klein
Later modifications by Almar Klein (Dec 2013)
License: BSD
@@ -39,13 +40,9 @@ import heap
cimport numpy as cnp
cimport heap
ctypedef cnp.int8_t OFFSET_T
OFFSET_D = np.int8
ctypedef cnp.int16_t OFFSETS_INDEX_T
OFFSETS_INDEX_D = np.int16
ctypedef cnp.int8_t EDGE_T
EDGE_D = np.int8
ctypedef cnp.intp_t INDEX_T
INDEX_D = np.intp
FLOAT_D = np.float64
@@ -106,7 +103,7 @@ def _offset_edge_map(shape, offsets):
[0, 0, 2, 1]], dtype=int8)
"""
indices = np.indices(shape) # indices.shape = (n,)+shape
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
@@ -172,11 +169,12 @@ def make_offsets(d, fully_connected):
def _unravel_index_fortran(flat_indices, shape):
"""_unravel_index_fortran(flat_indices, shape)
Given a flat index into an n-d fortran-strided array, return an index tuple.
Given a flat index into an n-d fortran-strided array, return an
index tuple.
"""
strides = np.multiply.accumulate([1] + list(shape[:-1]))
indices = [tuple(idx/strides % shape) for idx in flat_indices]
indices = [tuple((idx // strides) % shape) for idx in flat_indices]
return indices
@@ -185,7 +183,8 @@ def _unravel_index_fortran(flat_indices, shape):
def _ravel_index_fortran(indices, shape):
"""_ravel_index_fortran(flat_indices, shape)
Given an index tuple into an n-d fortran-strided array, return a flat index.
Given an index tuple into an n-d fortran-strided array, return a
flat index.
"""
strides = np.multiply.accumulate([1] + list(shape[:-1]))
@@ -209,7 +208,7 @@ def _normalize_indices(indices, shape):
for i, s in zip(index, shape):
i = int(i)
if i < 0:
i = s+i
i = s + i
if not (0 <= i < s):
return None
new_index.append(i)
@@ -220,14 +219,15 @@ def _normalize_indices(indices, shape):
@cython.boundscheck(True)
@cython.wraparound(True)
def _reverse(arr):
"""Reverse index an array safely, with bounds/wraparound checks on."""
"""Reverse index an array safely, with bounds/wraparound checks on.
"""
return arr[::-1]
@cython.boundscheck(False)
@cython.wraparound(False)
cdef class MCP:
"""MCP(costs, offsets=None, fully_connected=True)
"""MCP(costs, offsets=None, fully_connected=True, sampling=None)
A class for finding the minimum cost path through a given n-d costs array.
@@ -261,7 +261,10 @@ cdef class MCP:
generated neighborhood. If true, the path may go along diagonals
between elements of the `costs` array; otherwise only axial moves are
permitted.
sampling : tuple, optional
For each dimension, specifies the distance between two cells/voxels.
If not given or None, the distance is assumed unit.
Attributes
----------
offsets : ndarray
@@ -271,15 +274,27 @@ cdef class MCP:
returned by the find_costs() method.
"""
def __init__(self, costs, offsets=None, fully_connected=True):
"""__init__(costs, offsets=None, fully_connected=True)
def __init__(self, costs, offsets=None, fully_connected=True,
sampling=None):
"""__init__(costs, offsets=None, fully_connected=True, sampling=None)
See class documentation.
"""
costs = np.asarray(costs)
if not np.can_cast(costs.dtype, FLOAT_D):
raise TypeError('cannot cast costs array to ' + str(FLOAT_D))
# Check sampling
if sampling is None:
sampling = np.array([1.0 for s in costs.shape], FLOAT_D)
elif isinstance(sampling, (list, tuple)):
sampling = np.array(sampling, FLOAT_D)
if sampling.ndim != 1 or len(sampling) != costs.ndim:
raise ValueError('Need one sampling element per dimension.')
else:
raise ValueError('Invalid type for sampling: %r.' % type(sampling))
# We use flat, fortran-style indexing here (could use C-style,
# but this is my code and I like fortran-style! Also, it's
# faster when working with image arrays, which are often
@@ -287,23 +302,21 @@ cdef class MCP:
self.flat_costs = costs.astype(FLOAT_D).flatten('F')
size = self.flat_costs.shape[0]
self.flat_cumulative_costs = np.empty(size, dtype=FLOAT_D)
self.flat_cumulative_costs.fill(np.inf)
self.dim = len(costs.shape)
self.costs_shape = costs.shape
self.costs_heap = heap.FastUpdateBinaryHeap(initial_capacity=size,
self.costs_heap = heap.FastUpdateBinaryHeap(initial_capacity=128,
max_reference=size-1)
# This array stores, for each point, the index into the offset
# array (see below) that leads to that point from the
# predecessor point.
self.traceback_offsets = np.empty(size, dtype=OFFSETS_INDEX_D)
self.traceback_offsets.fill(-1)
# The offsets are a list of relative offsets from a central
# point to each point in the relevant neighborhood. (e.g. (-1,
# 0) might be a 2d offset).
# These offsets are raveled to provide flat, 1d offsets that can be used
# in the same way for flat indices to move to neighboring points.
# These offsets are raveled to provide flat, 1d offsets that can be
# used in the same way for flat indices to move to neighboring points.
if offsets is None:
offsets = make_offsets(self.dim, fully_connected)
self.offsets = np.array(offsets, dtype=OFFSET_D)
@@ -313,9 +326,9 @@ cdef class MCP:
# Instead of unraveling each index during the pathfinding algorithm, we
# will use a pre-computed "edge map" that specifies for each dimension
# whether a given index is on a lower or upper boundary (or none at all)
# Flatten this map to get something that can be indexed as by the same
# flat indices as elsewhere.
# whether a given index is on a lower or upper boundary (or none at
# all). Flatten this map to get something that can be indexed as by the
# same flat indices as elsewhere.
# 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.
@@ -325,26 +338,80 @@ cdef class MCP:
# The offset lengths are the distances traveled along each offset
self.offset_lengths = np.sqrt(
np.sum(self.offsets**2, axis=1)).astype(FLOAT_D)
self.offset_lengths = np.sqrt(np.sum((sampling * self.offsets)**2,
axis=1)).astype(FLOAT_D)
self.dirty = 0
self.use_start_cost = 1
def _reset(self):
"""_reset()
Clears paths found by find_costs().
"""
self.costs_heap.reset()
self.traceback_offsets.fill(-1)
self.flat_cumulative_costs.fill(np.inf)
self.traceback_offsets[...] = -2 # -2 is not reached, -1 is start
self.flat_cumulative_costs[...] = np.inf
self.dirty = 0
# Get starts and ends
# We do not pass them in as arguments for backwards compat
starts, ends = self._starts, self._ends
# push each start point into the heap. Note that we use flat indexing!
for start in _ravel_index_fortran(starts, self.costs_shape):
self.traceback_offsets[start] = -1
if self.use_start_cost:
self.costs_heap.push_fast(self.flat_costs[start], start)
else:
self.costs_heap.push_fast(0, start)
cdef FLOAT_T _travel_cost(self, FLOAT_T old_cost,
FLOAT_T new_cost, FLOAT_T offset_length):
""" float _travel_cost(float old_cost, float new_cost,
float offset_length)
The travel cost for going from the current node to the next.
Default is simply the cost of the next node.
"""
return new_cost
def find_costs(self, starts, ends=None, find_all_ends=True):
cpdef int goal_reached(self, INDEX_T index, FLOAT_T cumcost):
""" int goal_reached(int index, float cumcost)
This method is called each iteration after popping an index
from the heap, before examining the neighbours.
This method can be overloaded to modify the behavior of the MCP
algorithm. An example might be to stop the algorithm when a
certain cumulative cost is reached, or when the front is a
certain distance away from the seed point.
This method should return 1 if the algorithm should not check
the current point's neighbours and 2 if the algorithm is now
done.
"""
return 0
cdef void _examine_neighbor(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" _examine_neighbor(int index, int new_index, float offset_length)
This method is called once for every pair of neighboring nodes,
as soon as both nodes become frozen.
"""
pass
cdef void _update_node(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" _update_node(int index, int new_index, float offset_length)
This method is called when a node is updated.
"""
pass
def find_costs(self, starts, ends=None, find_all_ends=True,
max_coverage=1.0, max_cumulative_cost=None, max_cost=None):
"""
Find the minimum-cost path from the given starting points.
@@ -392,10 +459,13 @@ cdef class MCP:
cdef BOOL_T use_ends = 0
cdef INDEX_T num_ends
cdef BOOL_T all_ends = find_all_ends
cdef cnp.ndarray[INDEX_T, ndim=1] flat_ends
cdef INDEX_T [:] flat_ends
starts = _normalize_indices(starts, self.costs_shape)
if starts is None:
raise ValueError('start points must all be within the costs array')
elif not starts:
raise ValueError('no valid start points to start front' +
'propagation')
if ends is not None:
ends = _normalize_indices(ends, self.costs_shape)
if ends is None:
@@ -406,57 +476,66 @@ cdef class MCP:
flat_ends = np.array(_ravel_index_fortran(
ends, self.costs_shape), dtype=INDEX_D)
if self.dirty:
self._reset()
# lookup and array-ify object attributes for fast use
# Always perform a reset to (re)initialize our arrays and start
# positions
self._starts, self._ends = starts, ends
self._reset()
# Get shorter names for arrays
cdef FLOAT_T [:] flat_costs = self.flat_costs
cdef FLOAT_T [:] flat_cumulative_costs = self.flat_cumulative_costs
cdef OFFSETS_INDEX_T [:] traceback_offsets = self.traceback_offsets
cdef EDGE_T [:,:] flat_pos_edge_map = self.flat_pos_edge_map
cdef EDGE_T [:,:] flat_neg_edge_map = self.flat_neg_edge_map
cdef OFFSET_T [:,:] offsets = self.offsets
cdef INDEX_T [:] flat_offsets = self.flat_offsets
cdef FLOAT_T [:] offset_lengths = self.offset_lengths
# Short names for other attributes
cdef heap.FastUpdateBinaryHeap costs_heap = self.costs_heap
cdef cnp.ndarray[FLOAT_T, ndim=1] flat_costs = self.flat_costs
cdef cnp.ndarray[FLOAT_T, ndim=1] flat_cumulative_costs = \
self.flat_cumulative_costs
cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \
self.traceback_offsets
cdef cnp.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \
self.flat_pos_edge_map
cdef cnp.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \
self.flat_neg_edge_map
cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets
cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets
cdef cnp.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths
cdef DIM_T dim = self.dim
cdef int num_offsets = len(flat_offsets)
# push each start point into the heap. Note that we use flat indexing!
for start in _ravel_index_fortran(starts, self.costs_shape):
if self.use_start_cost:
costs_heap.push_fast(flat_costs[start], start)
else:
costs_heap.push_fast(0, start)
cdef FLOAT_T cost, new_cost
# Variables used during front propagation
cdef FLOAT_T cost, new_cost, cumcost, new_cumcost, offset_length
cdef INDEX_T index, new_index
cdef BOOL_T is_at_edge, use_offset
cdef INDEX_T d, i
cdef INDEX_T d, i, iter
cdef OFFSET_T offset
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
while 1:
cdef int goal_reached
cdef INDEX_T maxiter = int(max_coverage * flat_costs.size)
for iter in range(maxiter):
# This is rather like a while loop, except we are guaranteed to
# exit, which is nice during developing to prevent eternal loops.
# Find the point with the minimum cost in the heap. Once
# popped, this point's minimum cost path has been found.
if costs_heap.count == 0:
# nothing in the heap: we've found paths to every
# point in the array
break
cost = costs_heap.pop_fast()
# Get current cumulative cost and index from the heap
cumcost = costs_heap.pop_fast()
index = costs_heap._popped_ref
# Record the cost we found to this point
flat_cumulative_costs[index] = cost
flat_cumulative_costs[index] = cumcost
# Check if goal is reached
goal_reached = self.goal_reached(index, cumcost)
if goal_reached > 0:
if goal_reached == 1:
continue # Skip neighbours
else:
break # Done completely
if use_ends:
# If we're only tracing out a path to one or more
# endpoints, check to see if this is an endpoint, and
@@ -470,7 +549,7 @@ cdef class MCP:
# if we've found one or all of the end points (as
# requested), stop searching
break
# Look into the edge map to see if this point is at an
# edge along any axis
is_at_edge = 0
@@ -504,58 +583,75 @@ cdef class MCP:
# push over the edge, then we go on.
if not use_offset:
continue
# using the flat offsets, calculate the new flat index
new_index = index + flat_offsets[i]
# Get offset length
offset_length = offset_lengths[i]
# If we have already found the best path here then
# ignore this point
if flat_cumulative_costs[new_index] != inf:
# Give subclass the oportunity to examine these two nodes
# Note that only when both nodes are "frozen" their
# cumulative cost is set. By doing the check here, each
# pair of nodes is checked exactly once.
self._examine_neighbor(index, new_index, offset_length)
continue
# If the cost at this point is negative or infinite, ignore it
# Get cost and new cost
cost = flat_costs[index]
new_cost = flat_costs[new_index]
# If the cost at this point is negative or infinite, ignore it
if new_cost < 0 or new_cost == inf:
continue
# Calculate new cumulative cost
new_cumcost = cumcost + self._travel_cost(cost, new_cost,
offset_length)
# Now we ask the heap to append or update the cost to
# this new point, but only if that point isn't already
# in the heap, or it is but the new cost is lower.
travel_cost = self._travel_cost(flat_costs[index],
new_cost,
offset_lengths[i])
# don't push infs into the heap though!
new_cost = cost + travel_cost
if new_cost != inf:
costs_heap.push_if_lower_fast(new_cost, new_index)
if new_cumcost != inf:
costs_heap.push_if_lower_fast(new_cumcost, new_index)
# If we did perform an append or update, we should
# record the offset from the predecessor to this new
# point
if costs_heap._pushed:
traceback_offsets[new_index] = i
self._update_node(index, new_index, offset_length)
# Un-flatten the costs and traceback arrays for human consumption.
cumulative_costs = flat_cumulative_costs.reshape(self.costs_shape,
order='F')
traceback = traceback_offsets.reshape(self.costs_shape, order='F')
cumulative_costs = np.asarray(flat_cumulative_costs)
cumulative_costs = cumulative_costs.reshape(self.costs_shape,
order='F')
traceback = np.asarray(traceback_offsets)
traceback = traceback.reshape(self.costs_shape, order='F')
self.dirty = 1
return cumulative_costs, traceback
def traceback(self, end):
"""traceback(end)
Trace a minimum cost path through the pre-calculated traceback array.
This convenience function reconstructs the the minimum cost path to a
given end position from one of the starting indices provided to
find_costs(), which must have been called previously. This function
can be called as many times as desired after find_costs() has been
run.
Parameters
----------
end : iterable
An n-d index into the `costs` array.
Returns
-------
traceback : list of n-d tuples
@@ -579,14 +675,14 @@ cdef class MCP:
if self.flat_cumulative_costs[flat_position] == np.inf:
raise ValueError('no minimum-cost path was found '
'to the specified end point')
cdef cnp.ndarray[INDEX_T, ndim=1] position = \
np.array(ends[0], dtype=INDEX_D)
cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \
self.traceback_offsets
cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets
cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets
# Short names for arrays
cdef OFFSETS_INDEX_T [:] traceback_offsets = self.traceback_offsets
cdef OFFSET_T [:,:] offsets = self.offsets
cdef INDEX_T [:] flat_offsets = self.flat_offsets
# New array
cdef INDEX_T [:] position = np.array(ends[0], dtype=INDEX_D)
cdef OFFSETS_INDEX_T offset
cdef DIM_T d
cdef DIM_T dim = self.dim
@@ -602,6 +698,7 @@ cdef class MCP:
return _reverse(traceback)
@cython.boundscheck(False)
@cython.wraparound(False)
cdef class MCP_Geometric(MCP):
@@ -626,15 +723,17 @@ cdef class MCP_Geometric(MCP):
`(sqrt(2)/2)*costs[1,1] + (sqrt(2)/2)*costs[2,2]`.
These calculations don't make a lot of sense with offsets of magnitude
greater than 1.
greater than 1. Use the `sampling` argument in order to deal with
anisotropic data.
"""
def __init__(self, costs, offsets=None, fully_connected=True):
"""__init__(costs, offsets=None, fully_connected=True)
def __init__(self, costs, offsets=None, fully_connected=True,
sampling=None):
"""__init__(costs, offsets=None, fully_connected=True, sampling=None)
See class documentation.
"""
MCP.__init__(self, costs, offsets, fully_connected)
MCP.__init__(self, costs, offsets, fully_connected, sampling)
if np.absolute(self.offsets).max() > 1:
raise ValueError('all offset components must be 0, 1, or -1')
self.use_start_cost = 0
@@ -642,3 +741,185 @@ cdef class MCP_Geometric(MCP):
cdef FLOAT_T _travel_cost(self, FLOAT_T old_cost, FLOAT_T new_cost,
FLOAT_T offset_length):
return offset_length * 0.5 * (old_cost + new_cost)
@cython.boundscheck(True)
@cython.wraparound(True)
cdef class MCP_Connect(MCP):
"""MCP_Connect(costs, offsets=None, fully_connected=True)
Connect source points using the distance-weighted minimum cost function.
A front is grown from each seed point simultaneously, while the
origin of the front is tracked as well. When two fronts meet,
create_connection() is called. This method must be overloaded to
deal with the found edges in a way that is appropriate for the
application.
"""
cdef INDEX_T [:] flat_idmap
def __init__(self, costs, offsets=None, fully_connected=True,
sampling=None):
MCP.__init__(self, costs, offsets, fully_connected, sampling)
# Create id map to keep track of origin of nodes
self.flat_idmap = np.zeros(self.costs_shape, INDEX_D).ravel('F')
def _reset(self):
""" Reset the id map.
"""
MCP._reset(self)
starts, ends = self._starts, self._ends
# Reset idmap
self.flat_idmap[...] = -1
id = 0
for start in _ravel_index_fortran(starts, self.costs_shape):
self.flat_idmap[start] = id
id += 1
cdef FLOAT_T _travel_cost(self, FLOAT_T old_cost, FLOAT_T new_cost,
FLOAT_T offset_length):
""" Equivalent to MCP_Geometric.
"""
return offset_length * 0.5 * (old_cost + new_cost)
cdef void _examine_neighbor(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" Check whether two fronts are meeting. If so, the flat_traceback
is obtained and a connection is created.
"""
# Short names
cdef INDEX_T [:] flat_idmap = self.flat_idmap
cdef FLOAT_T [:] flat_cumulative_costs = self.flat_cumulative_costs
# Get ids
cdef INDEX_T id1 = flat_idmap[index]
cdef INDEX_T id2 = flat_idmap[new_index]
if id2 < 0 or id1 < 0:
pass
elif id2 != id1:
# We reached the 'front' of another seed point!
# Get position/coordinates
pos1, pos2 = _unravel_index_fortran([index, new_index],
self.costs_shape)
# Also get the costs, so we can keep the path with the least cost
cost1 = flat_cumulative_costs[index]
cost2 = flat_cumulative_costs[new_index]
# Create connection
self.create_connection(id1, id2, pos1, pos2, cost1, cost2)
def create_connection(self, id1, id2, tb1, tb2, cost1, cost2):
""" create_connection id1, id2, pos1, pos2, cost1, cost2)
Overload this method to keep track of the connections that are
found during MCP processing. Note that a connection with the
same ids can be found multiple times (but with different
positions and costs).
At the time that this method is called, both points are "frozen"
and will not be visited again by the MCP algorithm.
Parameters
----------
id1 : int
The seed point id where the first neighbor originated from.
id2 : int
The seed point id where the second neighbor originated from.
pos1 : tuple
The index of of the first neighbour in the connection.
pos2 : tuple
The index of of the second neighbour in the connection.
cost1 : float
The cumulative cost at `pos1`.
cost2 : float
The cumulative costs at `pos2`.
"""
pass
cdef void _update_node(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" Keep track of the id map so that we know which seed point
a certain front originates from.
"""
self.flat_idmap[new_index] = self.flat_idmap[index]
@cython.boundscheck(False)
@cython.wraparound(False)
cdef class MCP_Flexible(MCP):
"""MCP_Flexible(costs, offsets=None, fully_connected=True)
Find minimum cost paths through an N-d costs array.
See the documentation for MCP for full details. This class differs from
MCP in that several methods can be overloaded (from pure Python) to
modify the behavior of the algorithm and/or create custom algorithms
based on MCP. Note that goal_reached can also be overloaded in the
MCP class.
"""
def travel_cost(self, FLOAT_T old_cost, FLOAT_T new_cost,
FLOAT_T offset_length):
""" travel_cost(old_cost, new_cost, offset_length)
This method calculates the travel cost for going from the
current node to the next. The default implementation returns
new_cost. Overload this method to adapt the behaviour of the
algorithm.
"""
return new_cost
def examine_neighbor(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" examine_neighbor(index, new_index, offset_length)
This method is called once for every pair of neighboring nodes,
as soon as both nodes are frozen.
This method can be overloaded to obtain information about
neightboring nodes, and/or to modify the behavior of the MCP
algorithm. One example is the MCP_Connect class, which checks
for meeting fronts using this hook.
"""
pass
def update_node(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
""" update_node(index, new_index, offset_length)
This method is called when a node is updated, right after
new_index is pushed onto the heap and the traceback map is
updated.
This method can be overloaded to keep track of other arrays
that are used by a specific implementation of the algorithm.
For instance the MCP_Connect class uses it to update an id map.
"""
pass
cdef FLOAT_T _travel_cost(self, FLOAT_T old_cost, FLOAT_T new_cost,
FLOAT_T offset_length):
return self.travel_cost(old_cost, new_cost, offset_length)
cdef void _examine_neighbor(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
self.examine_neighbor(index, new_index, offset_length)
cdef void _update_node(self, INDEX_T index, INDEX_T new_index,
FLOAT_T offset_length):
self.update_node(index, new_index, offset_length)
+1 -1
View File
@@ -1,4 +1,4 @@
from ._mcp import MCP, MCP_Geometric
from ._mcp import MCP, MCP_Geometric, MCP_Connect, MCP_Flexible
def route_through_array(array, start, end, fully_connected=True,
+53
View File
@@ -0,0 +1,53 @@
import skimage.graph.mcp as mcp
from numpy.testing import (assert_array_equal,
assert_almost_equal,
)
import numpy as np
a = np.ones((8, 8), dtype=np.float32)
horizontal_ramp = np.array([[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,],
[ 0., 1., 2., 3., 4., 5., 6., 7.,]])
vertical_ramp = np.array( [[ 0., 0., 0., 0., 0., 0., 0., 0.,],
[ 1., 1., 1., 1., 1., 1., 1., 1.,],
[ 2., 2., 2., 2., 2., 2., 2., 2.,],
[ 3., 3., 3., 3., 3., 3., 3., 3.,],
[ 4., 4., 4., 4., 4., 4., 4., 4.,],
[ 5., 5., 5., 5., 5., 5., 5., 5.,],
[ 6., 6., 6., 6., 6., 6., 6., 6.,],
[ 7., 7., 7., 7., 7., 7., 7., 7.,]])
def test_anisotropy():
# Create seeds; vertical seeds create a horizonral ramp
seeds_for_horizontal = [(i, 0) for i in range(8) ]
seeds_for_vertcal = [(0, i) for i in range(8) ]
for sy in range(1, 5):
for sx in range(1,5):
sampling = sy, sx
# Trace horizontally
m1 = mcp.MCP_Geometric(a, sampling=sampling, fully_connected=True)
costs1, traceback = m1.find_costs(seeds_for_horizontal)
# Trace vertically
m2 = mcp.MCP_Geometric(a, sampling=sampling, fully_connected=True)
costs2, traceback = m2.find_costs(seeds_for_vertcal)
# Check
assert_array_equal(costs1, horizontal_ramp * sx)
assert_array_equal(costs2, vertical_ramp * sy)
if __name__ == "__main__":
np.testing.run_module_suite()
+83
View File
@@ -0,0 +1,83 @@
import skimage.graph.mcp as mcp
# import stentseg.graph._mcp as mcp
from numpy.testing import (assert_array_equal,
assert_almost_equal,
)
import numpy as np
a = np.ones((8, 8), dtype=np.float32)
count = 0
class MCP(mcp.MCP_Connect):
def _reset(self):
""" Reset the id map.
"""
mcp.MCP_Connect._reset(self)
self._conn = {}
self._bestconn = {}
def create_connection(self, id1, id2, pos1, pos2, cost1, cost2):
# Process data
hash = min(id1, id2), max(id1, id2)
val = min(pos1, pos2), max(pos1, pos2)
cost = min(cost1, cost2)
# Add to total list
self._conn.setdefault(hash, []).append(val)
# Keep track of connection with lowest cost
curcost = self._bestconn.get(hash, (np.inf,))[0]
if cost < curcost:
self._bestconn[hash] = (cost,) + val
def test_connections():
# Create MCP object with three seed points
mcp = MCP(a)
costs, traceback = mcp.find_costs([ (1,1), (7,7), (1,7) ])
# Test that all three seed points are connected
connections = set(mcp._conn.keys())
assert (0, 1) in connections
assert (1, 2) in connections
assert (0, 2) in connections
# Test that any two neighbors have only been connected once
for position_tuples in mcp._conn.values():
n1 = len(position_tuples)
n2 = len(set(position_tuples))
assert n1 == n2
# For seed 0 and 1
cost, pos1, pos2 = mcp._bestconn[(0,1)]
# Test meeting points
assert (pos1, pos2) == ( (3,3), (4,4) )
# Test the whole path
path = mcp.traceback(pos1) + list(reversed(mcp.traceback(pos2)))
assert_array_equal(path,
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)])
# For seed 1 and 2
cost, pos1, pos2 = mcp._bestconn[(1,2)]
# Test meeting points
assert (pos1, pos2) == ( (3,7), (4,7) )
# Test the whole path
path = mcp.traceback(pos1) + list(reversed(mcp.traceback(pos2)))
assert_array_equal(path,
[(1, 7), (2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (7, 7)])
# For seed 0 and 2
cost, pos1, pos2 = mcp._bestconn[(0,2)]
# Test meeting points
assert (pos1, pos2) == ( (1,3), (1,4) )
# Test the whole path
path = mcp.traceback(pos1) + list(reversed(mcp.traceback(pos2)))
assert_array_equal(path,
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7)])
if __name__ == "__main__":
np.testing.run_module_suite()
+61
View File
@@ -0,0 +1,61 @@
import skimage.graph.mcp as mcp
from numpy.testing import (assert_array_equal,
assert_almost_equal,
)
import numpy as np
a = np.ones((8, 8), dtype=np.float32)
a[1::2] *= 2.0
class FlexibleMCP(mcp.MCP_Flexible):
""" Simple MCP subclass that allows the front to travel
a certain distance from the seed point, and uses a constant
cost factor that is independant of the cost array.
"""
def _reset(self):
mcp.MCP_Flexible._reset(self)
self._distance = np.zeros((8, 8), dtype=np.float32).ravel()
def goal_reached(self, index, cumcost):
if self._distance[index] > 4:
return 2
else:
return 0
def travel_cost(self, index, new_index, offset_length):
return 1.0 # fixed cost
def examine_neighbor(self, index, new_index, offset_length):
pass # We do not test this
def update_node(self, index, new_index, offset_length):
self._distance[new_index] = self._distance[index] + 1
def test_flexible():
# Create MCP and do a traceback
mcp = FlexibleMCP(a)
costs, traceback = mcp.find_costs([(0, 0)])
# Check that inner part is correct. This basically
# tests whether travel_cost works.
assert_array_equal(costs[:4,:4], [[1, 2, 3, 4],
[2, 2, 3, 4],
[3, 3, 3, 4],
[4, 4, 4, 4]])
# Test that the algorithm stopped at the right distance.
# Note that some of the costs are filled in but not yet frozen,
# so we take a bit of margin
assert np.all( costs[-2:,:] == np.inf )
assert np.all( costs[:,-2:] == np.inf )
#print(costs)
if __name__ == "__main__":
np.testing.run_module_suite()
+3 -2
View File
@@ -116,8 +116,8 @@ def test_offsets():
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],
[[-2, -2, -2, -2, -2, -2, -2, -2],
[-2, -2, -2, -2, -2, -2, -1, -2],
[15, 14, 13, 12, 11, 10, 0, 1],
[10, 0, 1, 2, 3, 4, 5, 6],
[10, 0, 1, 2, 3, 4, 5, 6],
@@ -151,5 +151,6 @@ def _test_random(shape):
return a, costs, offsets
if __name__ == "__main__":
np.testing.run_module_suite()
+28 -46
View File
@@ -1,49 +1,34 @@
__doc__ = """Utilities to read and write images in various formats.
"""Utilities to read and write images in various formats.
The following plug-ins are available:
"""
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
from ._plugins import reset_plugins as _reset_plugins
from .manage_plugins import *
from .sift import *
from .collection import *
from ._io import *
from ._image_stack import *
from .video import *
available_plugins = plugins()
reset_plugins()
WRAP_LEN = 73
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, OSError):
pass
def _separator(char, lengths):
return [char * separator_length for separator_length in lengths]
# 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 _format_plugin_info_table(info_table, column_lengths):
"""Add separators and column titles to plugin info table."""
info_table.insert(0, _separator('=', column_lengths))
info_table.insert(1, ('Plugin', 'Description'))
info_table.insert(2, _separator('-', column_lengths))
info_table.append(_separator('-', column_lengths))
def _update_doc(doc):
"""Add a list of plugins to the module docstring, formatted as
@@ -52,27 +37,24 @@ def _update_doc(doc):
"""
from textwrap import wrap
info = [(p, plugin_info(p)) for p in plugins() if not p == 'test']
col_1_len = max([len(n) for (n, _) in info])
wrap_len = 73
col_2_len = wrap_len - 1 - col_1_len
info_table = [(p, plugin_info(p).get('description', 'no description'))
for p in available_plugins if not p == 'test']
# Insert table header
info.insert(0, ('=' * col_1_len, {'description': '=' * col_2_len}))
info.insert(1, ('Plugin', {'description': 'Description'}))
info.insert(2, ('-' * col_1_len, {'description': '-' * col_2_len}))
info.append(('=' * col_1_len, {'description': '=' * col_2_len}))
name_length = max([len(n) for (n, _) in info_table])
description_length = WRAP_LEN - 1 - name_length
column_lengths = [name_length, description_length]
_format_plugin_info_table(info_table, column_lengths)
for (name, meta_data) in info:
wrapped_descr = wrap(meta_data.get('description', ''),
col_2_len)
doc += "%s %s\n" % (name.ljust(col_1_len),
'\n'.join(wrapped_descr))
for (name, plugin_description) in info_table:
description_lines = wrap(plugin_description, description_length)
name_column = [name]
name_column.extend(['' for _ in range(len(description_lines) - 1)])
for name, description in zip(name_column, description_lines):
doc += "%s %s\n" % (name.ljust(name_length), description)
doc = doc.strip()
return doc
__doc__ = _update_doc(__doc__)
reset_plugins()
__doc__ = _update_doc(__doc__)
+35
View File
@@ -0,0 +1,35 @@
import numpy as np
__all__ = ['image_stack', 'push', 'pop']
# Shared image queue
image_stack = []
def push(img):
"""Push an image onto the shared image stack.
Parameters
----------
img : ndarray
Image to push.
"""
if not isinstance(img, np.ndarray):
raise ValueError("Can only push ndarrays to the image stack.")
image_stack.append(img)
def pop():
"""Pop an image from the shared image stack.
Returns
-------
img : ndarray
Image popped from the stack.
"""
return image_stack.pop()
+4 -58
View File
@@ -1,34 +1,14 @@
__all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show',
'push', 'pop']
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
import os
import re
import tempfile
from io import BytesIO
import numpy as np
import six
from skimage.io._plugins import call as call_plugin
from skimage.io.manage_plugins import call_plugin
from skimage.color import rgb2grey
from .util import file_or_url_context
# Shared image queue
_image_stack = []
URL_REGEX = re.compile(r'http://|https://|ftp://|file://|file:\\')
def is_url(filename):
"""Return True if string is an http or ftp path."""
return (isinstance(filename, six.string_types) and
URL_REGEX.match(filename) is not None)
__all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show']
class Image(np.ndarray):
@@ -77,33 +57,6 @@ class Image(np.ndarray):
return return_str
def push(img):
"""Push an image onto the shared image stack.
Parameters
----------
img : ndarray
Image to push.
"""
if not isinstance(img, np.ndarray):
raise ValueError("Can only push ndarrays to the image stack.")
_image_stack.append(img)
def pop():
"""Pop an image from the shared image stack.
Returns
-------
img : ndarray
Image popped from the stack.
"""
return _image_stack.pop()
def imread(fname, as_grey=False, plugin=None, flatten=None,
**plugin_args):
"""Load an image from file.
@@ -140,14 +93,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None,
if flatten is not None:
as_grey = flatten
if is_url(fname):
_, ext = os.path.splitext(fname)
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f:
u = urlopen(fname)
f.write(u.read())
img = call_plugin('imread', f.name, plugin=plugin, **plugin_args)
os.remove(f.name)
else:
with file_or_url_context(fname) as fname:
img = call_plugin('imread', fname, plugin=plugin, **plugin_args)
if as_grey and getattr(img, 'ndim', 0) >= 3:
-1
View File
@@ -1 +0,0 @@
from .plugin import *
+1 -1
View File
@@ -1,3 +1,3 @@
[null]
description = Default plugin that does nothing
provides = imshow, imread, _app_show
provides = imshow, imread, imsave, _app_show
+6 -1
View File
@@ -1,4 +1,4 @@
__all__ = ['imshow', 'imread', '_app_show']
__all__ = ['imshow', 'imread', 'imsave', '_app_show']
import warnings
@@ -17,4 +17,9 @@ def imshow(*args, **kwargs):
def imread(*args, **kwargs):
warnings.warn(RuntimeWarning(message))
def imsave(*args, **kwargs):
warnings.warn(RuntimeWarning(message))
_app_show = imshow

Some files were not shown because too many files have changed in this diff Show More