mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-07 10:24:25 +08:00
Merged with master
This commit is contained in:
@@ -7,3 +7,7 @@ include = */skimage/*
|
||||
omit =
|
||||
*/setup.py
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
def __repr__
|
||||
if __name__ == .__main__.:
|
||||
|
||||
+10
-3
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
"""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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([])
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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:')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()])
|
||||
|
||||
|
||||
@@ -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, {})
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,10 @@ from .colorconv import (convert_colorspace,
|
||||
lab2xyz,
|
||||
lab2rgb,
|
||||
rgb2lab,
|
||||
xyz2luv,
|
||||
luv2xyz,
|
||||
luv2rgb,
|
||||
rgb2luv,
|
||||
rgb2hed,
|
||||
hed2rgb,
|
||||
lab2lch,
|
||||
|
||||
+246
-63
@@ -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
|
||||
-------
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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__":
|
||||
|
||||
@@ -4,6 +4,7 @@ from .exposure import histogram, equalize, equalize_hist, \
|
||||
|
||||
from ._adapthist import equalize_adapthist
|
||||
|
||||
|
||||
__all__ = ['histogram',
|
||||
'equalize',
|
||||
'equalize_hist',
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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,
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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__)
|
||||
|
||||
@@ -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
@@ -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 +0,0 @@
|
||||
from .plugin import *
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[null]
|
||||
description = Default plugin that does nothing
|
||||
provides = imshow, imread, _app_show
|
||||
provides = imshow, imread, imsave, _app_show
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user