mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-03 19:48:48 +08:00
Merge pull request #205 from amueller/pep8
COSMIT: PEP8 compatibility and other cleanups.
This commit is contained in:
+5
-2
@@ -61,8 +61,8 @@ try:
|
||||
except ImportError:
|
||||
__version__ = "unbuilt-dev"
|
||||
|
||||
|
||||
def _setup_test(verbose=False):
|
||||
import gzip
|
||||
import functools
|
||||
|
||||
args = ['', '--exe', '-w', pkg_dir]
|
||||
@@ -93,6 +93,7 @@ if test_verbose is None:
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
def get_log(name=None):
|
||||
"""Return a console logger.
|
||||
|
||||
@@ -120,11 +121,13 @@ def get_log(name=None):
|
||||
log = logging.getLogger(name)
|
||||
return log
|
||||
|
||||
|
||||
def _setup_log():
|
||||
"""Configure root logger.
|
||||
|
||||
"""
|
||||
import logging, sys
|
||||
import logging
|
||||
import sys
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
+5
-4
@@ -1,9 +1,8 @@
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import hashlib
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
|
||||
def cython(pyx_files, working_path=''):
|
||||
"""Use Cython to convert the given files to C.
|
||||
@@ -39,17 +38,18 @@ def cython(pyx_files, working_path=''):
|
||||
print(cmd)
|
||||
|
||||
try:
|
||||
status = subprocess.call(['cython', '-o', c_file, pyxfile])
|
||||
subprocess.call(['cython', '-o', c_file, pyxfile])
|
||||
except WindowsError:
|
||||
# On Windows cython.exe may be missing if Cython was installed
|
||||
# via distutils. Run the cython.py script instead.
|
||||
status = subprocess.call(
|
||||
subprocess.call(
|
||||
[sys.executable,
|
||||
os.path.join(os.path.dirname(sys.executable),
|
||||
'Scripts', 'cython.py'),
|
||||
'-o', c_file, pyxfile],
|
||||
shell=True)
|
||||
|
||||
|
||||
def _md5sum(f):
|
||||
m = hashlib.new('md5')
|
||||
while True:
|
||||
@@ -60,6 +60,7 @@ def _md5sum(f):
|
||||
m.update(d)
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def _changed(filename):
|
||||
"""Compare the hash of a Cython file to the cached hash value on disk.
|
||||
|
||||
|
||||
@@ -485,7 +485,7 @@ def rgb2grey(rgb):
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `rgb2grey` is not a 3-D array of shape (.., .., 3) or
|
||||
If `rgb2grey` is not a 3-D array of shape (.., .., 3) or
|
||||
(.., .., 4).
|
||||
|
||||
References
|
||||
|
||||
@@ -57,8 +57,7 @@ class TestColorconv(TestCase):
|
||||
self.assertRaises(ValueError, rgb2hsv, self.img_grayscale)
|
||||
|
||||
def test_rgb2hsv_error_one_element(self):
|
||||
self.assertRaises(ValueError, rgb2hsv, self.img_rgb[0,0])
|
||||
|
||||
self.assertRaises(ValueError, rgb2hsv, self.img_rgb[0, 0])
|
||||
|
||||
# HSV to RGB
|
||||
def test_hsv2rgb_conversion(self):
|
||||
@@ -74,19 +73,18 @@ class TestColorconv(TestCase):
|
||||
self.assertRaises(ValueError, hsv2rgb, self.img_grayscale)
|
||||
|
||||
def test_hsv2rgb_error_one_element(self):
|
||||
self.assertRaises(ValueError, hsv2rgb, self.img_rgb[0,0])
|
||||
|
||||
self.assertRaises(ValueError, hsv2rgb, self.img_rgb[0, 0])
|
||||
|
||||
# RGB to XYZ
|
||||
def test_rgb2xyz_conversion(self):
|
||||
gt = np.array([[[ 0.950456, 1. , 1.088754],
|
||||
[ 0.538003, 0.787329, 1.06942 ],
|
||||
[ 0.592876, 0.28484 , 0.969561],
|
||||
[ 0.180423, 0.072169, 0.950227]],
|
||||
[[ 0.770033, 0.927831, 0.138527],
|
||||
[ 0.35758 , 0.71516 , 0.119193],
|
||||
[ 0.412453, 0.212671, 0.019334],
|
||||
[ 0. , 0. , 0. ]]])
|
||||
gt = np.array([[[0.950456, 1. , 1.088754],
|
||||
[0.538003, 0.787329, 1.06942 ],
|
||||
[0.592876, 0.28484 , 0.969561],
|
||||
[0.180423, 0.072169, 0.950227]],
|
||||
[[0.770033, 0.927831, 0.138527],
|
||||
[0.35758 , 0.71516 , 0.119193],
|
||||
[0.412453, 0.212671, 0.019334],
|
||||
[0. , 0. , 0. ]]])
|
||||
assert_almost_equal(rgb2xyz(self.colbars_array), gt)
|
||||
|
||||
# stop repeating the "raises" checks for all other functions that are
|
||||
@@ -95,8 +93,7 @@ class TestColorconv(TestCase):
|
||||
self.assertRaises(ValueError, rgb2xyz, self.img_grayscale)
|
||||
|
||||
def test_rgb2xyz_error_one_element(self):
|
||||
self.assertRaises(ValueError, rgb2xyz, self.img_rgb[0,0])
|
||||
|
||||
self.assertRaises(ValueError, rgb2xyz, self.img_rgb[0, 0])
|
||||
|
||||
# XYZ to RGB
|
||||
def test_xyz2rgb_conversion(self):
|
||||
@@ -104,7 +101,6 @@ class TestColorconv(TestCase):
|
||||
assert_almost_equal(xyz2rgb(rgb2xyz(self.colbars_array)),
|
||||
self.colbars_array)
|
||||
|
||||
|
||||
# RGB to RGB CIE
|
||||
def test_rgb2rgbcie_conversion(self):
|
||||
gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574],
|
||||
@@ -117,7 +113,6 @@ class TestColorconv(TestCase):
|
||||
[ 0. , 0. , 0. ]]])
|
||||
assert_almost_equal(rgb2rgbcie(self.colbars_array), gt)
|
||||
|
||||
|
||||
# RGB CIE to RGB
|
||||
def test_rgbcie2rgb_conversion(self):
|
||||
# only roundtrip test, we checked rgb2rgbcie above already
|
||||
@@ -151,6 +146,7 @@ class TestColorconv(TestCase):
|
||||
|
||||
assert_equal(g.shape, (1, 1))
|
||||
|
||||
|
||||
def test_gray2rgb():
|
||||
x = np.array([0, 0.5, 1])
|
||||
assert_raises(ValueError, gray2rgb, x)
|
||||
@@ -170,4 +166,3 @@ def test_gray2rgb():
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import os as _os
|
||||
from ..io import imread
|
||||
from skimage import data_dir
|
||||
|
||||
|
||||
def load(f):
|
||||
"""Load an image file located in the data directory.
|
||||
|
||||
@@ -26,6 +27,7 @@ def load(f):
|
||||
"""
|
||||
return imread(_os.path.join(data_dir, f))
|
||||
|
||||
|
||||
def camera():
|
||||
"""Gray-level "camera" image, often used for segmentation
|
||||
and denoising examples.
|
||||
@@ -33,6 +35,7 @@ def camera():
|
||||
"""
|
||||
return load("camera.png")
|
||||
|
||||
|
||||
def lena():
|
||||
"""Colour "Lena" image.
|
||||
|
||||
@@ -44,8 +47,9 @@ def lena():
|
||||
"""
|
||||
return load("lena.png")
|
||||
|
||||
|
||||
def text():
|
||||
""" Gray-level "text" image used for corner detection.
|
||||
""" Gray-level "text" image used for corner detection.
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -56,7 +60,8 @@ def text():
|
||||
|
||||
"""
|
||||
|
||||
return load("text.png")
|
||||
return load("text.png")
|
||||
|
||||
|
||||
def checkerboard():
|
||||
"""Checkerboard image.
|
||||
@@ -68,6 +73,7 @@ def checkerboard():
|
||||
"""
|
||||
return load("chessboard_GRAY.png")
|
||||
|
||||
|
||||
def coins():
|
||||
"""Greek coins from Pompeii.
|
||||
|
||||
@@ -88,6 +94,7 @@ def coins():
|
||||
"""
|
||||
return load("coins.png")
|
||||
|
||||
|
||||
def moon():
|
||||
"""Surface of the moon.
|
||||
|
||||
@@ -97,6 +104,7 @@ def moon():
|
||||
"""
|
||||
return load("moon.png")
|
||||
|
||||
|
||||
def page():
|
||||
"""Scanned page.
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import skimage.data as data
|
||||
from numpy.testing import assert_equal, assert_array_equal
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
|
||||
def test_lena():
|
||||
""" Test that "Lena" image can be loaded. """
|
||||
lena = data.lena()
|
||||
assert_equal(lena.shape, (512, 512, 3))
|
||||
|
||||
|
||||
def test_camera():
|
||||
""" Test that "camera" image can be loaded. """
|
||||
cameraman = data.camera()
|
||||
assert_equal(cameraman.ndim, 2)
|
||||
|
||||
|
||||
def test_checkerboard():
|
||||
""" Test that checkerboard image can be loaded. """
|
||||
checkerboard = data.checkerboard()
|
||||
data.checkerboard()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from numpy.testing import run_module_suite
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
|
||||
@@ -20,11 +21,11 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image developers',
|
||||
author = 'scikits-image developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Drawing',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'SciPy License (BSD Style)',
|
||||
setup(maintainer='scikits-image developers',
|
||||
author='scikits-image developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Drawing',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ def test_line_horizontal():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_line_vertical():
|
||||
img = np.zeros((10, 10))
|
||||
|
||||
@@ -26,6 +27,7 @@ def test_line_vertical():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_line_reverse():
|
||||
img = np.zeros((10, 10))
|
||||
|
||||
@@ -37,6 +39,7 @@ def test_line_reverse():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_line_diag():
|
||||
img = np.zeros((5, 5))
|
||||
|
||||
@@ -52,20 +55,21 @@ def test_polygon_rectangle():
|
||||
img = np.zeros((10, 10), 'uint8')
|
||||
poly = np.array(((1, 1), (4, 1), (4, 4), (1, 4), (1, 1)))
|
||||
|
||||
rr, cc = polygon(poly[:,0], poly[:,1])
|
||||
img[rr,cc] = 1
|
||||
rr, cc = polygon(poly[:, 0], poly[:, 1])
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.zeros((10, 10))
|
||||
img_[1:4,1:4] = 1
|
||||
img_[1:4, 1:4] = 1
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_polygon_rectangle_angular():
|
||||
img = np.zeros((10, 10), 'uint8')
|
||||
poly = np.array(((0, 3), (4, 7), (7, 4), (3, 0), (0, 3)))
|
||||
|
||||
rr, cc = polygon(poly[:,0], poly[:,1])
|
||||
img[rr,cc] = 1
|
||||
rr, cc = polygon(poly[:, 0], poly[:, 1])
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -82,12 +86,13 @@ def test_polygon_rectangle_angular():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_polygon_parallelogram():
|
||||
img = np.zeros((10, 10), 'uint8')
|
||||
poly = np.array(((1, 1), (5, 1), (7, 6), (3, 6), (1, 1)))
|
||||
|
||||
rr, cc = polygon(poly[:,0], poly[:,1])
|
||||
img[rr,cc] = 1
|
||||
rr, cc = polygon(poly[:, 0], poly[:, 1])
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -104,23 +109,25 @@ def test_polygon_parallelogram():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_polygon_exceed():
|
||||
img = np.zeros((10, 10), 'uint8')
|
||||
poly = np.array(((1, -1), (100, -1), (100, 100), (1, 100), (1, 1)))
|
||||
|
||||
rr, cc = polygon(poly[:,0], poly[:,1], img.shape)
|
||||
img[rr,cc] = 1
|
||||
rr, cc = polygon(poly[:, 0], poly[:, 1], img.shape)
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.zeros((10, 10))
|
||||
img_[1:,:] = 1
|
||||
img_[1:, :] = 1
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_circle():
|
||||
img = np.zeros((15, 15), 'uint8')
|
||||
|
||||
rr, cc = circle(7, 7, 6)
|
||||
img[rr,cc] = 1
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -142,11 +149,12 @@ def test_circle():
|
||||
|
||||
assert_array_equal(img, img_)
|
||||
|
||||
|
||||
def test_ellipse():
|
||||
img = np.zeros((15, 15), 'uint8')
|
||||
|
||||
rr, cc = ellipse(7, 7, 3, 7)
|
||||
img[rr,cc] = 1
|
||||
img[rr, cc] = 1
|
||||
|
||||
img_ = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
|
||||
@@ -183,4 +183,3 @@ def rescale_intensity(image, in_range=None, out_range=None):
|
||||
|
||||
image = (image - imin) / float(imax - imin)
|
||||
return dtype(image * (omax - omin) + omin)
|
||||
|
||||
|
||||
@@ -74,4 +74,3 @@ def test_rescale_out_range():
|
||||
if __name__ == '__main__':
|
||||
from numpy import testing
|
||||
testing.run_module_suite()
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ properties to characterize image textures.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import skimage.util
|
||||
|
||||
from ._greycomatrix import _glcm_loop
|
||||
|
||||
@@ -28,17 +27,17 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
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.
|
||||
(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.
|
||||
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
|
||||
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
|
||||
offset. The elements of the resulting matrix sum to 1. The
|
||||
default is False.
|
||||
|
||||
Returns
|
||||
@@ -54,10 +53,10 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
----------
|
||||
.. [1] The GLCM Tutorial Home Page,
|
||||
http://www.fp.ucalgary.ca/mhallbey/tutorial.htm
|
||||
.. [2] Pattern Recognition Engineering, Morton Nadler & Eric P.
|
||||
.. [2] Pattern Recognition Engineering, Morton Nadler & Eric P.
|
||||
Smith
|
||||
.. [3] Wikipedia, http://en.wikipedia.org/wiki/Co-occurrence_matrix
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
@@ -74,7 +73,7 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
[0, 2, 0, 0],
|
||||
[0, 0, 3, 1],
|
||||
[0, 0, 0, 1]], dtype=uint32)
|
||||
>>> result[:, :, 0, 1]
|
||||
>>> result[:, :, 0, 1]
|
||||
array([[3, 0, 2, 0],
|
||||
[0, 2, 2, 0],
|
||||
[0, 0, 1, 2],
|
||||
@@ -82,7 +81,7 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
|
||||
"""
|
||||
|
||||
assert levels <= 256
|
||||
assert levels <= 256
|
||||
image = np.ascontiguousarray(image)
|
||||
assert image.ndim == 2
|
||||
assert image.min() >= 0
|
||||
@@ -95,7 +94,7 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
|
||||
P = np.zeros((levels, levels, len(distances), len(angles)),
|
||||
dtype=np.uint32, order='C')
|
||||
|
||||
|
||||
# count co-occurences
|
||||
_glcm_loop(image, distances, angles, levels, P)
|
||||
|
||||
@@ -103,8 +102,7 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
if symmetric:
|
||||
Pt = np.transpose(P, (1, 0, 2, 3))
|
||||
P = P + Pt
|
||||
|
||||
|
||||
|
||||
# normalize each GLMC
|
||||
if normed:
|
||||
P = P.astype(np.float64)
|
||||
@@ -117,25 +115,25 @@ def greycomatrix(image, distances, angles, levels=256, symmetric=False,
|
||||
|
||||
def greycoprops(P, prop='contrast'):
|
||||
"""Calculate texture properties of a GLCM.
|
||||
|
||||
Compute a feature of a grey level co-occurrence matrix to serve as
|
||||
|
||||
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`
|
||||
- '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) \\
|
||||
.. 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
|
||||
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
|
||||
@@ -144,80 +142,80 @@ def greycoprops(P, prop='contrast'):
|
||||
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
|
||||
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
|
||||
|
||||
http://www.fp.ucalgary.ca/mhallbey/tutorial.htm
|
||||
|
||||
Examples
|
||||
--------
|
||||
Compute the contrast for GLCMs with distances [1, 2] and angles
|
||||
[0 degrees, 90 degrees]
|
||||
|
||||
[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,
|
||||
>>> 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
|
||||
weights = (I - J)**2
|
||||
elif prop == 'dissimilarity':
|
||||
weights = np.abs(I - J)
|
||||
elif prop == 'homogeneity':
|
||||
weights = 1. / (1. + (I - J) ** 2)
|
||||
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
|
||||
# compute property for each GLCM
|
||||
if prop == 'energy':
|
||||
asm = np.apply_over_axes(np.sum, (P ** 2), axes=(0, 1))[0, 0]
|
||||
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]
|
||||
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),
|
||||
|
||||
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),
|
||||
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)),
|
||||
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])
|
||||
|
||||
@@ -42,7 +42,7 @@ def _compute_harris_response(image, eps=1e-6, gaussian_deviation=1):
|
||||
Wyy = ndimage.gaussian_filter(imy * imy, 1.5, mode='constant')
|
||||
|
||||
# determinant and trace
|
||||
Wdet = Wxx * Wyy - Wxy ** 2
|
||||
Wdet = Wxx * Wyy - Wxy**2
|
||||
Wtr = Wxx + Wyy
|
||||
# Alternate formula for Harris response.
|
||||
# Alison Noble, "Descriptions of Image Surfaces", PhD thesis (1989)
|
||||
@@ -76,7 +76,7 @@ def harris(image, min_distance=10, threshold=0.1, eps=1e-6,
|
||||
-------
|
||||
coordinates : (N, 2) array
|
||||
(row, column) coordinates of interest points.
|
||||
|
||||
|
||||
Examples
|
||||
-------
|
||||
>>> square = np.zeros([10,10])
|
||||
@@ -93,18 +93,17 @@ def harris(image, min_distance=10, threshold=0.1, eps=1e-6,
|
||||
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
|
||||
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
|
||||
>>> harris(square, min_distance=1)
|
||||
|
||||
|
||||
Corners of the square
|
||||
|
||||
|
||||
array([[3, 3],
|
||||
[3, 6],
|
||||
[6, 3],
|
||||
[6, 6]])
|
||||
[6, 6]])
|
||||
"""
|
||||
|
||||
|
||||
harrisim = _compute_harris_response(image, eps=eps,
|
||||
gaussian_deviation=gaussian_deviation)
|
||||
coordinates = peak.peak_local_max(harrisim, min_distance=min_distance,
|
||||
threshold=threshold)
|
||||
return coordinates
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import numpy as np
|
||||
from scipy import sqrt, pi, arctan2, cos, sin
|
||||
from scipy.ndimage import uniform_filter
|
||||
|
||||
|
||||
def hog(image, orientations=9, pixels_per_cell=(8, 8),
|
||||
cells_per_block=(3, 3), visualise=False, normalise=False):
|
||||
"""Extract Histogram of Oriented Gradients (HOG) for a given image.
|
||||
@@ -94,7 +95,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
|
||||
cell are used to vote into the orientation histogram.
|
||||
"""
|
||||
|
||||
magnitude = sqrt(gx ** 2 + gy ** 2)
|
||||
magnitude = sqrt(gx**2 + gy**2)
|
||||
orientation = arctan2(gy, (gx + 1e-15)) * (180 / pi) + 90
|
||||
|
||||
sy, sx = image.shape
|
||||
@@ -118,12 +119,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
|
||||
cond2 = temp_ori > 0
|
||||
temp_mag = np.where(cond2, magnitude, 0)
|
||||
|
||||
orientation_histogram[:,:,i] = uniform_filter(temp_mag, size=(cy, cx))[cy/2::cy, cx/2::cx]
|
||||
|
||||
orientation_histogram[:, :, i] = uniform_filter(temp_mag,
|
||||
size=(cy, cx))[cy / 2::cy, cx / 2::cx]
|
||||
|
||||
# now for each cell, compute the histogram
|
||||
#orientation_histogram = np.zeros((n_cellsx, n_cellsy, orientations))
|
||||
|
||||
radius = min(cx, cy) // 2 - 1
|
||||
hog_image = None
|
||||
if visualise:
|
||||
@@ -131,7 +131,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
|
||||
|
||||
if visualise:
|
||||
from skimage import draw
|
||||
|
||||
|
||||
for x in range(n_cellsx):
|
||||
for y in range(n_cellsy):
|
||||
for o in range(orientations):
|
||||
@@ -166,7 +166,7 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
|
||||
for y in range(n_blocksy):
|
||||
block = orientation_histogram[y:y + by, x:x + bx, :]
|
||||
eps = 1e-5
|
||||
normalised_blocks[y, x, :] = block / sqrt(block.sum() ** 2 + eps)
|
||||
normalised_blocks[y, x, :] = block / sqrt(block.sum()**2 + eps)
|
||||
|
||||
"""
|
||||
The final step collects the HOG descriptors from all blocks of a dense
|
||||
|
||||
@@ -38,15 +38,15 @@ def peak_local_max(image, min_distance=10, threshold='deprecated',
|
||||
-------
|
||||
coordinates : (N, 2) array
|
||||
(row, column) coordinates of peaks.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
The peak local maximum function returns the coordinates of local peaks (maxima)
|
||||
in a image. A maximum filter is used for finding local maxima. This operation
|
||||
dilates the original image. After comparison between dilated and original image,
|
||||
dilates the original image. After comparison between dilated and original image,
|
||||
peak_local_max function returns the coordinates of peaks where
|
||||
dilated image = original.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> im = np.zeros((7, 7))
|
||||
@@ -60,14 +60,14 @@ def peak_local_max(image, min_distance=10, threshold='deprecated',
|
||||
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
|
||||
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
|
||||
[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ]])
|
||||
|
||||
|
||||
>>> peak_local_max(im, min_distance=1)
|
||||
array([[3, 2],
|
||||
[3, 4]])
|
||||
|
||||
|
||||
>>> peak_local_max(im, min_distance=2)
|
||||
array([[3, 2]])
|
||||
|
||||
|
||||
"""
|
||||
if np.all(image == image.flat[0]):
|
||||
return []
|
||||
@@ -101,4 +101,3 @@ def peak_local_max(image, min_distance=10, threshold='deprecated',
|
||||
coordinates = coordinates[idx_maxsort][:2]
|
||||
|
||||
return coordinates
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
|
||||
@@ -23,11 +24,11 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
author = 'scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Features',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'SciPy License (BSD Style)',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
author='scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Features',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -77,8 +77,7 @@ def match_template(image, template, pad_input=False):
|
||||
i0, j0 = template.shape
|
||||
i0 /= 2
|
||||
j0 /= 2
|
||||
pad_image[i0:i0+h, j0:j0+w] = image
|
||||
pad_image[i0:i0 + h, j0:j0 + w] = image
|
||||
image = pad_image
|
||||
result = _template.match_template(image, template)
|
||||
return result
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ class TestGLCM():
|
||||
self.image = np.array([[0, 0, 1, 1],
|
||||
[0, 0, 1, 1],
|
||||
[0, 2, 2, 2],
|
||||
[2, 2, 3, 3]], dtype=np.uint8)
|
||||
|
||||
[2, 2, 3, 3]], dtype=np.uint8)
|
||||
|
||||
def test_output_angles(self):
|
||||
result = greycomatrix(self.image, [1], [0, np.pi / 2], 4)
|
||||
assert result.shape == (4, 4, 1, 2)
|
||||
@@ -21,23 +21,23 @@ class TestGLCM():
|
||||
[0, 2, 2, 0],
|
||||
[0, 0, 1, 2],
|
||||
[0, 0, 0, 0]], dtype=np.uint32)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 1], expected2)
|
||||
|
||||
def test_output_symmetric_1(self):
|
||||
result = greycomatrix(self.image, [1], [np.pi / 2], 4,
|
||||
np.testing.assert_array_equal(result[:, :, 0, 1], expected2)
|
||||
|
||||
def test_output_symmetric_1(self):
|
||||
result = greycomatrix(self.image, [1], [np.pi / 2], 4,
|
||||
symmetric=True)
|
||||
assert result.shape == (4, 4, 1, 1)
|
||||
expected = np.array([[6, 0, 2, 0],
|
||||
[0, 4, 2, 0],
|
||||
[2, 2, 2, 2],
|
||||
[0, 0, 2, 0]], dtype=np.uint32)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected)
|
||||
|
||||
def test_output_distance(self):
|
||||
im = np.array([[0, 0, 0, 0],
|
||||
[1, 0, 0, 1],
|
||||
[2, 0, 0, 2],
|
||||
[3, 0, 0, 3]], dtype=np.uint8)
|
||||
[3, 0, 0, 3]], dtype=np.uint8)
|
||||
result = greycomatrix(im, [3], [0], 4, symmetric=False)
|
||||
expected = np.array([[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
@@ -52,7 +52,7 @@ class TestGLCM():
|
||||
[3]], dtype=np.uint8)
|
||||
result = greycomatrix(im, [1, 2], [0, np.pi / 2], 4)
|
||||
assert result.shape == (4, 4, 2, 2)
|
||||
|
||||
|
||||
z = np.zeros((4, 4), dtype=np.uint32)
|
||||
e1 = np.array([[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
@@ -62,7 +62,7 @@ class TestGLCM():
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]], dtype=np.uint32)
|
||||
|
||||
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], z)
|
||||
np.testing.assert_array_equal(result[:, :, 1, 0], z)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 1], e1)
|
||||
@@ -70,39 +70,39 @@ class TestGLCM():
|
||||
|
||||
def test_output_empty(self):
|
||||
result = greycomatrix(self.image, [10], [0], 4)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0],
|
||||
np.zeros((4, 4), dtype=np.uint32))
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0],
|
||||
np.zeros((4, 4), dtype=np.uint32))
|
||||
result = greycomatrix(self.image, [10], [0], 4, normed=True)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0],
|
||||
np.zeros((4, 4), dtype=np.uint32))
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0],
|
||||
np.zeros((4, 4), dtype=np.uint32))
|
||||
|
||||
def test_normed_symmetric(self):
|
||||
result = greycomatrix(self.image, [1, 2, 3],
|
||||
[0, np.pi / 2, np.pi], 4,
|
||||
def test_normed_symmetric(self):
|
||||
result = greycomatrix(self.image, [1, 2, 3],
|
||||
[0, np.pi / 2, np.pi], 4,
|
||||
normed=True, symmetric=True)
|
||||
for d in range(result.shape[2]):
|
||||
for a in range(result.shape[3]):
|
||||
np.testing.assert_almost_equal(result[:, :, d, a].sum(),
|
||||
np.testing.assert_almost_equal(result[:, :, d, a].sum(),
|
||||
1.0)
|
||||
np.testing.assert_array_equal(result[:, :, d, a],
|
||||
np.testing.assert_array_equal(result[:, :, d, a],
|
||||
result[:, :, d, a].transpose())
|
||||
|
||||
def test_contrast(self):
|
||||
result = greycomatrix(self.image, [1, 2], [0], 4,
|
||||
result = greycomatrix(self.image, [1, 2], [0], 4,
|
||||
normed=True, symmetric=True)
|
||||
result = np.round(result, 3)
|
||||
contrast = greycoprops(result, 'contrast')
|
||||
np.testing.assert_almost_equal(contrast[0, 0], 0.586)
|
||||
|
||||
|
||||
def test_dissimilarity(self):
|
||||
result = greycomatrix(self.image, [1], [0, np.pi / 2], 4,
|
||||
result = greycomatrix(self.image, [1], [0, np.pi / 2], 4,
|
||||
normed=True, symmetric=True)
|
||||
result = np.round(result, 3)
|
||||
dissimilarity = greycoprops(result, 'dissimilarity')
|
||||
np.testing.assert_almost_equal(dissimilarity[0, 0], 0.418)
|
||||
|
||||
def test_dissimilarity_2(self):
|
||||
result = greycomatrix(self.image, [1, 3], [np.pi/2], 4,
|
||||
result = greycomatrix(self.image, [1, 3], [np.pi / 2], 4,
|
||||
normed=True, symmetric=True)
|
||||
result = np.round(result, 3)
|
||||
dissimilarity = greycoprops(result, 'dissimilarity')[0, 0]
|
||||
@@ -110,23 +110,23 @@ class TestGLCM():
|
||||
|
||||
def test_invalid_property(self):
|
||||
result = greycomatrix(self.image, [1], [0], 4)
|
||||
np.testing.assert_raises(ValueError, greycoprops,
|
||||
np.testing.assert_raises(ValueError, greycoprops,
|
||||
result, 'ABC')
|
||||
|
||||
|
||||
def test_homogeneity(self):
|
||||
result = greycomatrix(self.image, [1], [0, 6], 4, normed=True,
|
||||
result = greycomatrix(self.image, [1], [0, 6], 4, normed=True,
|
||||
symmetric=True)
|
||||
homogeneity = greycoprops(result, 'homogeneity')[0, 0]
|
||||
np.testing.assert_almost_equal(homogeneity, 0.80833333)
|
||||
|
||||
def test_energy(self):
|
||||
result = greycomatrix(self.image, [1], [0, 4], 4, normed=True,
|
||||
result = greycomatrix(self.image, [1], [0, 4], 4, normed=True,
|
||||
symmetric=True)
|
||||
energy = greycoprops(result, 'energy')[0, 0]
|
||||
np.testing.assert_almost_equal(energy, 0.38188131)
|
||||
|
||||
|
||||
def test_correlation(self):
|
||||
result = greycomatrix(self.image, [1, 2], [0], 4, normed=True,
|
||||
result = greycomatrix(self.image, [1, 2], [0], 4, normed=True,
|
||||
symmetric=True)
|
||||
energy = greycoprops(result, 'correlation')
|
||||
np.testing.assert_almost_equal(energy[0, 0], 0.71953255)
|
||||
@@ -134,9 +134,9 @@ class TestGLCM():
|
||||
|
||||
def test_uniform_properties(self):
|
||||
im = np.ones((4, 4), dtype=np.uint8)
|
||||
result = greycomatrix(im, [1, 2, 8], [0, np.pi / 2], 4, normed=True,
|
||||
result = greycomatrix(im, [1, 2, 8], [0, np.pi / 2], 4, normed=True,
|
||||
symmetric=True)
|
||||
for prop in ['contrast', 'dissimilarity', 'homogeneity',
|
||||
for prop in ['contrast', 'dissimilarity', 'homogeneity',
|
||||
'energy', 'correlation', 'ASM']:
|
||||
greycoprops(result, prop)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ def test_square_image():
|
||||
assert results.any()
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_noisy_square_image():
|
||||
im = np.zeros((50, 50)).astype(float)
|
||||
im[:25, :25] = 1.
|
||||
@@ -21,6 +22,7 @@ def test_noisy_square_image():
|
||||
assert results.any()
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_squared_dot():
|
||||
im = np.zeros((50, 50))
|
||||
im[4:8, 4:8] = 1
|
||||
@@ -28,6 +30,7 @@ def test_squared_dot():
|
||||
results = harris(im, min_distance=3)
|
||||
assert (results == np.array([[6, 6]])).all()
|
||||
|
||||
|
||||
def test_rotated_lena():
|
||||
"""
|
||||
The harris filter should yield the same results with an image and it's
|
||||
@@ -44,4 +47,3 @@ def test_rotated_lena():
|
||||
if __name__ == '__main__':
|
||||
from numpy import testing
|
||||
testing.run_module_suite()
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import numpy as np
|
||||
import scipy
|
||||
|
||||
from skimage.feature import hog
|
||||
from skimage.feature import hog
|
||||
|
||||
|
||||
def test_histogram_of_oriented_gradients():
|
||||
# Replace with skimage.data.lena() after merge
|
||||
img = scipy.misc.lena()[:256,:].astype(np.int8)
|
||||
|
||||
fd = hog(img, orientations=9, pixels_per_cell=(8, 8),
|
||||
img = scipy.misc.lena()[:256, :].astype(np.int8)
|
||||
|
||||
fd = hog(img, orientations=9, pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1))
|
||||
|
||||
assert len(fd) == 9 * (256//8) * (512//8)
|
||||
|
||||
assert len(fd) == 9 * (256 // 8) * (512 // 8)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.testing import run_module_suite
|
||||
run_module_suite()
|
||||
|
||||
@@ -65,4 +65,3 @@ def test_num_peaks():
|
||||
if __name__ == '__main__':
|
||||
from numpy import testing
|
||||
testing.run_module_suite()
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ def test_normalization():
|
||||
image[ineg:ineg + n, jneg:jneg + n] = 0
|
||||
|
||||
# white square with a black border
|
||||
template = np.zeros((n+2, n+2))
|
||||
template[1:1+n, 1:1+n] = 1
|
||||
template = np.zeros((n + 2, n + 2))
|
||||
template[1:1 + n, 1:1 + n] = 1
|
||||
|
||||
result = match_template(image, template)
|
||||
|
||||
@@ -121,4 +121,3 @@ def test_pad_input():
|
||||
if __name__ == "__main__":
|
||||
from numpy import testing
|
||||
testing.run_module_suite()
|
||||
|
||||
|
||||
+13
-13
@@ -56,7 +56,7 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
|
||||
Parameters
|
||||
-----------
|
||||
image : array_like, dtype=float
|
||||
The greyscale input image to detect edges on; should be normalized to
|
||||
The greyscale input image to detect edges on; should be normalized to
|
||||
0.0 to 1.0.
|
||||
|
||||
sigma : float
|
||||
@@ -85,21 +85,21 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
|
||||
The steps of the algorithm are as follows:
|
||||
|
||||
* Smooth the image using a Gaussian with ``sigma`` width.
|
||||
|
||||
|
||||
* Apply the horizontal and vertical Sobel operators to get the gradients
|
||||
within the image. The edge strength is the norm of the gradient.
|
||||
|
||||
* Thin potential edges to 1-pixel wide curves. First, find the normal
|
||||
to the edge at each point. This is done by looking at the
|
||||
|
||||
* Thin potential edges to 1-pixel wide curves. First, find the normal
|
||||
to the edge at each point. This is done by looking at the
|
||||
signs and the relative magnitude of the X-Sobel and Y-Sobel
|
||||
to sort the points into 4 categories: horizontal, vertical,
|
||||
diagonal and antidiagonal. Then look in the normal and reverse
|
||||
directions to see if the values in either of those directions are
|
||||
greater than the point in question. Use interpolation to get a mix of
|
||||
points instead of picking the one that's the closest to the normal.
|
||||
|
||||
* Perform a hysteresis thresholding: first label all points above the
|
||||
high threshold as edges. Then recursively label any point above the
|
||||
diagonal and antidiagonal. Then look in the normal and reverse
|
||||
directions to see if the values in either of those directions are
|
||||
greater than the point in question. Use interpolation to get a mix of
|
||||
points instead of picking the one that's the closest to the normal.
|
||||
|
||||
* Perform a hysteresis thresholding: first label all points above the
|
||||
high threshold as edges. Then recursively label any point above the
|
||||
low threshold that is 8-connected to a labeled point as an edge.
|
||||
|
||||
References
|
||||
@@ -120,7 +120,7 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
|
||||
>>> # First trial with the Canny filter, with the default smoothing
|
||||
>>> edges1 = filter.canny(im)
|
||||
>>> # Increase the smoothing for better results
|
||||
>>> edges2 = filter.canny(im, sigma=3)
|
||||
>>> edges2 = filter.canny(im, sigma=3)
|
||||
'''
|
||||
|
||||
#
|
||||
|
||||
@@ -12,6 +12,7 @@ import numpy as np
|
||||
from skimage import img_as_float
|
||||
from scipy.ndimage import convolve, binary_erosion, generate_binary_structure
|
||||
|
||||
|
||||
def sobel(image, mask=None):
|
||||
"""Calculate the absolute magnitude Sobel to find edges.
|
||||
|
||||
@@ -38,6 +39,7 @@ def sobel(image, mask=None):
|
||||
"""
|
||||
return np.sqrt(hsobel(image, mask)**2 + vsobel(image, mask)**2)
|
||||
|
||||
|
||||
def hsobel(image, mask=None):
|
||||
"""Find the horizontal edges of an image using the Sobel transform.
|
||||
|
||||
@@ -68,7 +70,7 @@ def hsobel(image, mask=None):
|
||||
mask = np.ones(image.shape, bool)
|
||||
big_mask = binary_erosion(mask,
|
||||
generate_binary_structure(2, 2),
|
||||
border_value = 0)
|
||||
border_value=0)
|
||||
result = np.abs(convolve(image,
|
||||
np.array([[ 1, 2, 1],
|
||||
[ 0, 0, 0],
|
||||
@@ -76,6 +78,7 @@ def hsobel(image, mask=None):
|
||||
result[big_mask == False] = 0
|
||||
return result
|
||||
|
||||
|
||||
def vsobel(image, mask=None):
|
||||
"""Find the vertical edges of an image using the Sobel transform.
|
||||
|
||||
@@ -114,6 +117,7 @@ def vsobel(image, mask=None):
|
||||
result[big_mask == False] = 0
|
||||
return result
|
||||
|
||||
|
||||
def prewitt(image, mask=None):
|
||||
"""Find the edge magnitude using the Prewitt transform.
|
||||
|
||||
@@ -134,7 +138,8 @@ def prewitt(image, mask=None):
|
||||
Return the square root of the sum of squares of the horizontal
|
||||
and vertical Prewitt transforms.
|
||||
"""
|
||||
return np.sqrt(hprewitt(image, mask) ** 2 + vprewitt(image, mask) ** 2)
|
||||
return np.sqrt(hprewitt(image, mask)**2 + vprewitt(image, mask)**2)
|
||||
|
||||
|
||||
def hprewitt(image, mask=None):
|
||||
"""Find the horizontal edges of an image using the Prewitt transform.
|
||||
@@ -174,6 +179,7 @@ def hprewitt(image, mask=None):
|
||||
result[big_mask == False] = 0
|
||||
return result
|
||||
|
||||
|
||||
def vprewitt(image, mask=None):
|
||||
"""Find the vertical edges of an image using the Prewitt transform.
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ from scipy.fftpack import fftshift, ifftshift
|
||||
|
||||
eps = np.finfo(float).eps
|
||||
|
||||
|
||||
def _min_limit(x, val=eps):
|
||||
mask = np.abs(x) < eps
|
||||
x[mask] = np.sign(x[mask]) * eps
|
||||
|
||||
|
||||
def _centre(x, oshape):
|
||||
"""Return an array of oshape from the centre of x.
|
||||
|
||||
@@ -23,6 +25,7 @@ def _centre(x, oshape):
|
||||
out = x[[slice(s, s + n) for s, n in zip(start, oshape)]]
|
||||
return out
|
||||
|
||||
|
||||
def _pad(data, shape):
|
||||
"""Pad the data to the given shape with zeros.
|
||||
|
||||
@@ -38,7 +41,6 @@ def _pad(data, shape):
|
||||
return out
|
||||
|
||||
|
||||
|
||||
class LPIFilter2D(object):
|
||||
"""Linear Position-Invariant Filter (2-dimensional)
|
||||
|
||||
@@ -83,21 +85,21 @@ class LPIFilter2D(object):
|
||||
|
||||
"""
|
||||
dshape = np.array(data.shape)
|
||||
dshape += (dshape % 2 == 0) # all filter dimensions must be uneven
|
||||
dshape += (dshape % 2 == 0) # all filter dimensions must be uneven
|
||||
oshape = np.array(data.shape) * 2 - 1
|
||||
|
||||
if self._cache is None or np.any(self._cache.shape != oshape):
|
||||
coords = np.mgrid[[slice(0, float(n)) for n in dshape]]
|
||||
# this steps over two sets of coordinates,
|
||||
# not over the coordinates individually
|
||||
for k,coord in enumerate(coords):
|
||||
coord -= (dshape[k] - 1)/2.
|
||||
coords = coords.reshape(2, -1).T # coordinate pairs (r,c)
|
||||
for k, coord in enumerate(coords):
|
||||
coord -= (dshape[k] - 1) / 2.
|
||||
coords = coords.reshape(2, -1).T # coordinate pairs (r,c)
|
||||
|
||||
f = self.impulse_response(coords[:,0],coords[:,1],
|
||||
f = self.impulse_response(coords[:, 0], coords[:, 1],
|
||||
**self.filter_params).reshape(dshape)
|
||||
|
||||
f = _pad(f,oshape)
|
||||
f = _pad(f, oshape)
|
||||
F = np.dual.fftn(f)
|
||||
self._cache = F
|
||||
else:
|
||||
@@ -108,7 +110,7 @@ class LPIFilter2D(object):
|
||||
|
||||
return F, G
|
||||
|
||||
def __call__(self,data):
|
||||
def __call__(self, data):
|
||||
"""Apply the filter to the given data.
|
||||
|
||||
*Parameters*:
|
||||
@@ -120,6 +122,7 @@ class LPIFilter2D(object):
|
||||
out = np.abs(_centre(out, data.shape))
|
||||
return out
|
||||
|
||||
|
||||
def forward(data, impulse_response=None, filter_params={},
|
||||
predefined_filter=None):
|
||||
"""Apply the given filter to data.
|
||||
@@ -155,6 +158,7 @@ def forward(data, impulse_response=None, filter_params={},
|
||||
predefined_filter = LPIFilter2D(impulse_response, **filter_params)
|
||||
return predefined_filter(data)
|
||||
|
||||
|
||||
def inverse(data, impulse_response=None, filter_params={}, max_gain=2,
|
||||
predefined_filter=None):
|
||||
"""Apply the filter in reverse to the given data.
|
||||
@@ -189,12 +193,13 @@ def inverse(data, impulse_response=None, filter_params={}, max_gain=2,
|
||||
F, G = filt._prepare(data)
|
||||
_min_limit(F)
|
||||
|
||||
F = 1/F
|
||||
F = 1 / F
|
||||
mask = np.abs(F) > max_gain
|
||||
F[mask] = np.sign(F[mask]) * max_gain
|
||||
|
||||
return _centre(np.abs(ifftshift(np.dual.ifftn(G * F))), data.shape)
|
||||
|
||||
|
||||
def wiener(data, impulse_response=None, filter_params={}, K=0.25,
|
||||
predefined_filter=None):
|
||||
"""Minimum Mean Square Error (Wiener) inverse filter.
|
||||
@@ -228,11 +233,11 @@ def wiener(data, impulse_response=None, filter_params={}, K=0.25,
|
||||
_min_limit(F)
|
||||
|
||||
H_mag_sqr = np.abs(F)**2
|
||||
F = 1/F * H_mag_sqr / (H_mag_sqr + K)
|
||||
F = 1 / F * H_mag_sqr / (H_mag_sqr + K)
|
||||
|
||||
return _centre(np.abs(ifftshift(np.dual.ifftn(G * F))), data.shape)
|
||||
|
||||
|
||||
def constrained_least_squares(data, lam, impulse_response=None,
|
||||
filter_params={}):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ Original author: Lee Kamentstky
|
||||
"""
|
||||
import numpy
|
||||
|
||||
|
||||
def rank_order(image):
|
||||
"""Return an image of the same shape where each pixel is the
|
||||
index of the pixel value in the ascending order of the unique
|
||||
index of the pixel value in the ascending order of the unique
|
||||
values of `image`, aka the rank-order value.
|
||||
|
||||
Parameters
|
||||
@@ -48,14 +49,12 @@ def rank_order(image):
|
||||
flat_image = image.ravel()
|
||||
sort_order = flat_image.argsort().astype(numpy.uint32)
|
||||
flat_image = flat_image[sort_order]
|
||||
sort_rank = numpy.zeros_like(sort_order)
|
||||
sort_rank = numpy.zeros_like(sort_order)
|
||||
is_different = flat_image[:-1] != flat_image[1:]
|
||||
numpy.cumsum(is_different, out=sort_rank[1:])
|
||||
original_values = numpy.zeros((sort_rank[-1]+1,),image.dtype)
|
||||
original_values = numpy.zeros((sort_rank[-1] + 1,), image.dtype)
|
||||
original_values[0] = flat_image[0]
|
||||
original_values[1:] = flat_image[1:][is_different]
|
||||
original_values[1:] = flat_image[1:][is_different]
|
||||
int_image = numpy.zeros_like(sort_order)
|
||||
int_image[sort_order] = sort_rank
|
||||
return (int_image.reshape(image.shape), original_values)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
|
||||
@@ -20,11 +21,11 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
author = 'scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Filters',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'SciPy License (BSD Style)',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
author='scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Filters',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from scipy.ndimage import binary_dilation, binary_erosion
|
||||
import skimage.filter as F
|
||||
from skimage import data_dir, img_as_float
|
||||
|
||||
|
||||
class TestSobel():
|
||||
def test_00_00_zeros(self):
|
||||
"""Sobel on an array of all zeros"""
|
||||
@@ -70,6 +71,7 @@ class TestHSobel():
|
||||
result = F.hsobel(image)
|
||||
assert (np.all(result == 0))
|
||||
|
||||
|
||||
class TestVSobel():
|
||||
def test_00_00_zeros(self):
|
||||
"""Vertical sobel on an array of all zeros"""
|
||||
@@ -101,6 +103,7 @@ class TestVSobel():
|
||||
eps = .000001
|
||||
assert (np.all(np.abs(result) < eps))
|
||||
|
||||
|
||||
class TestPrewitt():
|
||||
def test_00_00_zeros(self):
|
||||
"""Prewitt on an array of all zeros"""
|
||||
@@ -132,10 +135,11 @@ class TestPrewitt():
|
||||
image = (j >= 0).astype(float)
|
||||
result = F.prewitt(image)
|
||||
eps = .000001
|
||||
j[np.abs(i)==5] = 10000
|
||||
j[np.abs(i) == 5] = 10000
|
||||
assert (np.all(result[j == 0] == 1))
|
||||
assert (np.all(np.abs(result[np.abs(j) > 1]) < eps))
|
||||
|
||||
|
||||
class TestHPrewitt():
|
||||
def test_00_00_zeros(self):
|
||||
"""Horizontal sobel on an array of all zeros"""
|
||||
@@ -169,6 +173,7 @@ class TestHPrewitt():
|
||||
eps = .000001
|
||||
assert (np.all(np.abs(result) < eps))
|
||||
|
||||
|
||||
class TestVPrewitt():
|
||||
def test_00_00_zeros(self):
|
||||
"""Vertical prewitt on an array of all zeros"""
|
||||
|
||||
@@ -7,12 +7,13 @@ from skimage import data_dir
|
||||
from skimage.io import *
|
||||
from skimage.filter import *
|
||||
|
||||
|
||||
class TestLPIFilter2D():
|
||||
img = imread(os.path.join(data_dir, 'camera.png'),
|
||||
flatten=True)[:50,:50]
|
||||
flatten=True)[:50, :50]
|
||||
|
||||
def filt_func(self,r,c):
|
||||
return np.exp(-np.hypot(r,c)/1)
|
||||
def filt_func(self, r, c):
|
||||
return np.exp(-np.hypot(r, c) / 1)
|
||||
|
||||
def setUp(self):
|
||||
self.f = LPIFilter2D(self.filt_func)
|
||||
@@ -33,27 +34,26 @@ class TestLPIFilter2D():
|
||||
g = inverse(F, predefined_filter=self.f)
|
||||
assert_equal(g.shape, self.img.shape)
|
||||
|
||||
g1 = inverse(F[::-1,::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1,::-1]).sum() < 55)
|
||||
g1 = inverse(F[::-1, ::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1, ::-1]).sum() < 55)
|
||||
|
||||
# test cache
|
||||
g1 = inverse(F[::-1,::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1,::-1]).sum() < 55)
|
||||
g1 = inverse(F[::-1, ::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1, ::-1]).sum() < 55)
|
||||
|
||||
g1 = inverse(F[::-1, ::-1], self.filt_func)
|
||||
assert ((g - g1[::-1,::-1]).sum() < 55)
|
||||
assert ((g - g1[::-1, ::-1]).sum() < 55)
|
||||
|
||||
def test_wiener(self):
|
||||
F = self.f(self.img)
|
||||
g = wiener(F, predefined_filter=self.f)
|
||||
assert_equal(g.shape, self.img.shape)
|
||||
|
||||
g1 = wiener(F[::-1,::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1,::-1]).sum() < 1)
|
||||
g1 = wiener(F[::-1, ::-1], predefined_filter=self.f)
|
||||
assert ((g - g1[::-1, ::-1]).sum() < 1)
|
||||
|
||||
g1 = wiener(F[::-1,::-1], self.filt_func)
|
||||
assert ((g - g1[::-1,::-1]).sum() < 1)
|
||||
g1 = wiener(F[::-1, ::-1], self.filt_func)
|
||||
assert ((g - g1[::-1, ::-1]).sum() < 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -75,17 +75,19 @@ class TestSimpleImage():
|
||||
def test_otsu_camera_image():
|
||||
assert threshold_otsu(data.camera()) == 87
|
||||
|
||||
|
||||
def test_otsu_coins_image():
|
||||
assert threshold_otsu(data.coins()) == 107
|
||||
|
||||
|
||||
def test_otsu_coins_image_as_float():
|
||||
coins = skimage.img_as_float(data.coins())
|
||||
assert 0.41 < threshold_otsu(coins) < 0.42
|
||||
|
||||
|
||||
def test_otsu_lena_image():
|
||||
assert threshold_otsu(data.lena()) == 141
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
np.testing.run_module_suite()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from numpy.testing import run_module_suite
|
||||
from skimage import filter, data, color
|
||||
from skimage import img_as_uint
|
||||
|
||||
|
||||
class TestTvDenoise():
|
||||
|
||||
def test_tv_denoise_2d(self):
|
||||
@@ -14,7 +15,7 @@ class TestTvDenoise():
|
||||
# lena image
|
||||
lena = color.rgb2gray(data.lena())[:256, :256]
|
||||
# add noise to lena
|
||||
lena += 0.5 * lena.std()*np.random.randn(*lena.shape)
|
||||
lena += 0.5 * lena.std() * np.random.randn(*lena.shape)
|
||||
# clip noise so that it does not exceed allowed range for float images.
|
||||
lena = np.clip(lena, 0, 1)
|
||||
# denoise
|
||||
@@ -22,25 +23,26 @@ class TestTvDenoise():
|
||||
# which dtype?
|
||||
assert denoised_lena.dtype in [np.float, np.float32, np.float64]
|
||||
from scipy import ndimage
|
||||
grad = ndimage.morphological_gradient(lena, size=((3,3)))
|
||||
grad_denoised = ndimage.morphological_gradient(denoised_lena, size=((3,3)))
|
||||
grad = ndimage.morphological_gradient(lena, size=((3, 3)))
|
||||
grad_denoised = ndimage.morphological_gradient(
|
||||
denoised_lena, size=((3, 3)))
|
||||
# test if the total variation has decreased
|
||||
assert np.sqrt((grad_denoised**2).sum()) < np.sqrt((grad**2).sum()) / 2
|
||||
assert (np.sqrt((grad_denoised**2).sum())
|
||||
< np.sqrt((grad**2).sum()) / 2)
|
||||
denoised_lena_int = filter.tv_denoise(img_as_uint(lena),
|
||||
weight=60.0, keep_type=True)
|
||||
assert denoised_lena_int.dtype is np.dtype('uint16')
|
||||
|
||||
|
||||
def test_tv_denoise_3d(self):
|
||||
"""
|
||||
Apply the TV denoising algorithm on a 3D image representing
|
||||
a sphere.
|
||||
"""
|
||||
x, y, z = np.ogrid[0:40, 0:40, 0:40]
|
||||
mask = (x -22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
|
||||
mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
|
||||
mask = 100 * mask.astype(np.float)
|
||||
mask += 60
|
||||
mask += 20*np.random.randn(*mask.shape)
|
||||
mask += 20 * np.random.randn(*mask.shape)
|
||||
mask[mask < 0] = 0
|
||||
mask[mask > 255] = 255
|
||||
res = filter.tv_denoise(mask.astype(np.uint8),
|
||||
|
||||
@@ -86,6 +86,7 @@ def threshold_adaptive(image, block_size, method='gaussian', offset=0,
|
||||
|
||||
return image > (thresh_image - offset)
|
||||
|
||||
|
||||
def threshold_otsu(image, nbins=256):
|
||||
"""Return threshold value based on Otsu's method.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
|
||||
"""
|
||||
Perform total-variation denoising on 3-D arrays
|
||||
@@ -10,8 +11,8 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
|
||||
3-D input data to be denoised
|
||||
|
||||
weight: float, optional
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
|
||||
eps: float, optional
|
||||
relative difference of the value of the cost function that determines
|
||||
@@ -29,7 +30,7 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
|
||||
|
||||
Notes
|
||||
-----
|
||||
Rudin, Osher and Fatemi algorithm
|
||||
Rudin, Osher and Fatemi algorithm
|
||||
|
||||
Examples
|
||||
---------
|
||||
@@ -50,25 +51,25 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
|
||||
i = 0
|
||||
while i < n_iter_max:
|
||||
d = - px - py - pz
|
||||
d[1:] += px[:-1]
|
||||
d[:, 1:] += py[:, :-1]
|
||||
d[:, :, 1:] += pz[:, :, :-1]
|
||||
|
||||
d[1:] += px[:-1]
|
||||
d[:, 1:] += py[:, :-1]
|
||||
d[:, :, 1:] += pz[:, :, :-1]
|
||||
|
||||
out = im + d
|
||||
E = (d**2).sum()
|
||||
|
||||
gx[:-1] = np.diff(out, axis=0)
|
||||
gy[:, :-1] = np.diff(out, axis=1)
|
||||
gz[:, :, :-1] = np.diff(out, axis=2)
|
||||
gx[:-1] = np.diff(out, axis=0)
|
||||
gy[:, :-1] = np.diff(out, axis=1)
|
||||
gz[:, :, :-1] = np.diff(out, axis=2)
|
||||
norm = np.sqrt(gx**2 + gy**2 + gz**2)
|
||||
E += weight * norm.sum()
|
||||
norm *= 0.5 / weight
|
||||
norm += 1.
|
||||
px -= 1./6.*gx
|
||||
px -= 1. / 6. * gx
|
||||
px /= norm
|
||||
py -= 1./6.*gy
|
||||
py -= 1. / 6. * gy
|
||||
py /= norm
|
||||
pz -= 1/6.*gz
|
||||
pz -= 1 / 6. * gz
|
||||
pz /= norm
|
||||
E /= float(im.size)
|
||||
if i == 0:
|
||||
@@ -81,7 +82,8 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
|
||||
E_previous = E
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
|
||||
def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
||||
"""
|
||||
Perform total-variation denoising
|
||||
@@ -92,8 +94,8 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
||||
input data to be denoised
|
||||
|
||||
weight: float, optional
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
|
||||
eps: float, optional
|
||||
relative difference of the value of the cost function that determines
|
||||
@@ -114,14 +116,14 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
||||
The principle of total variation denoising is explained in
|
||||
http://en.wikipedia.org/wiki/Total_variation_denoising
|
||||
|
||||
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
|
||||
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
|
||||
that was proposed by Chambolle in [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [1] A. Chambolle, An algorithm for total variation minimization and
|
||||
applications, Journal of Mathematical Imaging and Vision,
|
||||
.. [1] A. Chambolle, An algorithm for total variation minimization and
|
||||
applications, Journal of Mathematical Imaging and Vision,
|
||||
Springer, 2004, 20, 89-97.
|
||||
|
||||
Examples
|
||||
@@ -140,21 +142,21 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
||||
d = np.zeros_like(im)
|
||||
i = 0
|
||||
while i < n_iter_max:
|
||||
d = -px -py
|
||||
d[1:] += px[:-1]
|
||||
d[:, 1:] += py[:, :-1]
|
||||
|
||||
d = -px - py
|
||||
d[1:] += px[:-1]
|
||||
d[:, 1:] += py[:, :-1]
|
||||
|
||||
out = im + d
|
||||
E = (d**2).sum()
|
||||
gx[:-1] = np.diff(out, axis=0)
|
||||
gy[:, :-1] = np.diff(out, axis=1)
|
||||
gx[:-1] = np.diff(out, axis=0)
|
||||
gy[:, :-1] = np.diff(out, axis=1)
|
||||
norm = np.sqrt(gx**2 + gy**2)
|
||||
E += weight * norm.sum()
|
||||
norm *= 0.5 / weight
|
||||
norm += 1
|
||||
px -= 0.25*gx
|
||||
px -= 0.25 * gx
|
||||
px /= norm
|
||||
py -= 0.25*gy
|
||||
py -= 0.25 * gy
|
||||
py /= norm
|
||||
E /= float(im.size)
|
||||
if i == 0:
|
||||
@@ -168,6 +170,7 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def tv_denoise(im, weight=50, eps=2.e-4, keep_type=False, n_iter_max=200):
|
||||
"""
|
||||
Perform total-variation denoising on 2-d and 3-d images
|
||||
@@ -176,21 +179,21 @@ def tv_denoise(im, weight=50, eps=2.e-4, keep_type=False, n_iter_max=200):
|
||||
----------
|
||||
im: ndarray (2d or 3d) of ints, uints or floats
|
||||
input data to be denoised. `im` can be of any numeric type,
|
||||
but it is cast into an ndarray of floats for the computation
|
||||
but it is cast into an ndarray of floats for the computation
|
||||
of the denoised image.
|
||||
|
||||
weight: float, optional
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
denoising weight. The greater ``weight``, the more denoising (at
|
||||
the expense of fidelity to ``input``)
|
||||
|
||||
eps: float, optional
|
||||
relative difference of the value of the cost function that
|
||||
relative difference of the value of the cost function that
|
||||
determines the stop criterion. The algorithm stops when:
|
||||
|
||||
(E_(n-1) - E_n) < eps * E_0
|
||||
|
||||
keep_type: bool, optional (False)
|
||||
whether the output has the same dtype as the input array.
|
||||
whether the output has the same dtype as the input array.
|
||||
keep_type is False by default, and the dtype of the output
|
||||
is np.float
|
||||
|
||||
@@ -209,19 +212,19 @@ def tv_denoise(im, weight=50, eps=2.e-4, keep_type=False, n_iter_max=200):
|
||||
http://en.wikipedia.org/wiki/Total_variation_denoising
|
||||
|
||||
The principle of total variation denoising is to minimize the
|
||||
total variation of the image, which can be roughly described as
|
||||
the integral of the norm of the image gradient. Total variation
|
||||
denoising tends to produce "cartoon-like" images, that is,
|
||||
total variation of the image, which can be roughly described as
|
||||
the integral of the norm of the image gradient. Total variation
|
||||
denoising tends to produce "cartoon-like" images, that is,
|
||||
piecewise-constant images.
|
||||
|
||||
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
|
||||
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
|
||||
that was proposed by Chambolle in [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [1] A. Chambolle, An algorithm for total variation minimization and
|
||||
applications, Journal of Mathematical Imaging and Vision,
|
||||
.. [1] A. Chambolle, An algorithm for total variation minimization and
|
||||
applications, Journal of Mathematical Imaging and Vision,
|
||||
Springer, 2004, 20, 89-97.
|
||||
|
||||
Examples
|
||||
@@ -249,9 +252,9 @@ def tv_denoise(im, weight=50, eps=2.e-4, keep_type=False, n_iter_max=200):
|
||||
elif im.ndim == 3:
|
||||
out = _tv_denoise_3d(im, weight, eps, n_iter_max)
|
||||
else:
|
||||
raise ValueError('only 2-d and 3-d images may be denoised with this function')
|
||||
raise ValueError('only 2-d and 3-d images may be denoised with this '
|
||||
'function')
|
||||
if keep_type:
|
||||
return out.astype(im_type)
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from ._mcp import MCP, MCP_Geometric, make_offsets
|
||||
|
||||
|
||||
def route_through_array(array, start, end, fully_connected=True, geometric=True):
|
||||
"""Simple example of how to use the MCP and MCP_Geometric classes.
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os.path
|
||||
|
||||
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
|
||||
|
||||
@@ -28,10 +29,10 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Graph-based Image-processing Algorithms',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'Modified BSD',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Graph-based Image-processing Algorithms',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='Modified BSD',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import numpy as np
|
||||
from . import _spath
|
||||
|
||||
|
||||
def shortest_path(arr, reach=1, axis=-1, output_indexlist=False):
|
||||
"""Find the shortest path through an n-d array from one side to another.
|
||||
|
||||
@@ -39,7 +40,7 @@ def shortest_path(arr, reach=1, axis=-1, output_indexlist=False):
|
||||
# a grid defined by the reach.
|
||||
if axis < 0:
|
||||
axis += arr.ndim
|
||||
offset_ind_shape = (2*reach + 1,) * (arr.ndim - 1)
|
||||
offset_ind_shape = (2 * reach + 1,) * (arr.ndim - 1)
|
||||
offset_indices = np.indices(offset_ind_shape) - reach
|
||||
offset_indices = np.insert(offset_indices, axis,
|
||||
np.ones(offset_ind_shape), axis=0)
|
||||
@@ -49,7 +50,7 @@ def shortest_path(arr, reach=1, axis=-1, output_indexlist=False):
|
||||
# Valid starting positions are anywhere on the hyperplane defined by
|
||||
# position 0 on the given axis. Ending positions are anywhere on the
|
||||
# hyperplane at position -1 along the same.
|
||||
non_axis_shape = arr.shape[:axis] + arr.shape[axis+1:]
|
||||
non_axis_shape = arr.shape[:axis] + arr.shape[axis + 1:]
|
||||
non_axis_indices = np.indices(non_axis_shape)
|
||||
non_axis_size = np.multiply.reduce(non_axis_shape)
|
||||
start_indices = np.insert(non_axis_indices, axis,
|
||||
@@ -72,7 +73,7 @@ def shortest_path(arr, reach=1, axis=-1, output_indexlist=False):
|
||||
|
||||
if not output_indexlist:
|
||||
traceback = np.array(traceback)
|
||||
traceback = np.concatenate([traceback[:,:axis], traceback[:,axis+1:]],
|
||||
traceback = np.concatenate([traceback[:, :axis], traceback[:, axis + 1:]],
|
||||
axis=1)
|
||||
traceback = np.squeeze(traceback)
|
||||
|
||||
|
||||
@@ -5,18 +5,20 @@ import time
|
||||
import random
|
||||
import skimage.graph.heap as heap
|
||||
|
||||
|
||||
def test_heap():
|
||||
_test_heap(100000, True)
|
||||
_test_heap(100000, False)
|
||||
|
||||
|
||||
def _test_heap(n, fast_update):
|
||||
# generate random numbers with duplicates
|
||||
random.seed(0)
|
||||
a = [random.uniform(1.0,100.0) for i in range(n//2)]
|
||||
a = a+a
|
||||
|
||||
a = [random.uniform(1.0, 100.0) for i in range(n // 2)]
|
||||
a = a + a
|
||||
|
||||
t0 = time.clock()
|
||||
|
||||
|
||||
# insert in heap with random removals
|
||||
if fast_update:
|
||||
h = heap.FastUpdateBinaryHeap(128, n)
|
||||
@@ -25,12 +27,12 @@ def _test_heap(n, fast_update):
|
||||
for i in range(len(a)):
|
||||
h.push(a[i], i)
|
||||
if a[i] < 25:
|
||||
# double-push same ref sometimes to test fast update codepaths
|
||||
h.push(2*a[i], i)
|
||||
# double-push same ref sometimes to test fast update codepaths
|
||||
h.push(2 * a[i], i)
|
||||
if 25 < a[i] < 50:
|
||||
# pop some to test random removal
|
||||
h.pop()
|
||||
|
||||
# pop some to test random removal
|
||||
h.pop()
|
||||
|
||||
# pop from heap
|
||||
b = []
|
||||
while True:
|
||||
@@ -38,14 +40,14 @@ def _test_heap(n, fast_update):
|
||||
b.append(h.pop()[0])
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
t1 = time.clock()
|
||||
|
||||
|
||||
# verify
|
||||
for i in range(1,len(b)):
|
||||
assert(b[i] >= b[i-1])
|
||||
|
||||
return t1-t0
|
||||
for i in range(1, len(b)):
|
||||
assert(b[i] >= b[i - 1])
|
||||
|
||||
return t1 - t0
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
@@ -3,7 +3,7 @@ from numpy.testing import *
|
||||
|
||||
import skimage.graph.mcp as mcp
|
||||
|
||||
a = np.ones((8,8), dtype=np.float32)
|
||||
a = np.ones((8, 8), dtype=np.float32)
|
||||
a[1:-1, 1] = 0
|
||||
a[1, 1:-1] = 0
|
||||
|
||||
@@ -16,19 +16,20 @@ a[1, 1:-1] = 0
|
||||
## [ 1., 0., 1., 1., 1., 1., 1., 1.],
|
||||
## [ 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)
|
||||
|
||||
|
||||
def test_basic():
|
||||
m = mcp.MCP(a, fully_connected=True)
|
||||
costs, traceback = m.find_costs([(1,6)])
|
||||
costs, traceback = m.find_costs([(1, 6)])
|
||||
return_path = m.traceback((7, 2))
|
||||
assert_array_equal(costs,
|
||||
[[ 1., 1., 1., 1., 1., 1., 1., 1.],
|
||||
[ 1., 0., 0., 0., 0., 0., 0., 1.],
|
||||
[ 1., 0., 1., 1., 1., 1., 1., 1.],
|
||||
[ 1., 0., 1., 2., 2., 2., 2., 2.],
|
||||
[ 1., 0., 1., 2., 3., 3., 3., 3.],
|
||||
[ 1., 0., 1., 2., 3., 4., 4., 4.],
|
||||
[ 1., 0., 1., 2., 3., 4., 5., 5.],
|
||||
[ 1., 1., 1., 2., 3., 4., 5., 6.]])
|
||||
[[1., 1., 1., 1., 1., 1., 1., 1.],
|
||||
[1., 0., 0., 0., 0., 0., 0., 1.],
|
||||
[1., 0., 1., 1., 1., 1., 1., 1.],
|
||||
[1., 0., 1., 2., 2., 2., 2., 2.],
|
||||
[1., 0., 1., 2., 3., 3., 3., 3.],
|
||||
[1., 0., 1., 2., 3., 4., 4., 4.],
|
||||
[1., 0., 1., 2., 3., 4., 5., 5.],
|
||||
[1., 1., 1., 2., 3., 4., 5., 6.]])
|
||||
|
||||
assert_array_equal(return_path,
|
||||
[(1, 6),
|
||||
@@ -43,8 +44,9 @@ def test_basic():
|
||||
(6, 1),
|
||||
(7, 2)])
|
||||
|
||||
|
||||
def test_neg_inf():
|
||||
expected_costs = np.where(a==1, np.inf, 0)
|
||||
expected_costs = np.where(a == 1, np.inf, 0)
|
||||
expected_path = [(1, 6),
|
||||
(1, 5),
|
||||
(1, 4),
|
||||
@@ -55,8 +57,8 @@ def test_neg_inf():
|
||||
(4, 1),
|
||||
(5, 1),
|
||||
(6, 1)]
|
||||
test_neg = np.where(a==1, -1, 0)
|
||||
test_inf = np.where(a==1, np.inf, 0)
|
||||
test_neg = np.where(a == 1, -1, 0)
|
||||
test_inf = np.where(a == 1, np.inf, 0)
|
||||
m = mcp.MCP(test_neg, fully_connected=True)
|
||||
costs, traceback = m.find_costs([(1, 6)])
|
||||
return_path = m.traceback((6, 1))
|
||||
@@ -67,11 +69,12 @@ def test_neg_inf():
|
||||
return_path = m.traceback((6, 1))
|
||||
assert_array_equal(costs, expected_costs)
|
||||
assert_array_equal(return_path, expected_path)
|
||||
|
||||
|
||||
|
||||
def test_route():
|
||||
return_path, cost = mcp.route_through_array(a, (1,6), (7,2), geometric=True)
|
||||
assert_almost_equal(cost, np.sqrt(2)/2)
|
||||
return_path, cost = mcp.route_through_array(a, (1, 6), (7, 2),
|
||||
geometric=True)
|
||||
assert_almost_equal(cost, np.sqrt(2) / 2)
|
||||
assert_array_equal(return_path,
|
||||
[(1, 6),
|
||||
(1, 5),
|
||||
@@ -85,19 +88,20 @@ def test_route():
|
||||
(6, 1),
|
||||
(7, 2)])
|
||||
|
||||
|
||||
def test_no_diagonal():
|
||||
m = mcp.MCP(a, fully_connected=False)
|
||||
costs, traceback = m.find_costs([(1,6)])
|
||||
costs, traceback = m.find_costs([(1, 6)])
|
||||
return_path = m.traceback((7, 2))
|
||||
assert_array_equal(costs,
|
||||
[[ 2., 1., 1., 1., 1., 1., 1., 2.],
|
||||
[ 1., 0., 0., 0., 0., 0., 0., 1.],
|
||||
[ 1., 0., 1., 1., 1., 1., 1., 2.],
|
||||
[ 1., 0., 1., 2., 2., 2., 2., 3.],
|
||||
[ 1., 0., 1., 2., 3., 3., 3., 4.],
|
||||
[ 1., 0., 1., 2., 3., 4., 4., 5.],
|
||||
[ 1., 0., 1., 2., 3., 4., 5., 6.],
|
||||
[ 2., 1., 2., 3., 4., 5., 6., 7.]])
|
||||
[[2., 1., 1., 1., 1., 1., 1., 2.],
|
||||
[1., 0., 0., 0., 0., 0., 0., 1.],
|
||||
[1., 0., 1., 1., 1., 1., 1., 2.],
|
||||
[1., 0., 1., 2., 2., 2., 2., 3.],
|
||||
[1., 0., 1., 2., 3., 3., 3., 4.],
|
||||
[1., 0., 1., 2., 3., 4., 4., 5.],
|
||||
[1., 0., 1., 2., 3., 4., 5., 6.],
|
||||
[2., 1., 2., 3., 4., 5., 6., 7.]])
|
||||
assert_array_equal(return_path,
|
||||
[(1, 6),
|
||||
(1, 5),
|
||||
@@ -115,34 +119,36 @@ def test_no_diagonal():
|
||||
|
||||
|
||||
def test_offsets():
|
||||
offsets = [(1,i) for i in range(10)] + [(1, -i) for i in range(1,10)]
|
||||
m = mcp.MCP(a, offsets=offsets)
|
||||
costs, traceback = m.find_costs([(1,6)])
|
||||
assert_array_equal(traceback,
|
||||
[[-1, -1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1, -1],
|
||||
[15, 14, 13, 12, 11, 10, 0, 1],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6]])
|
||||
|
||||
offsets = [(1, i) for i in range(10)] + [(1, -i) for i in range(1, 10)]
|
||||
m = mcp.MCP(a, offsets=offsets)
|
||||
costs, traceback = m.find_costs([(1, 6)])
|
||||
assert_array_equal(traceback,
|
||||
[[-1, -1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1, -1],
|
||||
[15, 14, 13, 12, 11, 10, 0, 1],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6],
|
||||
[10, 0, 1, 2, 3, 4, 5, 6]])
|
||||
|
||||
|
||||
def test_crashing():
|
||||
for shape in [(100, 100), (5, 8, 13, 17)]*5:
|
||||
for shape in [(100, 100), (5, 8, 13, 17)] * 5:
|
||||
yield _test_random, shape
|
||||
|
||||
|
||||
def _test_random(shape):
|
||||
# Just tests for crashing -- not for correctness.
|
||||
np.random.seed(0)
|
||||
a = np.random.random(shape).astype(np.float32)
|
||||
starts = [[0]*len(shape), [-1]*len(shape),
|
||||
(np.random.random(len(shape))*shape).astype(int)]
|
||||
ends = [(np.random.random(len(shape))*shape).astype(int) for i in range(4)]
|
||||
starts = [[0] * len(shape), [-1] * len(shape),
|
||||
(np.random.random(len(shape)) * shape).astype(int)]
|
||||
ends = [(np.random.random(len(shape)) * shape).astype(int)
|
||||
for i in range(4)]
|
||||
m = mcp.MCP(a, fully_connected=True)
|
||||
costs, offsets = m.find_costs(starts)
|
||||
for point in [(np.random.random(len(shape))*shape).astype(int)
|
||||
for point in [(np.random.random(len(shape)) * shape).astype(int)
|
||||
for i in range(4)]:
|
||||
m.traceback(point)
|
||||
m._reset()
|
||||
|
||||
@@ -3,6 +3,7 @@ from numpy.testing import *
|
||||
|
||||
import skimage.graph.spath as spath
|
||||
|
||||
|
||||
def test_basic():
|
||||
x = np.array([[1, 1, 3],
|
||||
[0, 2, 0],
|
||||
@@ -11,6 +12,7 @@ def test_basic():
|
||||
assert_array_equal(path, [0, 0, 1])
|
||||
assert_equal(cost, 1)
|
||||
|
||||
|
||||
def test_reach():
|
||||
x = np.array([[1, 1, 3],
|
||||
[0, 2, 0],
|
||||
@@ -19,6 +21,7 @@ def test_reach():
|
||||
assert_array_equal(path, [0, 0, 2])
|
||||
assert_equal(cost, 0)
|
||||
|
||||
|
||||
def test_non_square():
|
||||
x = np.array([[1, 1, 1, 1, 5, 5, 5],
|
||||
[5, 0, 0, 5, 9, 1, 1],
|
||||
|
||||
@@ -8,6 +8,7 @@ import numpy as np
|
||||
# Shared image queue
|
||||
_image_stack = []
|
||||
|
||||
|
||||
def push(img):
|
||||
"""Push an image onto the shared image stack.
|
||||
|
||||
@@ -22,6 +23,7 @@ def push(img):
|
||||
|
||||
_image_stack.append(img)
|
||||
|
||||
|
||||
def pop():
|
||||
"""Pop an image from the shared image stack.
|
||||
|
||||
@@ -33,6 +35,7 @@ def pop():
|
||||
"""
|
||||
return _image_stack.pop()
|
||||
|
||||
|
||||
def imread(fname, as_grey=False, plugin=None, flatten=None,
|
||||
**plugin_args):
|
||||
"""Load an image from file.
|
||||
@@ -76,6 +79,7 @@ def imread(fname, as_grey=False, plugin=None, flatten=None,
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def imread_collection(load_pattern, conserve_memory=True,
|
||||
plugin=None, **plugin_args):
|
||||
"""
|
||||
@@ -128,6 +132,7 @@ def imsave(fname, arr, plugin=None, **plugin_args):
|
||||
"""
|
||||
return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
|
||||
|
||||
|
||||
def imshow(arr, plugin=None, **plugin_args):
|
||||
"""Display an image.
|
||||
|
||||
@@ -148,6 +153,7 @@ def imshow(arr, plugin=None, **plugin_args):
|
||||
"""
|
||||
return call_plugin('imshow', arr, plugin=plugin, **plugin_args)
|
||||
|
||||
|
||||
def show():
|
||||
'''Display pending images.
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ def imread(fname, dtype=None):
|
||||
for hdu in hdulist:
|
||||
if isinstance(hdu, pyfits.ImageHDU) or \
|
||||
isinstance(hdu, pyfits.PrimaryHDU):
|
||||
if hdu.data is not None:
|
||||
img_array = hdu.data
|
||||
break
|
||||
if hdu.data is not None:
|
||||
img_array = hdu.data
|
||||
break
|
||||
hdulist.close()
|
||||
|
||||
return img_array
|
||||
@@ -109,7 +109,7 @@ def imread_collection(load_pattern, conserve_memory=True):
|
||||
|
||||
def FITSFactory(image_ext):
|
||||
"""Load an image extension from a FITS file and return a NumPy array
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
@@ -136,7 +136,7 @@ def FITSFactory(image_ext):
|
||||
raise ValueError("Expected a (filename, extension) tuple")
|
||||
|
||||
hdulist = pyfits.open(filename)
|
||||
|
||||
|
||||
data = hdulist[extnum].data
|
||||
|
||||
hdulist.close()
|
||||
@@ -146,4 +146,3 @@ def FITSFactory(image_ext):
|
||||
(extnum, filename))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def _generate_candidate_libs():
|
||||
lib_dirs.append(os.path.join(os.environ['HOME'], 'lib'))
|
||||
lib_dirs = [ld for ld in lib_dirs if os.path.exists(ld)]
|
||||
|
||||
lib_names = ['libfreeimage', 'freeimage'] # should be lower-case!
|
||||
lib_names = ['libfreeimage', 'freeimage'] # should be lower-case!
|
||||
# Now attempt to find libraries of that name in the given directory
|
||||
# (case-insensitive and without regard for extension)
|
||||
lib_paths = []
|
||||
@@ -34,6 +34,7 @@ def _generate_candidate_libs():
|
||||
|
||||
return lib_dirs, lib_paths
|
||||
|
||||
|
||||
def load_freeimage():
|
||||
if sys.platform == 'win32':
|
||||
loader = ctypes.windll
|
||||
@@ -70,13 +71,13 @@ def load_freeimage():
|
||||
if errors:
|
||||
# No freeimage library loaded, and load-errors reported for some
|
||||
# candidate libs
|
||||
err_txt = ['%s:\n%s'%(l, str(e.message)) for l, e in errors]
|
||||
err_txt = ['%s:\n%s' % (l, str(e.message)) for l, e in errors]
|
||||
raise OSError('One or more FreeImage libraries were found, but '
|
||||
'could not be loaded due to the following errors:\n'+
|
||||
'could not be loaded due to the following errors:\n'
|
||||
'\n\n'.join(err_txt))
|
||||
else:
|
||||
# No errors, because no potential libraries found at all!
|
||||
raise OSError('Could not find a FreeImage library in any of:\n'+
|
||||
raise OSError('Could not find a FreeImage library in any of:\n' +
|
||||
'\n'.join(lib_dirs))
|
||||
|
||||
# FreeImage found
|
||||
@@ -113,7 +114,9 @@ API = {
|
||||
}
|
||||
|
||||
# Albert's ctypes pattern
|
||||
def register_api(lib,api):
|
||||
|
||||
|
||||
def register_api(lib, api):
|
||||
for f, (restype, argtypes) in api.items():
|
||||
func = getattr(lib, f)
|
||||
func.restype = restype
|
||||
@@ -205,111 +208,112 @@ class FI_TYPES(object):
|
||||
extra_dims = cls.extra_dims[fi_type]
|
||||
return numpy.dtype(dtype), extra_dims + [w, h]
|
||||
|
||||
|
||||
class IO_FLAGS(object):
|
||||
FIF_LOAD_NOPIXELS = 0x8000 # loading: load the image header only
|
||||
# (not supported by all plugins)
|
||||
FIF_LOAD_NOPIXELS = 0x8000 # loading: load the image header only
|
||||
# (not supported by all plugins)
|
||||
|
||||
BMP_DEFAULT = 0
|
||||
BMP_SAVE_RLE = 1
|
||||
CUT_DEFAULT = 0
|
||||
DDS_DEFAULT = 0
|
||||
EXR_DEFAULT = 0 # save data as half with piz-based wavelet compression
|
||||
EXR_FLOAT = 0x0001 # save data as float instead of as half (not recommended)
|
||||
EXR_NONE = 0x0002 # save with no compression
|
||||
EXR_ZIP = 0x0004 # save with zlib compression, in blocks of 16 scan lines
|
||||
EXR_PIZ = 0x0008 # save with piz-based wavelet compression
|
||||
EXR_PXR24 = 0x0010 # save with lossy 24-bit float compression
|
||||
EXR_B44 = 0x0020 # save with lossy 44% float compression
|
||||
EXR_DEFAULT = 0 # save data as half with piz-based wavelet compression
|
||||
EXR_FLOAT = 0x0001 # save data as float instead of as half (not recommended)
|
||||
EXR_NONE = 0x0002 # save with no compression
|
||||
EXR_ZIP = 0x0004 # save with zlib compression, in blocks of 16 scan lines
|
||||
EXR_PIZ = 0x0008 # save with piz-based wavelet compression
|
||||
EXR_PXR24 = 0x0010 # save with lossy 24-bit float compression
|
||||
EXR_B44 = 0x0020 # save with lossy 44% float compression
|
||||
# - goes to 22% when combined with EXR_LC
|
||||
EXR_LC = 0x0040 # save images with one luminance and two chroma channels,
|
||||
EXR_LC = 0x0040 # save images with one luminance and two chroma channels,
|
||||
# rather than as RGB (lossy compression)
|
||||
FAXG3_DEFAULT = 0
|
||||
GIF_DEFAULT = 0
|
||||
GIF_LOAD256 = 1 # Load the image as a 256 color image with ununsed
|
||||
# palette entries, if it's 16 or 2 color
|
||||
GIF_PLAYBACK = 2 # 'Play' the GIF to generate each frame (as 32bpp)
|
||||
# instead of returning raw frame data when loading
|
||||
GIF_LOAD256 = 1 # Load the image as a 256 color image with ununsed
|
||||
# palette entries, if it's 16 or 2 color
|
||||
GIF_PLAYBACK = 2 # 'Play' the GIF to generate each frame (as 32bpp)
|
||||
# instead of returning raw frame data when loading
|
||||
HDR_DEFAULT = 0
|
||||
ICO_DEFAULT = 0
|
||||
ICO_MAKEALPHA = 1 # convert to 32bpp and create an alpha channel from the
|
||||
# AND-mask when loading
|
||||
ICO_MAKEALPHA = 1 # convert to 32bpp and create an alpha channel from the
|
||||
# AND-mask when loading
|
||||
IFF_DEFAULT = 0
|
||||
J2K_DEFAULT = 0 # save with a 16:1 rate
|
||||
JP2_DEFAULT = 0 # save with a 16:1 rate
|
||||
JPEG_DEFAULT = 0 # loading (see JPEG_FAST);
|
||||
# saving (see JPEG_QUALITYGOOD|JPEG_SUBSAMPLING_420)
|
||||
JPEG_FAST = 0x0001 # load the file as fast as possible,
|
||||
# sacrificing some quality
|
||||
JPEG_ACCURATE = 0x0002 # load the file with the best quality,
|
||||
# sacrificing some speed
|
||||
JPEG_CMYK = 0x0004 # load separated CMYK "as is"
|
||||
# (use | to combine with other load flags)
|
||||
JPEG_EXIFROTATE = 0x0008 # load and rotate according to
|
||||
# Exif 'Orientation' tag if available
|
||||
JPEG_QUALITYSUPERB = 0x80 # save with superb quality (100:1)
|
||||
JPEG_QUALITYGOOD = 0x0100 # save with good quality (75:1)
|
||||
JPEG_QUALITYNORMAL = 0x0200 # save with normal quality (50:1)
|
||||
JPEG_QUALITYAVERAGE = 0x0400 # save with average quality (25:1)
|
||||
JPEG_QUALITYBAD = 0x0800 # save with bad quality (10:1)
|
||||
JPEG_PROGRESSIVE = 0x2000 # save as a progressive-JPEG
|
||||
# (use | to combine with other save flags)
|
||||
JPEG_SUBSAMPLING_411 = 0x1000 # save with high 4x1 chroma
|
||||
# subsampling (4:1:1)
|
||||
JPEG_SUBSAMPLING_420 = 0x4000 # save with medium 2x2 medium chroma
|
||||
# subsampling (4:2:0) - default value
|
||||
JPEG_SUBSAMPLING_422 = 0x8000 # save with low 2x1 chroma subsampling (4:2:2)
|
||||
JPEG_SUBSAMPLING_444 = 0x10000 # save with no chroma subsampling (4:4:4)
|
||||
JPEG_OPTIMIZE = 0x20000 # on saving, compute optimal Huffman coding tables
|
||||
# (can reduce a few percent of file size)
|
||||
JPEG_BASELINE = 0x40000 # save basic JPEG, without metadata or any markers
|
||||
J2K_DEFAULT = 0 # save with a 16:1 rate
|
||||
JP2_DEFAULT = 0 # save with a 16:1 rate
|
||||
JPEG_DEFAULT = 0 # loading (see JPEG_FAST);
|
||||
# saving (see JPEG_QUALITYGOOD|JPEG_SUBSAMPLING_420)
|
||||
JPEG_FAST = 0x0001 # load the file as fast as possible,
|
||||
# sacrificing some quality
|
||||
JPEG_ACCURATE = 0x0002 # load the file with the best quality,
|
||||
# sacrificing some speed
|
||||
JPEG_CMYK = 0x0004 # load separated CMYK "as is"
|
||||
# (use | to combine with other load flags)
|
||||
JPEG_EXIFROTATE = 0x0008 # load and rotate according to
|
||||
# Exif 'Orientation' tag if available
|
||||
JPEG_QUALITYSUPERB = 0x80 # save with superb quality (100:1)
|
||||
JPEG_QUALITYGOOD = 0x0100 # save with good quality (75:1)
|
||||
JPEG_QUALITYNORMAL = 0x0200 # save with normal quality (50:1)
|
||||
JPEG_QUALITYAVERAGE = 0x0400 # save with average quality (25:1)
|
||||
JPEG_QUALITYBAD = 0x0800 # save with bad quality (10:1)
|
||||
JPEG_PROGRESSIVE = 0x2000 # save as a progressive-JPEG
|
||||
# (use | to combine with other save flags)
|
||||
JPEG_SUBSAMPLING_411 = 0x1000 # save with high 4x1 chroma
|
||||
# subsampling (4:1:1)
|
||||
JPEG_SUBSAMPLING_420 = 0x4000 # save with medium 2x2 medium chroma
|
||||
# subsampling (4:2:0) - default value
|
||||
JPEG_SUBSAMPLING_422 = 0x8000 # save with low 2x1 chroma subsampling (4:2:2)
|
||||
JPEG_SUBSAMPLING_444 = 0x10000 # save with no chroma subsampling (4:4:4)
|
||||
JPEG_OPTIMIZE = 0x20000 # on saving, compute optimal Huffman coding tables
|
||||
# (can reduce a few percent of file size)
|
||||
JPEG_BASELINE = 0x40000 # save basic JPEG, without metadata or any markers
|
||||
KOALA_DEFAULT = 0
|
||||
LBM_DEFAULT = 0
|
||||
MNG_DEFAULT = 0
|
||||
PCD_DEFAULT = 0
|
||||
PCD_BASE = 1 # load the bitmap sized 768 x 512
|
||||
PCD_BASEDIV4 = 2 # load the bitmap sized 384 x 256
|
||||
PCD_BASEDIV16 = 3 # load the bitmap sized 192 x 128
|
||||
PCD_BASE = 1 # load the bitmap sized 768 x 512
|
||||
PCD_BASEDIV4 = 2 # load the bitmap sized 384 x 256
|
||||
PCD_BASEDIV16 = 3 # load the bitmap sized 192 x 128
|
||||
PCX_DEFAULT = 0
|
||||
PFM_DEFAULT = 0
|
||||
PICT_DEFAULT = 0
|
||||
PNG_DEFAULT = 0
|
||||
PNG_IGNOREGAMMA = 1 # loading: avoid gamma correction
|
||||
PNG_Z_BEST_SPEED = 0x0001 # save using ZLib level 1 compression flag
|
||||
# (default value is 6)
|
||||
PNG_Z_DEFAULT_COMPRESSION = 0x0006 # save using ZLib level 6 compression
|
||||
# flag (default recommended value)
|
||||
PNG_Z_BEST_COMPRESSION = 0x0009 # save using ZLib level 9 compression flag
|
||||
# (default value is 6)
|
||||
PNG_Z_NO_COMPRESSION = 0x0100 # save without ZLib compression
|
||||
PNG_INTERLACED = 0x0200 # save using Adam7 interlacing (use | to combine
|
||||
# with other save flags)
|
||||
PNG_IGNOREGAMMA = 1 # loading: avoid gamma correction
|
||||
PNG_Z_BEST_SPEED = 0x0001 # save using ZLib level 1 compression flag
|
||||
# (default value is 6)
|
||||
PNG_Z_DEFAULT_COMPRESSION = 0x0006 # save using ZLib level 6 compression
|
||||
# flag (default recommended value)
|
||||
PNG_Z_BEST_COMPRESSION = 0x0009 # save using ZLib level 9 compression flag
|
||||
# (default value is 6)
|
||||
PNG_Z_NO_COMPRESSION = 0x0100 # save without ZLib compression
|
||||
PNG_INTERLACED = 0x0200 # save using Adam7 interlacing (use | to combine
|
||||
# with other save flags)
|
||||
PNM_DEFAULT = 0
|
||||
PNM_SAVE_RAW = 0 # Writer saves in RAW format (i.e. P4, P5 or P6)
|
||||
PNM_SAVE_ASCII = 1 # Writer saves in ASCII format (i.e. P1, P2 or P3)
|
||||
PNM_SAVE_RAW = 0 # Writer saves in RAW format (i.e. P4, P5 or P6)
|
||||
PNM_SAVE_ASCII = 1 # Writer saves in ASCII format (i.e. P1, P2 or P3)
|
||||
PSD_DEFAULT = 0
|
||||
PSD_CMYK = 1 # reads tags for separated CMYK (default is conversion to RGB)
|
||||
PSD_LAB = 2 # reads tags for CIELab (default is conversion to RGB)
|
||||
PSD_CMYK = 1 # reads tags for separated CMYK (default is conversion to RGB)
|
||||
PSD_LAB = 2 # reads tags for CIELab (default is conversion to RGB)
|
||||
RAS_DEFAULT = 0
|
||||
RAW_DEFAULT = 0 # load the file as linear RGB 48-bit
|
||||
RAW_PREVIEW = 1 # try to load the embedded JPEG preview with included
|
||||
# Exif Data or default to RGB 24-bit
|
||||
RAW_DISPLAY = 2 # load the file as RGB 24-bit
|
||||
RAW_DEFAULT = 0 # load the file as linear RGB 48-bit
|
||||
RAW_PREVIEW = 1 # try to load the embedded JPEG preview with included
|
||||
# Exif Data or default to RGB 24-bit
|
||||
RAW_DISPLAY = 2 # load the file as RGB 24-bit
|
||||
SGI_DEFAULT = 0
|
||||
TARGA_DEFAULT = 0
|
||||
TARGA_LOAD_RGB888 = 1 # Convert RGB555 and ARGB8888 -> RGB888.
|
||||
TARGA_SAVE_RLE = 2 # Save with RLE compression
|
||||
TARGA_LOAD_RGB888 = 1 # Convert RGB555 and ARGB8888 -> RGB888.
|
||||
TARGA_SAVE_RLE = 2 # Save with RLE compression
|
||||
TIFF_DEFAULT = 0
|
||||
TIFF_CMYK = 0x0001 # reads/stores tags for separated CMYK
|
||||
# (use | to combine with compression flags)
|
||||
TIFF_PACKBITS = 0x0100 # save using PACKBITS compression
|
||||
TIFF_DEFLATE = 0x0200 # save using DEFLATE (a.k.a. ZLIB) compression
|
||||
TIFF_ADOBE_DEFLATE = 0x0400 # save using ADOBE DEFLATE compression
|
||||
TIFF_NONE = 0x0800 # save without any compression
|
||||
TIFF_CCITTFAX3 = 0x1000 # save using CCITT Group 3 fax encoding
|
||||
TIFF_CCITTFAX4 = 0x2000 # save using CCITT Group 4 fax encoding
|
||||
TIFF_LZW = 0x4000 # save using LZW compression
|
||||
TIFF_JPEG = 0x8000 # save using JPEG compression
|
||||
TIFF_LOGLUV = 0x10000 # save using LogLuv compression
|
||||
TIFF_CMYK = 0x0001 # reads/stores tags for separated CMYK
|
||||
# (use | to combine with compression flags)
|
||||
TIFF_PACKBITS = 0x0100 # save using PACKBITS compression
|
||||
TIFF_DEFLATE = 0x0200 # save using DEFLATE (a.k.a. ZLIB) compression
|
||||
TIFF_ADOBE_DEFLATE = 0x0400 # save using ADOBE DEFLATE compression
|
||||
TIFF_NONE = 0x0800 # save without any compression
|
||||
TIFF_CCITTFAX3 = 0x1000 # save using CCITT Group 3 fax encoding
|
||||
TIFF_CCITTFAX4 = 0x2000 # save using CCITT Group 4 fax encoding
|
||||
TIFF_LZW = 0x4000 # save using LZW compression
|
||||
TIFF_JPEG = 0x8000 # save using JPEG compression
|
||||
TIFF_LOGLUV = 0x10000 # save using LogLuv compression
|
||||
WBMP_DEFAULT = 0
|
||||
XBM_DEFAULT = 0
|
||||
XPM_DEFAULT = 0
|
||||
@@ -329,23 +333,23 @@ class METADATA_MODELS(object):
|
||||
|
||||
|
||||
class METADATA_DATATYPE(object):
|
||||
FIDT_BYTE = 1 # 8-bit unsigned integer
|
||||
FIDT_ASCII = 2 # 8-bit bytes w/ last byte null
|
||||
FIDT_SHORT = 3 # 16-bit unsigned integer
|
||||
FIDT_LONG = 4 # 32-bit unsigned integer
|
||||
FIDT_RATIONAL = 5 # 64-bit unsigned fraction
|
||||
FIDT_SBYTE = 6 # 8-bit signed integer
|
||||
FIDT_UNDEFINED = 7 # 8-bit untyped data
|
||||
FIDT_SSHORT = 8 # 16-bit signed integer
|
||||
FIDT_SLONG = 9 # 32-bit signed integer
|
||||
FIDT_SRATIONAL = 10 # 64-bit signed fraction
|
||||
FIDT_FLOAT = 11 # 32-bit IEEE floating point
|
||||
FIDT_DOUBLE = 12 # 64-bit IEEE floating point
|
||||
FIDT_IFD = 13 # 32-bit unsigned integer (offset)
|
||||
FIDT_PALETTE = 14 # 32-bit RGBQUAD
|
||||
FIDT_LONG8 = 16 # 64-bit unsigned integer
|
||||
FIDT_SLONG8 = 17 # 64-bit signed integer
|
||||
FIDT_IFD8 = 18 # 64-bit unsigned integer (offset)
|
||||
FIDT_BYTE = 1 # 8-bit unsigned integer
|
||||
FIDT_ASCII = 2 # 8-bit bytes w/ last byte null
|
||||
FIDT_SHORT = 3 # 16-bit unsigned integer
|
||||
FIDT_LONG = 4 # 32-bit unsigned integer
|
||||
FIDT_RATIONAL = 5 # 64-bit unsigned fraction
|
||||
FIDT_SBYTE = 6 # 8-bit signed integer
|
||||
FIDT_UNDEFINED = 7 # 8-bit untyped data
|
||||
FIDT_SSHORT = 8 # 16-bit signed integer
|
||||
FIDT_SLONG = 9 # 32-bit signed integer
|
||||
FIDT_SRATIONAL = 10 # 64-bit signed fraction
|
||||
FIDT_FLOAT = 11 # 32-bit IEEE floating point
|
||||
FIDT_DOUBLE = 12 # 64-bit IEEE floating point
|
||||
FIDT_IFD = 13 # 32-bit unsigned integer (offset)
|
||||
FIDT_PALETTE = 14 # 32-bit RGBQUAD
|
||||
FIDT_LONG8 = 16 # 64-bit unsigned integer
|
||||
FIDT_SLONG8 = 17 # 64-bit signed integer
|
||||
FIDT_IFD8 = 18 # 64-bit unsigned integer (offset)
|
||||
|
||||
dtypes = {
|
||||
FIDT_BYTE: numpy.uint8,
|
||||
@@ -384,6 +388,7 @@ def _process_bitmap(filename, flags, process_func):
|
||||
finally:
|
||||
_FI.FreeImage_Unload(bitmap)
|
||||
|
||||
|
||||
def read(filename, flags=0):
|
||||
"""Read an image to a numpy array of shape (height, width) for
|
||||
greyscale images, or shape (height, width, nchannels) for RGB or
|
||||
@@ -394,6 +399,7 @@ def read(filename, flags=0):
|
||||
"""
|
||||
return _process_bitmap(filename, flags, _array_from_bitmap)
|
||||
|
||||
|
||||
def read_metadata(filename):
|
||||
"""Return a dict containing all image metadata.
|
||||
|
||||
@@ -404,6 +410,7 @@ def read_metadata(filename):
|
||||
flags = IO_FLAGS.FIF_LOAD_NOPIXELS
|
||||
return _process_bitmap(filename, flags, _read_metadata)
|
||||
|
||||
|
||||
def _process_multipage(filename, flags, process_func):
|
||||
filename = asbytes(filename)
|
||||
ftype = _FI.FreeImage_GetFileType(filename, 0)
|
||||
@@ -435,6 +442,7 @@ def _process_multipage(filename, flags, process_func):
|
||||
finally:
|
||||
_FI.FreeImage_CloseMultiBitmap(multibitmap, 0)
|
||||
|
||||
|
||||
def read_multipage(filename, flags=0):
|
||||
"""Read a multipage image to a list of numpy arrays, where each
|
||||
array is of shape (height, width) for greyscale images, or shape
|
||||
@@ -445,6 +453,7 @@ def read_multipage(filename, flags=0):
|
||||
"""
|
||||
return _process_multipage(filename, flags, _array_from_bitmap)
|
||||
|
||||
|
||||
def read_multipage_metadata(filename):
|
||||
"""Read a multipage image to a list of metadata dicts, one dict for each
|
||||
page. The dict format is as in read_metadata().
|
||||
@@ -452,26 +461,28 @@ def read_multipage_metadata(filename):
|
||||
flags = IO_FLAGS.FIF_LOAD_NOPIXELS
|
||||
return _process_multipage(filename, flags, _read_metadata)
|
||||
|
||||
|
||||
def _wrap_bitmap_bits_in_array(bitmap, shape, dtype):
|
||||
"""Return an ndarray view on the data in a FreeImage bitmap. Only
|
||||
valid for as long as the bitmap is loaded (if single page) / locked
|
||||
in memory (if multipage).
|
||||
"""Return an ndarray view on the data in a FreeImage bitmap. Only
|
||||
valid for as long as the bitmap is loaded (if single page) / locked
|
||||
in memory (if multipage).
|
||||
|
||||
"""
|
||||
pitch = _FI.FreeImage_GetPitch(bitmap)
|
||||
height = shape[-1]
|
||||
byte_size = height * pitch
|
||||
itemsize = dtype.itemsize
|
||||
"""
|
||||
pitch = _FI.FreeImage_GetPitch(bitmap)
|
||||
height = shape[-1]
|
||||
byte_size = height * pitch
|
||||
itemsize = dtype.itemsize
|
||||
|
||||
if len(shape) == 3:
|
||||
strides = (itemsize, shape[0] * itemsize, pitch)
|
||||
else:
|
||||
strides = (itemsize, pitch)
|
||||
bits = _FI.FreeImage_GetBits(bitmap)
|
||||
array = numpy.ndarray(shape, dtype=dtype,
|
||||
buffer=(ctypes.c_char * byte_size).from_address(bits),
|
||||
strides=strides)
|
||||
return array
|
||||
|
||||
if len(shape) == 3:
|
||||
strides = (itemsize, shape[0]*itemsize, pitch)
|
||||
else:
|
||||
strides = (itemsize, pitch)
|
||||
bits = _FI.FreeImage_GetBits(bitmap)
|
||||
array = numpy.ndarray(shape, dtype=dtype,
|
||||
buffer=(ctypes.c_char*byte_size).from_address(bits),
|
||||
strides=strides)
|
||||
return array
|
||||
|
||||
def _array_from_bitmap(bitmap):
|
||||
"""Convert a FreeImage bitmap pointer to a numpy array.
|
||||
@@ -482,6 +493,7 @@ def _array_from_bitmap(bitmap):
|
||||
# swizzle the color components and flip the scanlines to go from
|
||||
# FreeImage's BGR[A] and upside-down internal memory format to something
|
||||
# more normal
|
||||
|
||||
def n(arr):
|
||||
return arr[..., ::-1].T
|
||||
if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \
|
||||
@@ -490,10 +502,10 @@ def _array_from_bitmap(bitmap):
|
||||
g = n(array[1])
|
||||
r = n(array[2])
|
||||
if shape[0] == 3:
|
||||
return numpy.dstack( (r, g, b) )
|
||||
return numpy.dstack((r, g, b))
|
||||
elif shape[0] == 4:
|
||||
a = n(array[3])
|
||||
return numpy.dstack( (r, g, b, a) )
|
||||
return numpy.dstack((r, g, b, a))
|
||||
else:
|
||||
raise ValueError('Cannot handle images of shape %s' % shape)
|
||||
|
||||
@@ -501,6 +513,7 @@ def _array_from_bitmap(bitmap):
|
||||
# after bitmap is freed.
|
||||
return n(array).copy()
|
||||
|
||||
|
||||
def _read_metadata(bitmap):
|
||||
metadata = {}
|
||||
models = [(name[5:], number) for name, number in
|
||||
@@ -531,6 +544,7 @@ def _read_metadata(bitmap):
|
||||
_FI.FreeImage_FindCloseMetadata(mdhandle)
|
||||
return metadata
|
||||
|
||||
|
||||
def write(array, filename, flags=0):
|
||||
"""Write a (height, width) or (height, width, nchannels) array to
|
||||
a greyscale, RGB, or RGBA image, with file type deduced from the
|
||||
@@ -558,7 +572,8 @@ def write(array, filename, flags=0):
|
||||
if not res:
|
||||
raise RuntimeError('Could not save image properly.')
|
||||
finally:
|
||||
_FI.FreeImage_Unload(bitmap)
|
||||
_FI.FreeImage_Unload(bitmap)
|
||||
|
||||
|
||||
def write_multipage(arrays, filename, flags=0):
|
||||
"""Write a list of (height, width) or (height, width, nchannels)
|
||||
@@ -592,19 +607,20 @@ def write_multipage(arrays, filename, flags=0):
|
||||
# 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255
|
||||
_GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32)
|
||||
|
||||
|
||||
def _array_to_bitmap(array):
|
||||
"""Allocate a FreeImage bitmap and copy a numpy array into it.
|
||||
|
||||
"""
|
||||
shape = array.shape
|
||||
dtype = array.dtype
|
||||
r,c = shape[:2]
|
||||
r, c = shape[:2]
|
||||
if len(shape) == 2:
|
||||
n_channels = 1
|
||||
w_shape = (c,r)
|
||||
w_shape = (c, r)
|
||||
elif len(shape) == 3:
|
||||
n_channels = shape[2]
|
||||
w_shape = (n_channels,c,r)
|
||||
w_shape = (n_channels, c, r)
|
||||
else:
|
||||
n_channels = shape[0]
|
||||
try:
|
||||
@@ -619,18 +635,18 @@ def _array_to_bitmap(array):
|
||||
if not bitmap:
|
||||
raise RuntimeError('Could not allocate image for storage')
|
||||
try:
|
||||
def n(arr): # normalise to freeimage's in-memory format
|
||||
return arr.T[:,::-1]
|
||||
def n(arr): # normalise to freeimage's in-memory format
|
||||
return arr.T[:, ::-1]
|
||||
wrapped_array = _wrap_bitmap_bits_in_array(bitmap, w_shape, dtype)
|
||||
# swizzle the color components and flip the scanlines to go to
|
||||
# FreeImage's BGR[A] and upside-down internal memory format
|
||||
if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \
|
||||
dtype.type == numpy.uint8:
|
||||
wrapped_array[0] = n(array[:,:,2])
|
||||
wrapped_array[1] = n(array[:,:,1])
|
||||
wrapped_array[2] = n(array[:,:,0])
|
||||
wrapped_array[0] = n(array[:, :, 2])
|
||||
wrapped_array[1] = n(array[:, :, 1])
|
||||
wrapped_array[2] = n(array[:, :, 0])
|
||||
if shape[2] == 4:
|
||||
wrapped_array[3] = n(array[:,:,3])
|
||||
wrapped_array[3] = n(array[:, :, 3])
|
||||
else:
|
||||
wrapped_array[:] = n(array)
|
||||
if len(shape) == 2 and dtype.type == numpy.uint8:
|
||||
@@ -641,8 +657,8 @@ def _array_to_bitmap(array):
|
||||
ctypes.memmove(palette, _GREY_PALETTE.ctypes.data, 1024)
|
||||
return bitmap, fi_type
|
||||
except:
|
||||
_FI.FreeImage_Unload(bitmap)
|
||||
raise
|
||||
_FI.FreeImage_Unload(bitmap)
|
||||
raise
|
||||
|
||||
|
||||
def imread(filename):
|
||||
@@ -661,6 +677,7 @@ def imread(filename):
|
||||
img = read(filename)
|
||||
return img
|
||||
|
||||
|
||||
def imsave(filename, img):
|
||||
'''
|
||||
imsave(filename, img)
|
||||
|
||||
@@ -9,6 +9,7 @@ except ImportError:
|
||||
"Please refer to http://www.gdal.org/ "
|
||||
"for further instructions.")
|
||||
|
||||
|
||||
def imread(fname, dtype=None):
|
||||
"""Load an image from file.
|
||||
|
||||
@@ -16,4 +17,3 @@ def imread(fname, dtype=None):
|
||||
ds = gdal.Open(fname)
|
||||
|
||||
return ds.ReadAsArray().astype(dtype)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def imshow(*args, **kwargs):
|
||||
kwargs.setdefault('interpolation', 'nearest')
|
||||
kwargs.setdefault('cmap', 'gray')
|
||||
@@ -8,5 +9,6 @@ def imshow(*args, **kwargs):
|
||||
imread = plt.imread
|
||||
show = plt.show
|
||||
|
||||
|
||||
def _app_show():
|
||||
show()
|
||||
|
||||
@@ -11,6 +11,7 @@ except ImportError:
|
||||
|
||||
from skimage.util import img_as_ubyte
|
||||
|
||||
|
||||
def imread(fname, dtype=None):
|
||||
"""Load an image from file.
|
||||
|
||||
@@ -33,6 +34,7 @@ def imread(fname, dtype=None):
|
||||
|
||||
return np.array(im, dtype=dtype)
|
||||
|
||||
|
||||
def _palette_is_grayscale(pil_image):
|
||||
"""Return True if PIL image in palette mode is grayscale.
|
||||
|
||||
@@ -56,6 +58,7 @@ def _palette_is_grayscale(pil_image):
|
||||
# are all zero.
|
||||
return np.allclose(np.diff(valid_palette), 0)
|
||||
|
||||
|
||||
def imsave(fname, arr):
|
||||
"""Save an image to disk.
|
||||
|
||||
@@ -100,6 +103,7 @@ def imsave(fname, arr):
|
||||
img = Image.fromstring(mode, (arr.shape[1], arr.shape[0]), arr.tostring())
|
||||
img.save(fname)
|
||||
|
||||
|
||||
def imshow(arr):
|
||||
"""Display an image, using PIL's default display command.
|
||||
|
||||
@@ -112,5 +116,6 @@ def imshow(arr):
|
||||
"""
|
||||
Image.fromarray(img_as_ubyte(arr)).show()
|
||||
|
||||
|
||||
def _app_show():
|
||||
pass
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
__all__ = ['use', 'available', 'call', 'info', 'configuration', 'reset_plugins']
|
||||
|
||||
import warnings
|
||||
from ConfigParser import ConfigParser
|
||||
import os.path
|
||||
from glob import glob
|
||||
@@ -15,8 +14,9 @@ plugin_provides = {}
|
||||
plugin_module_name = {}
|
||||
plugin_meta_data = {}
|
||||
|
||||
|
||||
def reset_plugins():
|
||||
"""Clear the plugin state to the default, i.e., where no plugins are loaded.
|
||||
"""Clear the plugin state to the default, i.e., where no plugins are loaded
|
||||
|
||||
"""
|
||||
global plugin_store
|
||||
@@ -28,6 +28,7 @@ def reset_plugins():
|
||||
|
||||
reset_plugins()
|
||||
|
||||
|
||||
def _scan_plugins():
|
||||
"""Scan the plugins directory for .ini files and parse them
|
||||
to gather plugin meta-data.
|
||||
@@ -59,6 +60,7 @@ def _scan_plugins():
|
||||
|
||||
_scan_plugins()
|
||||
|
||||
|
||||
def call(kind, *args, **kwargs):
|
||||
"""Find the appropriate plugin of 'kind' and execute it.
|
||||
|
||||
@@ -90,13 +92,14 @@ command. A list of all available plugins can be found using
|
||||
else:
|
||||
_load(plugin)
|
||||
try:
|
||||
func = [f for (p,f) in plugin_funcs if p == plugin][0]
|
||||
func = [f for (p, f) in plugin_funcs if p == plugin][0]
|
||||
except IndexError:
|
||||
raise RuntimeError('Could not find the plugin "%s" for %s.' % \
|
||||
(plugin, kind))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
def use(name, kind=None):
|
||||
"""Set the default plugin for a specified operation. The plugin
|
||||
will be loaded if it hasn't been already.
|
||||
@@ -149,6 +152,7 @@ def use(name, kind=None):
|
||||
|
||||
plugin_store[k] = funcs
|
||||
|
||||
|
||||
def available(loaded=False):
|
||||
"""List available plugins.
|
||||
|
||||
@@ -178,6 +182,7 @@ def available(loaded=False):
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def _load(plugin):
|
||||
"""Load the given plugin.
|
||||
|
||||
@@ -211,6 +216,7 @@ def _load(plugin):
|
||||
if not (plugin, func) in store:
|
||||
store.append((plugin, func))
|
||||
|
||||
|
||||
def info(plugin):
|
||||
"""Return plugin meta-data.
|
||||
|
||||
@@ -230,6 +236,7 @@ def info(plugin):
|
||||
except KeyError:
|
||||
raise ValueError('No information on plugin "%s"' % plugin)
|
||||
|
||||
|
||||
def configuration():
|
||||
"""Return the currently preferred plugin order.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# the module for the qt color_mixer plugin
|
||||
from PyQt4 import QtGui, QtCore
|
||||
from PyQt4.QtGui import (QWidget, QStackedWidget, QSlider, QVBoxLayout,
|
||||
QGridLayout, QLabel)
|
||||
from PyQt4.QtGui import (QWidget, QStackedWidget, QSlider, QGridLayout, QLabel)
|
||||
|
||||
from util import ColorMixer
|
||||
|
||||
@@ -36,7 +35,8 @@ class IntelligentSlider(QWidget):
|
||||
self.name_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
self.value_label = QLabel()
|
||||
self.value_label.setText('%2.2f' % (self.slider.value() * self.a + self.b))
|
||||
self.value_label.setText('%2.2f' % (self.slider.value() * self.a
|
||||
+ self.b))
|
||||
self.value_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
self.layout = QGridLayout(self)
|
||||
@@ -120,11 +120,9 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.rgb_widget.layout.addWidget(self.gs, 2, 1)
|
||||
self.rgb_widget.layout.addWidget(self.bs, 2, 2)
|
||||
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# HSV sliders
|
||||
#---------------------------------------------------------------
|
||||
|
||||
# radio buttons
|
||||
self.hsv_add = QtGui.QRadioButton('Additive')
|
||||
self.hsv_mul = QtGui.QRadioButton('Multiplicative')
|
||||
@@ -147,11 +145,9 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.hsv_widget.layout.addWidget(self.ss, 2, 1)
|
||||
self.hsv_widget.layout.addWidget(self.vs, 2, 2)
|
||||
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Brightness/Contrast sliders
|
||||
#---------------------------------------------------------------
|
||||
|
||||
# sliders
|
||||
cont = IntelligentSlider('x', 0.002, 0, self.bright_changed)
|
||||
bright = IntelligentSlider('+', 0.51, -255, self.bright_changed)
|
||||
@@ -164,10 +160,9 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.bright_widget.layout.addWidget(self.cont, 0, 0)
|
||||
self.bright_widget.layout.addWidget(self.bright, 0, 1)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------
|
||||
# Gamma Slider
|
||||
#-----------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------
|
||||
gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed)
|
||||
self.gamma = gamma
|
||||
|
||||
@@ -176,11 +171,9 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.gamma_widget.layout = QtGui.QGridLayout(self.gamma_widget)
|
||||
self.gamma_widget.layout.addWidget(self.gamma, 0, 0)
|
||||
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Sigmoid Gamma sliders
|
||||
#---------------------------------------------------------------
|
||||
|
||||
# sliders
|
||||
alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed)
|
||||
beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed)
|
||||
@@ -225,7 +218,6 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.rgb_mul.setChecked(True)
|
||||
self.hsv_mul.setChecked(True)
|
||||
|
||||
|
||||
def set_callback(self, callback):
|
||||
self.callback = callback
|
||||
|
||||
@@ -281,7 +273,6 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.a_gamma.set_value(1)
|
||||
self.b_gamma.set_value(0.5)
|
||||
|
||||
|
||||
def rgb_changed(self, name, val):
|
||||
if name == 'R':
|
||||
channel = self.mixer.RED
|
||||
@@ -346,4 +337,4 @@ class MixerPanel(QtGui.QFrame):
|
||||
self.reset_sliders()
|
||||
|
||||
if self.callback:
|
||||
self.callback()
|
||||
self.callback()
|
||||
|
||||
@@ -39,7 +39,7 @@ class ColorHistogram(QWidget):
|
||||
orig_height = self.height()
|
||||
|
||||
# fill perc % of the widget
|
||||
perc = 1
|
||||
perc = 1
|
||||
width = int(orig_width * perc)
|
||||
height = int(orig_height * perc)
|
||||
|
||||
@@ -60,14 +60,14 @@ class ColorHistogram(QWidget):
|
||||
remainder = width % nbars
|
||||
bar_width = [int(width / nbars)] * nbars
|
||||
for i in range(remainder):
|
||||
bar_width[i]+=1
|
||||
bar_width[i] += 1
|
||||
|
||||
paint = QPainter()
|
||||
paint.begin(self)
|
||||
|
||||
# determine the scaling factor
|
||||
max_val = np.max(self.counts)
|
||||
scale = 1. * height / max_val
|
||||
scale = 1. * height / max_val
|
||||
|
||||
# determine if we have a colormap and drop into the appopriate
|
||||
# loop.
|
||||
@@ -95,7 +95,6 @@ class ColorHistogram(QWidget):
|
||||
|
||||
paint.end()
|
||||
|
||||
|
||||
def update_hist(self, counts, cmap):
|
||||
self._validate_input(counts, cmap)
|
||||
self.counts = counts
|
||||
@@ -121,17 +120,17 @@ class QuadHistogram(QFrame):
|
||||
self.b_hist = ColorHistogram(b, (0, 0, 255))
|
||||
self.v_hist = ColorHistogram(v, (0, 0, 0))
|
||||
|
||||
self.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
|
||||
self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
|
||||
self.layout = QGridLayout(self)
|
||||
self.layout.setMargin(0)
|
||||
|
||||
order_map = {'R': self.r_hist, 'G': self.g_hist, 'B': self.b_hist,
|
||||
'V': self.v_hist}
|
||||
|
||||
if layout=='vertical':
|
||||
if layout == 'vertical':
|
||||
for i in range(len(order)):
|
||||
self.layout.addWidget(order_map[order[i]], i, 0)
|
||||
elif layout=='horizontal':
|
||||
elif layout == 'horizontal':
|
||||
for i in range(len(order)):
|
||||
self.layout.addWidget(order_map[order[i]], 0, i)
|
||||
|
||||
@@ -140,4 +139,4 @@ class QuadHistogram(QFrame):
|
||||
self.r_hist.update_hist(r, (255, 0, 0))
|
||||
self.g_hist.update_hist(g, (0, 255, 0))
|
||||
self.b_hist.update_hist(b, (0, 0, 255))
|
||||
self.v_hist.update_hist(v, (0, 0, 0))
|
||||
self.v_hist.update_hist(v, (0, 0, 0))
|
||||
|
||||
@@ -69,7 +69,7 @@ class ImageLabel(QLabel):
|
||||
class RGBHSVDisplay(QFrame):
|
||||
def __init__(self):
|
||||
QFrame.__init__(self)
|
||||
self.setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Sunken)
|
||||
self.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Sunken)
|
||||
|
||||
self.posx_label = QLabel('X-pos:')
|
||||
self.posx_value = QLabel()
|
||||
@@ -118,7 +118,6 @@ class RGBHSVDisplay(QFrame):
|
||||
self.v_value.setText(str(v)[:5])
|
||||
|
||||
|
||||
|
||||
class SkiviImageWindow(QMainWindow):
|
||||
def __init__(self, arr, mgr):
|
||||
QMainWindow.__init__(self)
|
||||
@@ -132,7 +131,8 @@ class SkiviImageWindow(QMainWindow):
|
||||
|
||||
self.label = ImageLabel(self, arr)
|
||||
self.label_container = QFrame()
|
||||
self.label_container.setFrameShape(QtGui.QFrame.StyledPanel|QtGui.QFrame.Sunken)
|
||||
self.label_container.setFrameShape(
|
||||
QtGui.QFrame.StyledPanel | QtGui.QFrame.Sunken)
|
||||
self.label_container.setLineWidth(1)
|
||||
|
||||
self.label_container.layout = QtGui.QGridLayout(self.label_container)
|
||||
@@ -171,7 +171,6 @@ class SkiviImageWindow(QMainWindow):
|
||||
self.layout.addWidget(self.save_stack, 1, 1)
|
||||
self.layout.addWidget(self.save_file, 1, 2)
|
||||
|
||||
|
||||
def closeEvent(self, event):
|
||||
# Allow window to be destroyed by removing any
|
||||
# references to it
|
||||
@@ -206,14 +205,13 @@ class SkiviImageWindow(QMainWindow):
|
||||
if x >= maxw or y >= maxh or x < 0 or y < 0:
|
||||
r = g = b = h = s = v = ''
|
||||
else:
|
||||
r = self.arr[y,x,0]
|
||||
g = self.arr[y,x,1]
|
||||
b = self.arr[y,x,2]
|
||||
r = self.arr[y, x, 0]
|
||||
g = self.arr[y, x, 1]
|
||||
b = self.arr[y, x, 2]
|
||||
h, s, v = self.mixer_panel.mixer.rgb_2_hsv_pixel(r, g, b)
|
||||
|
||||
self.rgb_hsv_disp.update_vals((x, y, r, g, b, h, s, v))
|
||||
|
||||
|
||||
def save_to_stack(self):
|
||||
from skimage import io
|
||||
img = self.arr.copy()
|
||||
@@ -238,5 +236,3 @@ class SkiviImageWindow(QMainWindow):
|
||||
if len(filename) == 0:
|
||||
return
|
||||
io.imsave(filename, self.arr)
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
# This mock-up is called by ../tests/test_plugin.py
|
||||
# to verify the behaviour of the plugin infrastructure
|
||||
|
||||
|
||||
def imread(fname, dtype=None):
|
||||
assert fname == 'test.png'
|
||||
assert dtype == 'i4'
|
||||
|
||||
|
||||
def imsave(fname, arr):
|
||||
assert fname == 'test.png'
|
||||
assert arr == [1, 2, 3]
|
||||
|
||||
|
||||
def imshow(arr, plugin_arg=None):
|
||||
assert arr == [1, 2, 3]
|
||||
assert plugin_arg == (1, 2)
|
||||
|
||||
|
||||
def imread_collection(x, conserve_memory=True):
|
||||
assert conserve_memory == False
|
||||
assert x == '*.png'
|
||||
|
||||
+21
-25
@@ -12,6 +12,7 @@ try:
|
||||
except:
|
||||
CPU_COUNT = 2
|
||||
|
||||
|
||||
class GuiLockError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
@@ -19,6 +20,7 @@ class GuiLockError(Exception):
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
class WindowManager(object):
|
||||
''' A class to keep track of spawned windows,
|
||||
and make any needed callback once all the windows,
|
||||
@@ -62,7 +64,8 @@ class WindowManager(object):
|
||||
self._gui_lock = False
|
||||
self._guikit = ''
|
||||
else:
|
||||
raise RuntimeError('Only the toolkit that owns the lock may release it')
|
||||
raise RuntimeError('Only the toolkit that owns the lock may '
|
||||
'release it')
|
||||
|
||||
def add_window(self, win):
|
||||
self._check_locked()
|
||||
@@ -138,13 +141,13 @@ def prepare_for_display(npy_img):
|
||||
if npy_img.ndim == 2 or \
|
||||
(npy_img.ndim == 3 and npy_img.shape[2] == 1):
|
||||
npy_plane = npy_img.reshape((height, width))
|
||||
out[:,:,0] = npy_plane
|
||||
out[:,:,1] = npy_plane
|
||||
out[:,:,2] = npy_plane
|
||||
out[:, :, 0] = npy_plane
|
||||
out[:, :, 1] = npy_plane
|
||||
out[:, :, 2] = npy_plane
|
||||
|
||||
elif npy_img.ndim == 3:
|
||||
if npy_img.shape[2] == 3 or npy_img.shape[2] == 4:
|
||||
out[:,:,:3] = npy_img[:,:,:3]
|
||||
out[:, :, :3] = npy_img[:, :, :3]
|
||||
else:
|
||||
raise ValueError('Image must have 1, 3, or 4 channels')
|
||||
|
||||
@@ -184,10 +187,10 @@ class ImgThread(threading.Thread):
|
||||
def run(self):
|
||||
self.func(*self.args)
|
||||
|
||||
|
||||
class ThreadDispatch(object):
|
||||
def __init__(self, img, stateimg, func, *args):
|
||||
|
||||
width = img.shape[1]
|
||||
height = img.shape[0]
|
||||
self.cores = CPU_COUNT
|
||||
self.threads = []
|
||||
@@ -197,21 +200,21 @@ class ThreadDispatch(object):
|
||||
self.chunks.append((img, stateimg))
|
||||
|
||||
elif self.cores >= 4:
|
||||
self.chunks.append((img[:(height/4), :, :],
|
||||
stateimg[:(height/4), :, :]))
|
||||
self.chunks.append((img[(height/4):(height/2), :, :],
|
||||
stateimg[(height/4):(height/2), :, :]))
|
||||
self.chunks.append((img[(height/2):(3*height/4), :, :],
|
||||
stateimg[(height/2):(3*height/4), :, :]))
|
||||
self.chunks.append((img[(3*height/4):, :, :],
|
||||
stateimg[(3*height/4):, :, :]))
|
||||
self.chunks.append((img[:(height / 4), :, :],
|
||||
stateimg[:(height / 4), :, :]))
|
||||
self.chunks.append((img[(height / 4):(height / 2), :, :],
|
||||
stateimg[(height / 4):(height / 2), :, :]))
|
||||
self.chunks.append((img[(height / 2):(3 * height / 4), :, :],
|
||||
stateimg[(height / 2):(3 * height / 4), :, :]))
|
||||
self.chunks.append((img[(3 * height / 4):, :, :],
|
||||
stateimg[(3 * height / 4):, :, :]))
|
||||
|
||||
# if they dont have 1, or 4 or more, 2 is good.
|
||||
else:
|
||||
self.chunks.append((img[:(height/2), :, :],
|
||||
stateimg[:(height/2), :, :]))
|
||||
self.chunks.append((img[(height/2):, :, :],
|
||||
stateimg[(height/2):, :, :]))
|
||||
self.chunks.append((img[:(height / 2), :, :],
|
||||
stateimg[:(height / 2), :, :]))
|
||||
self.chunks.append((img[(height / 2):, :, :],
|
||||
stateimg[(height / 2):, :, :]))
|
||||
|
||||
for i in range(len(self.chunks)):
|
||||
self.threads.append(ImgThread(func, self.chunks[i][0],
|
||||
@@ -224,7 +227,6 @@ class ThreadDispatch(object):
|
||||
t.join()
|
||||
|
||||
|
||||
|
||||
class ColorMixer(object):
|
||||
''' a class to manage mixing colors in an image.
|
||||
The input array must be an RGB uint8 image.
|
||||
@@ -300,8 +302,6 @@ class ColorMixer(object):
|
||||
_colormixer.add, channel, ammount)
|
||||
pool.run()
|
||||
|
||||
|
||||
|
||||
def multiply(self, channel, ammount):
|
||||
'''Mutliply the indicated channel by the specified value.
|
||||
|
||||
@@ -320,7 +320,6 @@ class ColorMixer(object):
|
||||
_colormixer.multiply, channel, ammount)
|
||||
pool.run()
|
||||
|
||||
|
||||
def brightness(self, factor, offset):
|
||||
'''Adjust the brightness off an image with an offset and factor.
|
||||
|
||||
@@ -338,13 +337,11 @@ class ColorMixer(object):
|
||||
_colormixer.brightness, factor, offset)
|
||||
pool.run()
|
||||
|
||||
|
||||
def sigmoid_gamma(self, alpha, beta):
|
||||
pool = ThreadDispatch(self.img, self.stateimg,
|
||||
_colormixer.sigmoid_gamma, alpha, beta)
|
||||
pool.run()
|
||||
|
||||
|
||||
def gamma(self, gamma):
|
||||
pool = ThreadDispatch(self.img, self.stateimg,
|
||||
_colormixer.gamma, gamma)
|
||||
@@ -435,4 +432,3 @@ class ColorMixer(object):
|
||||
'''
|
||||
R, G, B = _colormixer.py_hsv_2_rgb(H, S, V)
|
||||
return (R, G, B)
|
||||
|
||||
|
||||
@@ -118,7 +118,8 @@ class MultiImage(object):
|
||||
if -numframes <= n < numframes:
|
||||
n = n % numframes
|
||||
else:
|
||||
raise IndexError("There are only %s frames in the image"%numframes)
|
||||
raise IndexError("There are only %s frames in the image"
|
||||
% numframes)
|
||||
|
||||
if self.conserve_memory:
|
||||
if not self._cached == n:
|
||||
@@ -279,7 +280,8 @@ class ImageCollection(object):
|
||||
if -num <= n < num:
|
||||
n = n % num
|
||||
else:
|
||||
raise IndexError("There are only %s images in the collection"%num)
|
||||
raise IndexError("There are only %s images in the collection"
|
||||
% num)
|
||||
return n
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
+6
-5
@@ -6,6 +6,7 @@ import os.path
|
||||
|
||||
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
|
||||
|
||||
@@ -30,10 +31,10 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Image I/O Routines',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'Modified BSD',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Image I/O Routines',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='Modified BSD',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ __all__ = ['load_sift', 'load_surf']
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _sift_read(f, mode='SIFT'):
|
||||
"""Read SIFT or SURF features from a file.
|
||||
|
||||
@@ -56,9 +57,11 @@ def _sift_read(f, mode='SIFT'):
|
||||
|
||||
return data.view(datatype)
|
||||
|
||||
|
||||
def load_sift(f):
|
||||
return _sift_read(f, mode='SIFT')
|
||||
|
||||
|
||||
def load_surf(f):
|
||||
return _sift_read(f, mode='SURF')
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestImageCollection():
|
||||
def return_img(n):
|
||||
return self.collection[n]
|
||||
assert_raises(IndexError, return_img, num)
|
||||
assert_raises(IndexError, return_img, -num-1)
|
||||
assert_raises(IndexError, return_img, -num - 1)
|
||||
|
||||
def test_files_property(self):
|
||||
assert isinstance(self.collection.files, list)
|
||||
@@ -52,6 +52,7 @@ class TestImageCollection():
|
||||
|
||||
def test_custom_load(self):
|
||||
load_pattern = [(1, 'one'), (2, 'two')]
|
||||
|
||||
def load_fn(x):
|
||||
return x
|
||||
|
||||
@@ -83,7 +84,7 @@ class TestMultiImage():
|
||||
def return_img(n):
|
||||
return self.img[n]
|
||||
assert_raises(IndexError, return_img, num)
|
||||
assert_raises(IndexError, return_img, -num-1)
|
||||
assert_raises(IndexError, return_img, -num - 1)
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_files_property(self):
|
||||
@@ -102,10 +103,5 @@ class TestMultiImage():
|
||||
assert_raises(AttributeError, set_mem, True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import numpy as np
|
||||
|
||||
import skimage.io._plugins._colormixer as cm
|
||||
|
||||
|
||||
class ColorMixerTest(object):
|
||||
def setup(self):
|
||||
self.state = np.ones((18, 33, 3), dtype=np.uint8) * 200
|
||||
@@ -91,7 +92,8 @@ class TestColorMixer(object):
|
||||
def test_gamma(self):
|
||||
gamma = 1.5
|
||||
cm.gamma(self.img, self.state, gamma)
|
||||
img = np.asarray(((self.state/255.)**(1/gamma))*255, dtype='uint8')
|
||||
img = np.asarray(((self.state / 255.)**(1 / gamma)) * 255,
|
||||
dtype='uint8')
|
||||
assert_array_almost_equal(img, self.img)
|
||||
|
||||
def test_rgb_2_hsv(self):
|
||||
@@ -112,7 +114,6 @@ class TestColorMixer(object):
|
||||
assert_almost_equal(np.array([g]), np.array([0]))
|
||||
assert_almost_equal(np.array([b]), np.array([0]))
|
||||
|
||||
|
||||
def test_hsv_add(self):
|
||||
cm.hsv_add(self.img, self.state, 360, 0, 0)
|
||||
assert_almost_equal(self.img, self.state)
|
||||
@@ -123,7 +124,7 @@ class TestColorMixer(object):
|
||||
|
||||
def test_hsv_add_clip_pos(self):
|
||||
cm.hsv_add(self.img, self.state, 0, 0, 1)
|
||||
assert_equal(self.img, np.ones_like(self.state)*255)
|
||||
assert_equal(self.img, np.ones_like(self.state) * 255)
|
||||
|
||||
def test_hsv_mul(self):
|
||||
cm.hsv_multiply(self.img, self.state, 360, 1, 1)
|
||||
@@ -134,7 +135,5 @@ class TestColorMixer(object):
|
||||
assert_equal(self.img, np.zeros_like(self.state))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
@@ -15,6 +15,7 @@ except ImportError:
|
||||
else:
|
||||
import skimage.io._plugins.fits_plugin as fplug
|
||||
|
||||
|
||||
def test_fits_plugin_import():
|
||||
# Make sure we get an import exception if PyFITS isn't there
|
||||
# (not sure how useful this is, but it ensures there isn't some other
|
||||
@@ -36,14 +37,16 @@ def test_imread_MEF():
|
||||
io.use_plugin('fits')
|
||||
testfile = os.path.join(data_dir, 'multi.fits')
|
||||
img = io.imread(testfile)
|
||||
assert np.all(img==pyfits.getdata(testfile, 1))
|
||||
assert np.all(img == pyfits.getdata(testfile, 1))
|
||||
|
||||
|
||||
@skipif(not pyfits_available)
|
||||
def test_imread_simple():
|
||||
io.use_plugin('fits')
|
||||
testfile = os.path.join(data_dir, 'simple.fits')
|
||||
img = io.imread(testfile)
|
||||
assert np.all(img==pyfits.getdata(testfile, 0))
|
||||
assert np.all(img == pyfits.getdata(testfile, 0))
|
||||
|
||||
|
||||
@skipif(not pyfits_available)
|
||||
def test_imread_collection_single_MEF():
|
||||
@@ -54,6 +57,7 @@ def test_imread_collection_single_MEF():
|
||||
load_func=fplug.FITSFactory)
|
||||
assert _same_ImageCollection(ic1, ic2)
|
||||
|
||||
|
||||
@skipif(not pyfits_available)
|
||||
def test_imread_collection_MEF_and_simple():
|
||||
io.use_plugin('fits')
|
||||
@@ -65,6 +69,7 @@ def test_imread_collection_MEF_and_simple():
|
||||
load_func=fplug.FITSFactory)
|
||||
assert _same_ImageCollection(ic1, ic2)
|
||||
|
||||
|
||||
def _same_ImageCollection(collection1, collection2):
|
||||
"""Ancillary function to compare two ImageCollection objects, checking
|
||||
that their constituent arrays are equal.
|
||||
@@ -79,4 +84,3 @@ def _same_ImageCollection(collection1, collection2):
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ def teardown():
|
||||
def test_imread():
|
||||
img = sio.imread(os.path.join(si.data_dir, 'color.png'))
|
||||
assert img.shape == (370, 371, 3)
|
||||
assert all(img[274,135] == [0, 130, 253])
|
||||
assert all(img[274, 135] == [0, 130, 253])
|
||||
|
||||
|
||||
@skipif(not FI_available)
|
||||
def test_imread_uint16():
|
||||
@@ -44,6 +45,7 @@ def test_imread_uint16():
|
||||
assert img.dtype == np.uint16
|
||||
assert_array_almost_equal(img, expected)
|
||||
|
||||
|
||||
@skipif(not FI_available)
|
||||
def test_imread_uint16_big_endian():
|
||||
expected = np.load(os.path.join(si.data_dir, 'chessboard_GRAY_U8.npy'))
|
||||
@@ -54,7 +56,7 @@ def test_imread_uint16_big_endian():
|
||||
|
||||
class TestSave:
|
||||
def roundtrip(self, dtype, x, suffix):
|
||||
f = NamedTemporaryFile(suffix='.'+suffix)
|
||||
f = NamedTemporaryFile(suffix='.' + suffix)
|
||||
fname = f.name
|
||||
f.close()
|
||||
sio.imsave(fname, x)
|
||||
@@ -64,12 +66,12 @@ class TestSave:
|
||||
@skipif(not FI_available)
|
||||
def test_imsave_roundtrip(self):
|
||||
for shape, dtype, format in [
|
||||
[(10, 10), (np.uint8, np.uint16), ('tif', 'png')],
|
||||
[(10, 10), (np.float32,), ('tif',)],
|
||||
[(10, 10, 3), (np.uint8,), ('png',)],
|
||||
[(10, 10), (np.uint8, np.uint16), ('tif', 'png')],
|
||||
[(10, 10), (np.float32,), ('tif',)],
|
||||
[(10, 10, 3), (np.uint8,), ('png',)],
|
||||
[(10, 10, 4), (np.uint8,), ('png',)]
|
||||
]:
|
||||
tests = [(d,f) for d in dtype for f in format]
|
||||
tests = [(d, f) for d in dtype for f in format]
|
||||
for d, f in tests:
|
||||
x = np.ones(shape, dtype=d) * np.random.random(shape)
|
||||
if not np.issubdtype(d, float):
|
||||
@@ -83,7 +85,8 @@ def test_metadata():
|
||||
assert meta[('EXIF_MAIN', 'Orientation')] == 1
|
||||
assert meta[('EXIF_MAIN', 'Software')].startswith('ImageMagick')
|
||||
|
||||
meta = fi.read_multipage_metadata(os.path.join(si.data_dir, 'multipage.tif'))
|
||||
meta = fi.read_multipage_metadata(os.path.join(si.data_dir,
|
||||
'multipage.tif'))
|
||||
assert len(meta) == 2
|
||||
assert meta[0][('EXIF_MAIN', 'Orientation')] == 1
|
||||
assert meta[1][('EXIF_MAIN', 'Software')].startswith('ImageMagick')
|
||||
|
||||
@@ -4,20 +4,21 @@ import numpy as np
|
||||
import skimage.io._plugins._colormixer as cm
|
||||
from skimage.io._plugins._histograms import histograms
|
||||
|
||||
|
||||
class TestHistogram:
|
||||
def test_basic(self):
|
||||
img = np.ones((50, 50, 3), dtype=np.uint8)
|
||||
r, g, b, v = histograms(img, 255)
|
||||
|
||||
for band in (r, g, b, v):
|
||||
yield assert_equal, band.sum(), 50*50
|
||||
yield assert_equal, band.sum(), 50 * 50
|
||||
|
||||
def test_counts(self):
|
||||
channel = np.arange(255).reshape(51, 5)
|
||||
img = np.empty((51, 5, 3), dtype='uint8')
|
||||
img[:,:,0] = channel
|
||||
img[:,:,1] = channel
|
||||
img[:,:,2] = channel
|
||||
img[:, :, 0] = channel
|
||||
img[:, :, 1] = channel
|
||||
img[:, :, 2] = channel
|
||||
r, g, b, v = histograms(img, 255)
|
||||
assert_array_equal(r, g)
|
||||
assert_array_equal(r, b)
|
||||
|
||||
@@ -3,12 +3,14 @@ import numpy as np
|
||||
|
||||
import skimage.io as io
|
||||
|
||||
|
||||
def test_stack_basic():
|
||||
x = np.arange(12).reshape(3, 4)
|
||||
io.push(x)
|
||||
|
||||
assert_array_equal(io.pop(), x)
|
||||
|
||||
|
||||
@raises(ValueError)
|
||||
def test_stack_non_array():
|
||||
io.push([[1, 2, 3]])
|
||||
|
||||
@@ -32,6 +32,7 @@ def setup_module(self):
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_imread_flatten():
|
||||
# a color image is flattened
|
||||
@@ -42,6 +43,7 @@ def test_imread_flatten():
|
||||
# check that flattening does not occur for an image that is grey already.
|
||||
assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
|
||||
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_imread_palette():
|
||||
img = imread(os.path.join(data_dir, 'palette_gray.png'))
|
||||
@@ -49,6 +51,7 @@ def test_imread_palette():
|
||||
img = imread(os.path.join(data_dir, 'palette_color.png'))
|
||||
assert img.ndim == 3
|
||||
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_palette_is_gray():
|
||||
from PIL import Image
|
||||
@@ -57,6 +60,7 @@ def test_palette_is_gray():
|
||||
color = Image.open(os.path.join(data_dir, 'palette_color.png'))
|
||||
assert not _palette_is_grayscale(color)
|
||||
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_bilevel():
|
||||
expected = np.zeros((10, 10))
|
||||
@@ -65,6 +69,7 @@ def test_bilevel():
|
||||
img = imread(os.path.join(data_dir, 'checker_bilevel.png'))
|
||||
assert_array_equal(img, expected)
|
||||
|
||||
|
||||
@skipif(not PIL_available)
|
||||
def test_imread_uint16():
|
||||
expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy'))
|
||||
@@ -72,6 +77,7 @@ def test_imread_uint16():
|
||||
assert np.issubdtype(img.dtype, np.uint16)
|
||||
assert_array_almost_equal(img, expected)
|
||||
|
||||
|
||||
# Big endian images not correctly loaded for PIL < 1.1.7
|
||||
# Renable test when PIL 1.1.7 is more common.
|
||||
@skipif(True)
|
||||
|
||||
@@ -20,11 +20,13 @@ except OSError:
|
||||
|
||||
|
||||
def setup_module(self):
|
||||
plugin.use('test') # see ../_plugins/test_plugin.py
|
||||
plugin.use('test') # see ../_plugins/test_plugin.py
|
||||
|
||||
|
||||
def teardown_module(self):
|
||||
io.reset_plugins()
|
||||
|
||||
|
||||
class TestPlugin:
|
||||
def test_read(self):
|
||||
io.imread('test.png', as_grey=True, dtype='i4', plugin='test')
|
||||
|
||||
@@ -3,6 +3,7 @@ from skimage.io._plugins.util import prepare_for_display, WindowManager
|
||||
from numpy.testing import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
class TestPrepareForDisplay:
|
||||
def test_basic(self):
|
||||
prepare_for_display(np.random.random((10, 10)))
|
||||
@@ -12,7 +13,7 @@ class TestPrepareForDisplay:
|
||||
assert x.dtype == np.dtype(np.uint8)
|
||||
|
||||
def test_grey(self):
|
||||
x = prepare_for_display(np.arange(12, dtype=float).reshape((4,3))/11.)
|
||||
x = prepare_for_display(np.arange(12, dtype=float).reshape((4, 3)) / 11)
|
||||
assert_array_equal(x[..., 0], x[..., 2])
|
||||
assert x[0, 0, 0] == 0
|
||||
assert x[3, 2, 0] == 255
|
||||
@@ -31,6 +32,7 @@ class TestPrepareForDisplay:
|
||||
def test_wrong_depth(self):
|
||||
x = prepare_for_display(np.random.random((10, 10, 5)))
|
||||
|
||||
|
||||
class TestWindowManager:
|
||||
callback_called = False
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import os
|
||||
|
||||
from skimage.io import load_sift, load_surf
|
||||
|
||||
|
||||
def test_load_sift():
|
||||
f = NamedTemporaryFile(delete=False)
|
||||
fname = f.name
|
||||
@@ -40,6 +41,7 @@ def test_load_sift():
|
||||
assert_equal(features['row'][0], 133.92)
|
||||
assert_equal(features['column'][1], 99.75)
|
||||
|
||||
|
||||
def test_load_surf():
|
||||
f = NamedTemporaryFile(delete=False)
|
||||
fname = f.name
|
||||
|
||||
+55
-51
@@ -1,11 +1,12 @@
|
||||
import numpy as np
|
||||
import os, time
|
||||
import os
|
||||
from skimage.io import ImageCollection
|
||||
|
||||
try:
|
||||
import pygst
|
||||
pygst.require("0.10")
|
||||
import gst, gobject
|
||||
import gst
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
from gst.extend.discoverer import Discoverer
|
||||
gstreamer_available = True
|
||||
@@ -36,11 +37,11 @@ class CvVideo(object):
|
||||
self.source = source
|
||||
self.capture = cv.CreateFileCapture(self.source)
|
||||
self.size = size
|
||||
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Retrieve a video frame as a numpy array.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : array (image)
|
||||
@@ -55,31 +56,34 @@ class CvVideo(object):
|
||||
else:
|
||||
cv.Resize(img, cv.fromarray(img_mat))
|
||||
# opencv stores images in BGR format
|
||||
cv.CvtColor(cv.fromarray(img_mat), cv.fromarray(img_mat), cv.CV_BGR2RGB)
|
||||
cv.CvtColor(cv.fromarray(img_mat), cv.fromarray(img_mat),
|
||||
cv.CV_BGR2RGB)
|
||||
return img_mat
|
||||
|
||||
|
||||
def seek_frame(self, frame_number):
|
||||
"""
|
||||
Seek to specified frame in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame_number : int
|
||||
Frame position
|
||||
"""
|
||||
cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_FRAMES, frame_number)
|
||||
|
||||
cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_FRAMES,
|
||||
frame_number)
|
||||
|
||||
def seek_time(self, milliseconds):
|
||||
"""
|
||||
Seek to specified time in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
milliseconds : int
|
||||
Time position
|
||||
"""
|
||||
cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_MSEC, milliseconds)
|
||||
|
||||
cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_POS_MSEC,
|
||||
milliseconds)
|
||||
|
||||
def frame_count(self):
|
||||
"""
|
||||
Returns frame count of video.
|
||||
@@ -90,7 +94,7 @@ class CvVideo(object):
|
||||
Frame count.
|
||||
"""
|
||||
return cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_COUNT)
|
||||
|
||||
|
||||
def duration(self):
|
||||
"""
|
||||
Returns time length of video in milliseconds.
|
||||
@@ -102,8 +106,8 @@ class CvVideo(object):
|
||||
"""
|
||||
return cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS) * \
|
||||
cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_COUNT)
|
||||
|
||||
|
||||
|
||||
|
||||
class GstVideo(object):
|
||||
"""
|
||||
GStreamer-based video loader.
|
||||
@@ -115,9 +119,9 @@ class GstVideo(object):
|
||||
size: tuple, optional
|
||||
Size of returned array.
|
||||
sync: bool, optional (default False)
|
||||
Frames are extracted per frame or per time basis.
|
||||
If enabled the video time step continues onward according to the play rate.
|
||||
Useful for ip cameras and other real time video feeds.
|
||||
Frames are extracted per frame or per time basis. If enabled the video
|
||||
time step continues onward according to the play rate. Useful for ip
|
||||
cameras and other real time video feeds.
|
||||
"""
|
||||
def __init__(self, source=None, size=None, sync=False):
|
||||
if not gstreamer_available:
|
||||
@@ -127,7 +131,7 @@ class GstVideo(object):
|
||||
self.video_length = 0
|
||||
self.video_rate = 0
|
||||
# extract video size
|
||||
if not size:
|
||||
if not size:
|
||||
gobject.idle_add(self._discover_one)
|
||||
self.mainloop = gobject.MainLoop()
|
||||
self.mainloop.run()
|
||||
@@ -143,7 +147,7 @@ class GstVideo(object):
|
||||
"""
|
||||
discoverer = Discoverer(self.source)
|
||||
discoverer.connect('discovered', self._discovered)
|
||||
discoverer.discover()
|
||||
discoverer.discover()
|
||||
return False
|
||||
|
||||
def _discovered(self, d, is_media):
|
||||
@@ -152,13 +156,13 @@ class GstVideo(object):
|
||||
"""
|
||||
if is_media:
|
||||
self.size = (d.videowidth, d.videoheight)
|
||||
self.video_length = d.videolength / gst.MSECOND
|
||||
self.video_length = d.videolength / gst.MSECOND
|
||||
self.video_rate = d.videorate.num
|
||||
self.mainloop.quit()
|
||||
return False
|
||||
|
||||
|
||||
def _create_main_pipeline(self, source, size, sync):
|
||||
"""
|
||||
"""
|
||||
Create the frame extraction pipeline.
|
||||
"""
|
||||
pipeline_string = "uridecodebin name=decoder uri=%s ! ffmpegcolorspace ! videoscale ! appsink name=play_sink" % self.source
|
||||
@@ -173,42 +177,43 @@ class GstVideo(object):
|
||||
self.appsink.set_property('caps', gst.caps_from_string(caps))
|
||||
if self.pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE:
|
||||
raise NameError("Failed to load video source %s" % self.source)
|
||||
buff = self.appsink.emit('pull-preroll')
|
||||
|
||||
self.appsink.emit('pull-preroll')
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Retrieve a video frame as a numpy array.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : array (image)
|
||||
Retrieved image.
|
||||
"""
|
||||
buff = self.appsink.emit('pull-buffer')
|
||||
img_mat = np.ndarray(shape=(self.size[1], self.size[0], 3), dtype=np.uint8, buffer=buff.data)
|
||||
img_mat = np.ndarray(shape=(self.size[1], self.size[0], 3),
|
||||
dtype=np.uint8, buffer=buff.data)
|
||||
return img_mat
|
||||
|
||||
def seek_frame(self, frame_number):
|
||||
"""
|
||||
Seek to specified frame in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame_number : int
|
||||
Frame position
|
||||
"""
|
||||
self.pipeline.seek_simple(gst.FORMAT_DEFAULT, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, frame_number)
|
||||
|
||||
|
||||
def seek_time(self, milliseconds):
|
||||
"""
|
||||
Seek to specified time in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
milliseconds : int
|
||||
Time position
|
||||
"""
|
||||
self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, milliseconds/1000.0 * gst.SECOND)
|
||||
self.pipeline.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT, milliseconds / 1000.0 * gst.SECOND)
|
||||
|
||||
def frame_count(self):
|
||||
"""
|
||||
@@ -219,8 +224,8 @@ class GstVideo(object):
|
||||
output : int
|
||||
Frame count.
|
||||
"""
|
||||
return self.video_length/1000*self.video_rate
|
||||
|
||||
return self.video_length / 1000 * self.video_rate
|
||||
|
||||
def duration(self):
|
||||
"""
|
||||
Returns time length of video in milliseconds.
|
||||
@@ -236,7 +241,7 @@ class GstVideo(object):
|
||||
class Video(object):
|
||||
"""
|
||||
Video loader. Supports Opencv and Gstreamer backends.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
source : str
|
||||
@@ -248,7 +253,7 @@ class Video(object):
|
||||
If enabled the video time step continues onward according to the play rate.
|
||||
Useful for IP cameras and other real time video feeds.
|
||||
backend: str, 'gstreamer' or 'opencv'
|
||||
Backend to use.
|
||||
Backend to use.
|
||||
"""
|
||||
def __init__(self, source=None, size=None, sync=False, backend=None):
|
||||
if backend == None:
|
||||
@@ -270,29 +275,29 @@ class Video(object):
|
||||
def get(self):
|
||||
"""
|
||||
Retrieve the next video frame as a numpy array.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : array (image)
|
||||
Retrieved image.
|
||||
"""
|
||||
return self.video.get()
|
||||
|
||||
|
||||
def seek_frame(self, frame_number):
|
||||
"""
|
||||
Seek to specified frame in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame_number : int
|
||||
Frame position
|
||||
"""
|
||||
self.video.seek_frame(frame_number)
|
||||
|
||||
|
||||
def seek_time(self, milliseconds):
|
||||
"""
|
||||
Seek to specified time in video.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
milliseconds : int
|
||||
@@ -310,7 +315,7 @@ class Video(object):
|
||||
Frame count.
|
||||
"""
|
||||
return self.video.frame_count()
|
||||
|
||||
|
||||
def duration(self):
|
||||
"""
|
||||
Returns time length of video in milliseconds.
|
||||
@@ -321,11 +326,11 @@ class Video(object):
|
||||
Time length [ms].
|
||||
"""
|
||||
return self.video.duration()
|
||||
|
||||
|
||||
def get_index_frame(self, frame_number):
|
||||
"""
|
||||
Retrieve a specified video frame as a numpy array.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame_number : int
|
||||
@@ -335,28 +340,27 @@ class Video(object):
|
||||
-------
|
||||
output : array (image)
|
||||
Retrieved image.
|
||||
"""
|
||||
"""
|
||||
self.video.seek_frame(frame_number)
|
||||
return self.video.get()
|
||||
|
||||
|
||||
def get_collection(self, time_range=None):
|
||||
"""
|
||||
Returns an ImageCollection object.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time_range: range (int), optional
|
||||
Time steps to extract, defaults to the entire length of video.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
output: ImageCollection
|
||||
output: ImageCollection
|
||||
Collection of images iterator.
|
||||
"""
|
||||
if not time_range:
|
||||
time_range = range(int(self.frame_count()))
|
||||
return ImageCollection(time_range, load_func=self.get_index_frame)
|
||||
|
||||
|
||||
__all__ = ["Video"]
|
||||
|
||||
|
||||
__all__ = ["Video"]
|
||||
|
||||
@@ -201,17 +201,17 @@ def regionprops(label_image, properties=['Area', 'Centroid'],
|
||||
|
||||
m = _moments.central_moments(array, 0, 0, 3)
|
||||
# centroid
|
||||
cr = m[0,1] / m[0,0]
|
||||
cc = m[1,0] / m[0,0]
|
||||
cr = m[0, 1] / m[0, 0]
|
||||
cc = m[1, 0] / m[0, 0]
|
||||
mu = _moments.central_moments(array, cr, cc, 3)
|
||||
|
||||
#: elements of the inertia tensor [a b; b c]
|
||||
a = mu[2,0] / mu[0,0]
|
||||
b = mu[1,1] / mu[0,0]
|
||||
c = mu[0,2] / mu[0,0]
|
||||
a = mu[2, 0] / mu[0, 0]
|
||||
b = mu[1, 1] / mu[0, 0]
|
||||
c = mu[0, 2] / mu[0, 0]
|
||||
#: eigen values of inertia tensor
|
||||
l1 = (a + c) / 2 + sqrt(4 * b ** 2 + (a - c) ** 2) / 2
|
||||
l2 = (a + c) / 2 - sqrt(4 * b ** 2 + (a - c) ** 2) / 2
|
||||
l1 = (a + c) / 2 + sqrt(4 * b**2 + (a - c)**2) / 2
|
||||
l2 = (a + c) / 2 - sqrt(4 * b**2 + (a - c)**2) / 2
|
||||
|
||||
# cached results which are used by several properties
|
||||
_filled_image = None
|
||||
@@ -219,7 +219,7 @@ def regionprops(label_image, properties=['Area', 'Centroid'],
|
||||
_nu = None
|
||||
|
||||
if 'Area' in properties:
|
||||
obj_props['Area'] = m[0,0]
|
||||
obj_props['Area'] = m[0, 0]
|
||||
|
||||
if 'BoundingBox' in properties:
|
||||
obj_props['BoundingBox'] = (r0, c0, sl[0].stop, sl[1].stop)
|
||||
@@ -247,17 +247,17 @@ def regionprops(label_image, properties=['Area', 'Centroid'],
|
||||
obj_props['Eccentricity'] = sqrt(1 - l2 / l1)
|
||||
|
||||
if 'EquivDiameter' in properties:
|
||||
obj_props['EquivDiameter'] = sqrt(4 * m[0,0] / PI)
|
||||
obj_props['EquivDiameter'] = sqrt(4 * m[0, 0] / PI)
|
||||
|
||||
if 'EulerNumber' in properties:
|
||||
if _filled_image is None:
|
||||
_filled_image = ndimage.binary_fill_holes(array, STREL_8)
|
||||
euler_array = _filled_image != array
|
||||
_, num = ndimage.label(euler_array, STREL_8)
|
||||
obj_props['EulerNumber'] = - num
|
||||
obj_props['EulerNumber'] = - num
|
||||
|
||||
if 'Extent' in properties:
|
||||
obj_props['Extent'] = m[0,0] / (array.shape[0] * array.shape[1])
|
||||
obj_props['Extent'] = m[0, 0] / (array.shape[0] * array.shape[1])
|
||||
|
||||
if 'HuMoments' in properties:
|
||||
if _nu is None:
|
||||
@@ -300,16 +300,15 @@ def regionprops(label_image, properties=['Area', 'Centroid'],
|
||||
if 'Solidity' in properties:
|
||||
if _convex_image is None:
|
||||
_convex_image = convex_hull_image(array)
|
||||
obj_props['Solidity'] = m[0,0] / np.sum(_convex_image)
|
||||
|
||||
obj_props['Solidity'] = m[0, 0] / np.sum(_convex_image)
|
||||
|
||||
if intensity_image is not None:
|
||||
weighted_array = array * intensity_image[sl]
|
||||
|
||||
wm = _moments.central_moments(weighted_array, 0, 0, 3)
|
||||
# weighted centroid
|
||||
wcr = wm[0,1] / wm[0,0]
|
||||
wcc = wm[1,0] / wm[0,0]
|
||||
wcr = wm[0, 1] / wm[0, 0]
|
||||
wcc = wm[1, 0] / wm[0, 0]
|
||||
wmu = _moments.central_moments(weighted_array, wcr, wcc, 3)
|
||||
|
||||
# cached results which are used by several properties
|
||||
|
||||
@@ -5,6 +5,7 @@ from collections import deque
|
||||
|
||||
_param_options = ('high', 'low')
|
||||
|
||||
|
||||
def find_contours(array, level,
|
||||
fully_connected='low', positive_orientation='low'):
|
||||
"""Find iso-valued contours in a 2D array for a given level value.
|
||||
@@ -83,10 +84,10 @@ def find_contours(array, level,
|
||||
|
||||
This means that to find reasonable contours, it is best to find contours
|
||||
midway between the expected "light" and "dark" values. In particular,
|
||||
given a binarized array, *do not* choose to find contours at the low or high
|
||||
value of the array. This will often yield degenerate contours, especially
|
||||
around structures that are a single array element wide. Instead choose
|
||||
a middle value, as above.
|
||||
given a binarized array, *do not* choose to find contours at the low or
|
||||
high value of the array. This will often yield degenerate contours,
|
||||
especially around structures that are a single array element wide. Instead
|
||||
choose a middle value, as above.
|
||||
|
||||
References
|
||||
----------
|
||||
@@ -101,8 +102,8 @@ def find_contours(array, level,
|
||||
level = float(level)
|
||||
if (fully_connected not in _param_options or
|
||||
positive_orientation not in _param_options):
|
||||
raise ValueError('Parameters "fully_connected" and'
|
||||
' "positive_orientation" must be either "high" or "low".')
|
||||
raise ValueError('Parameters "fully_connected" and'
|
||||
' "positive_orientation" must be either "high" or "low".')
|
||||
point_list = _find_contours.iterate_and_store(array, level,
|
||||
fully_connected == 'high')
|
||||
contours = _assemble_contours(_take_2(point_list))
|
||||
@@ -110,12 +111,14 @@ def find_contours(array, level,
|
||||
contours = [c[::-1] for c in contours]
|
||||
return contours
|
||||
|
||||
|
||||
def _take_2(seq):
|
||||
iterator = iter(seq)
|
||||
while(True):
|
||||
n1 = iterator.next()
|
||||
n2 = iterator.next()
|
||||
yield (n1, n2)
|
||||
iterator = iter(seq)
|
||||
while(True):
|
||||
n1 = iterator.next()
|
||||
n2 = iterator.next()
|
||||
yield (n1, n2)
|
||||
|
||||
|
||||
def _assemble_contours(points_iterator):
|
||||
current_index = 0
|
||||
@@ -127,7 +130,8 @@ def _assemble_contours(points_iterator):
|
||||
# This happens when (and only when) one vertex of the square is
|
||||
# exactly the contour level, and the rest are above or below.
|
||||
# This degnerate vertex will be picked up later by neighboring squares.
|
||||
if from_point == to_point: continue
|
||||
if from_point == to_point:
|
||||
continue
|
||||
|
||||
tail_data = starts.get(to_point)
|
||||
head_data = ends.get(from_point)
|
||||
@@ -143,7 +147,7 @@ def _assemble_contours(points_iterator):
|
||||
head.append(to_point)
|
||||
del starts[to_point]
|
||||
del ends[from_point]
|
||||
else: # tail is not head
|
||||
else: # tail is not head
|
||||
# We need to join two distinct contours.
|
||||
# We want to keep the first contour segment created, so that
|
||||
# the final contours are ordered left->right, top->bottom.
|
||||
@@ -157,7 +161,7 @@ def _assemble_contours(points_iterator):
|
||||
# remove the old end of head and add the new end.
|
||||
del ends[from_point]
|
||||
ends[head[-1]] = (head, head_num)
|
||||
else: # tail_num <= head_num
|
||||
else: # tail_num <= head_num
|
||||
# head was created second. Prepend head to tail.
|
||||
tail.extendleft(reversed(head))
|
||||
# remove all traces of head:
|
||||
|
||||
@@ -5,6 +5,7 @@ from skimage._build import cython
|
||||
import os
|
||||
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
|
||||
|
||||
@@ -23,10 +24,10 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Graph-based Image-processing Algorithms',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'Modified BSD',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Graph-based Image-processing Algorithms',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='Modified BSD',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from numpy.testing import *
|
||||
|
||||
from skimage.measure import find_contours
|
||||
from skimage.measure import find_contours
|
||||
|
||||
a = np.ones((8,8), dtype=np.float32)
|
||||
a = np.ones((8, 8), dtype=np.float32)
|
||||
a[1:-1, 1] = 0
|
||||
a[1, 1:-1] = 0
|
||||
|
||||
@@ -16,43 +16,45 @@ a[1, 1:-1] = 0
|
||||
## [ 1., 0., 1., 1., 1., 1., 1., 1.],
|
||||
## [ 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)
|
||||
|
||||
x,y = np.mgrid[-1:1:5j,-1:1:5j]
|
||||
x, y = np.mgrid[-1:1:5j, -1:1:5j]
|
||||
r = np.sqrt(x**2 + y**2)
|
||||
|
||||
|
||||
def test_binary():
|
||||
contours = find_contours(a, 0.5)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0],
|
||||
[[ 6. , 1.5],
|
||||
[ 5. , 1.5],
|
||||
[ 4. , 1.5],
|
||||
[ 3. , 1.5],
|
||||
[ 2. , 1.5],
|
||||
[ 1.5, 2. ],
|
||||
[ 1.5, 3. ],
|
||||
[ 1.5, 4. ],
|
||||
[ 1.5, 5. ],
|
||||
[ 1.5, 6. ],
|
||||
[ 1. , 6.5],
|
||||
[ 0.5, 6. ],
|
||||
[ 0.5, 5. ],
|
||||
[ 0.5, 4. ],
|
||||
[ 0.5, 3. ],
|
||||
[ 0.5, 2. ],
|
||||
[ 0.5, 1. ],
|
||||
[ 1. , 0.5],
|
||||
[ 2. , 0.5],
|
||||
[ 3. , 0.5],
|
||||
[ 4. , 0.5],
|
||||
[ 5. , 0.5],
|
||||
[ 6. , 0.5],
|
||||
[ 6.5, 1. ],
|
||||
[ 6. , 1.5]])
|
||||
contours = find_contours(a, 0.5)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0],
|
||||
[[6. , 1.5],
|
||||
[5. , 1.5],
|
||||
[4. , 1.5],
|
||||
[3. , 1.5],
|
||||
[2. , 1.5],
|
||||
[1.5, 2. ],
|
||||
[1.5, 3. ],
|
||||
[1.5, 4. ],
|
||||
[1.5, 5. ],
|
||||
[1.5, 6. ],
|
||||
[1. , 6.5],
|
||||
[0.5, 6. ],
|
||||
[0.5, 5. ],
|
||||
[0.5, 4. ],
|
||||
[0.5, 3. ],
|
||||
[0.5, 2. ],
|
||||
[0.5, 1. ],
|
||||
[1. , 0.5],
|
||||
[2. , 0.5],
|
||||
[3. , 0.5],
|
||||
[4. , 0.5],
|
||||
[5. , 0.5],
|
||||
[6. , 0.5],
|
||||
[6.5, 1. ],
|
||||
[6. , 1.5]])
|
||||
|
||||
|
||||
def test_float():
|
||||
contours = find_contours(r, 0.5)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0],
|
||||
contours = find_contours(r, 0.5)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0],
|
||||
[[ 2., 3.],
|
||||
[ 1., 2.],
|
||||
[ 2., 1.],
|
||||
@@ -60,7 +62,6 @@ def test_float():
|
||||
[ 2., 3.]])
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.testing import run_module_suite
|
||||
run_module_suite()
|
||||
|
||||
@@ -18,19 +18,20 @@ SAMPLE = np.array(
|
||||
[0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]
|
||||
)
|
||||
INTENSITY_SAMPLE = SAMPLE.copy()
|
||||
INTENSITY_SAMPLE[1,9:11] = 2
|
||||
INTENSITY_SAMPLE[1, 9:11] = 2
|
||||
|
||||
|
||||
def test_area():
|
||||
area = regionprops(SAMPLE, ['Area'])[0]['Area']
|
||||
assert area == np.sum(SAMPLE)
|
||||
|
||||
|
||||
def test_bbox():
|
||||
bbox = regionprops(SAMPLE, ['BoundingBox'])[0]['BoundingBox']
|
||||
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]))
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[:,-1] = 0
|
||||
SAMPLE_mod[:, -1] = 0
|
||||
bbox = regionprops(SAMPLE_mod, ['BoundingBox'])[0]['BoundingBox']
|
||||
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1))
|
||||
|
||||
@@ -51,11 +52,13 @@ def test_centroid():
|
||||
# determined with MATLAB
|
||||
assert_array_almost_equal(centroid, (5.66666666666666, 9.444444444444444))
|
||||
|
||||
|
||||
def test_convex_area():
|
||||
area = regionprops(SAMPLE, ['ConvexArea'])[0]['ConvexArea']
|
||||
# determined with MATLAB
|
||||
assert area == 124
|
||||
|
||||
|
||||
def test_convex_image():
|
||||
img = regionprops(SAMPLE, ['ConvexImage'])[0]['ConvexImage']
|
||||
# determined with MATLAB
|
||||
@@ -73,28 +76,33 @@ def test_convex_image():
|
||||
)
|
||||
assert_array_equal(img, ref)
|
||||
|
||||
|
||||
def test_eccentricity():
|
||||
eps = regionprops(SAMPLE, ['Eccentricity'])[0]['Eccentricity']
|
||||
assert_almost_equal(eps, 0.814629313427)
|
||||
|
||||
|
||||
def test_equiv_diameter():
|
||||
diameter = regionprops(SAMPLE, ['EquivDiameter'])[0]['EquivDiameter']
|
||||
# determined with MATLAB
|
||||
assert_almost_equal(diameter, 9.57461472963)
|
||||
|
||||
|
||||
def test_euler_number():
|
||||
en = regionprops(SAMPLE, ['EulerNumber'])[0]['EulerNumber']
|
||||
assert en == 0
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[7,-3] = 0
|
||||
SAMPLE_mod[7, -3] = 0
|
||||
en = regionprops(SAMPLE_mod, ['EulerNumber'])[0]['EulerNumber']
|
||||
assert en == -1
|
||||
|
||||
|
||||
def test_extent():
|
||||
extent = regionprops(SAMPLE, ['Extent'])[0]['Extent']
|
||||
assert_almost_equal(extent, 0.4)
|
||||
|
||||
|
||||
def test_hu_moments():
|
||||
hu = regionprops(SAMPLE, ['HuMoments'])[0]['HuMoments']
|
||||
ref = np.array([
|
||||
@@ -109,46 +117,54 @@ def test_hu_moments():
|
||||
# bug in OpenCV caused in Central Moments calculation?
|
||||
assert_array_almost_equal(hu, ref)
|
||||
|
||||
|
||||
def test_image():
|
||||
img = regionprops(SAMPLE, ['Image'])[0]['Image']
|
||||
assert_array_equal(img, SAMPLE)
|
||||
|
||||
|
||||
def test_filled_area():
|
||||
area = regionprops(SAMPLE, ['FilledArea'])[0]['FilledArea']
|
||||
assert area == np.sum(SAMPLE)
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[7,-3] = 0
|
||||
SAMPLE_mod[7, -3] = 0
|
||||
area = regionprops(SAMPLE_mod, ['FilledArea'])[0]['FilledArea']
|
||||
assert area == np.sum(SAMPLE)
|
||||
|
||||
|
||||
def test_major_axis_length():
|
||||
length = regionprops(SAMPLE, ['MajorAxisLength'])[0]['MajorAxisLength']
|
||||
# MATLAB has different interpretation of ellipse than found in literature,
|
||||
# here implemented as found in literature
|
||||
assert_almost_equal(length, 16.7924234999)
|
||||
|
||||
|
||||
def test_max_intensity():
|
||||
intensity = regionprops(SAMPLE, ['MaxIntensity'], INTENSITY_SAMPLE
|
||||
)[0]['MaxIntensity']
|
||||
assert_almost_equal(intensity, 2)
|
||||
|
||||
|
||||
def test_mean_intensity():
|
||||
intensity = regionprops(SAMPLE, ['MeanIntensity'], INTENSITY_SAMPLE
|
||||
)[0]['MeanIntensity']
|
||||
assert_almost_equal(intensity, 1.02777777777777)
|
||||
|
||||
|
||||
def test_min_intensity():
|
||||
intensity = regionprops(SAMPLE, ['MinIntensity'], INTENSITY_SAMPLE
|
||||
)[0]['MinIntensity']
|
||||
assert_almost_equal(intensity, 1)
|
||||
|
||||
|
||||
def test_minor_axis_length():
|
||||
length = regionprops(SAMPLE, ['MinorAxisLength'])[0]['MinorAxisLength']
|
||||
# MATLAB has different interpretation of ellipse than found in literature,
|
||||
# here implemented as found in literature
|
||||
assert_almost_equal(length, 9.739302807263)
|
||||
|
||||
|
||||
def test_moments():
|
||||
m = regionprops(SAMPLE, ['Moments'])[0]['Moments']
|
||||
#: determined with OpenCV
|
||||
@@ -199,11 +215,13 @@ def test_weighted_central_moments():
|
||||
np.set_printoptions(precision=10)
|
||||
assert_array_almost_equal(wmu, ref)
|
||||
|
||||
|
||||
def test_weighted_centroid():
|
||||
centroid = regionprops(SAMPLE, ['WeightedCentroid'], INTENSITY_SAMPLE
|
||||
)[0]['WeightedCentroid']
|
||||
assert_array_almost_equal(centroid, (5.540540540540, 9.445945945945))
|
||||
|
||||
|
||||
def test_weighted_hu_moments():
|
||||
whu = regionprops(SAMPLE, ['WeightedHuMoments'], INTENSITY_SAMPLE
|
||||
)[0]['WeightedHuMoments']
|
||||
@@ -218,6 +236,7 @@ def test_weighted_hu_moments():
|
||||
])
|
||||
assert_array_almost_equal(whu, ref)
|
||||
|
||||
|
||||
def test_weighted_moments():
|
||||
wm = regionprops(SAMPLE, ['WeightedMoments'], INTENSITY_SAMPLE
|
||||
)[0]['WeightedMoments']
|
||||
|
||||
@@ -4,6 +4,7 @@ import numpy as np
|
||||
from ._pnpoly import points_inside_poly, grid_points_inside_poly
|
||||
from ._convex_hull import possible_hull
|
||||
|
||||
|
||||
def convex_hull_image(image):
|
||||
"""Compute the convex hull image of a binary image.
|
||||
|
||||
@@ -34,7 +35,7 @@ def convex_hull_image(image):
|
||||
# hull.
|
||||
coords = possible_hull(image.astype(np.uint8))
|
||||
N = len(coords)
|
||||
|
||||
|
||||
# Add a vertex for the middle of each pixel edge
|
||||
coords_corners = np.empty((N * 4, 2))
|
||||
for i, (x_offset, y_offset) in enumerate(zip((0, 0, -0.5, 0.5),
|
||||
@@ -61,5 +62,5 @@ def convex_hull_image(image):
|
||||
# For each pixel coordinate, check whether that pixel
|
||||
# lies inside the convex hull
|
||||
mask = grid_points_inside_poly(image.shape[:2], v)
|
||||
|
||||
|
||||
return mask
|
||||
|
||||
@@ -71,7 +71,7 @@ def erosion(image, selem, out=None, shift_x=False, shift_y=False):
|
||||
import skimage.morphology.cmorph as cmorph
|
||||
out = cmorph.erode(image, selem, out=out,
|
||||
shift_x=shift_x, shift_y=shift_y)
|
||||
return out;
|
||||
return out
|
||||
except ImportError:
|
||||
raise ImportError("cmorph extension not available.")
|
||||
|
||||
@@ -130,7 +130,7 @@ def dilation(image, selem, out=None, shift_x=False, shift_y=False):
|
||||
from . import cmorph
|
||||
out = cmorph.dilate(image, selem, out=out,
|
||||
shift_x=shift_x, shift_y=shift_y)
|
||||
return out;
|
||||
return out
|
||||
except ImportError:
|
||||
raise ImportError("cmorph extension not available.")
|
||||
|
||||
@@ -342,23 +342,27 @@ def greyscale_erode(*args, **kwargs):
|
||||
warnings.warn("`greyscale_erode` renamed `erosion`.")
|
||||
return erosion(*args, **kwargs)
|
||||
|
||||
|
||||
def greyscale_dilate(*args, **kwargs):
|
||||
warnings.warn("`greyscale_dilate` renamed `dilation`.")
|
||||
return dilation(*args, **kwargs)
|
||||
|
||||
|
||||
def greyscale_open(*args, **kwargs):
|
||||
warnings.warn("`greyscale_open` renamed `opening`.")
|
||||
return opening(*args, **kwargs)
|
||||
|
||||
|
||||
def greyscale_close(*args, **kwargs):
|
||||
warnings.warn("`greyscale_close` renamed `closing`.")
|
||||
return closing(*args, **kwargs)
|
||||
|
||||
|
||||
def greyscale_white_top_hat(*args, **kwargs):
|
||||
warnings.warn("`greyscale_white_top_hat` renamed `white_tophat`.")
|
||||
return white_tophat(*args, **kwargs)
|
||||
|
||||
|
||||
def greyscale_black_top_hat(*args, **kwargs):
|
||||
warnings.warn("`greyscale_black_top_hat` renamed `black_tophat`.")
|
||||
return black_tophat(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def square(width, dtype=np.uint8):
|
||||
"""
|
||||
Generates a flat, square-shaped structuring element. Every pixel
|
||||
@@ -30,6 +31,7 @@ def square(width, dtype=np.uint8):
|
||||
"""
|
||||
return np.ones((width, width), dtype=dtype)
|
||||
|
||||
|
||||
def rectangle(width, height, dtype=np.uint8):
|
||||
"""
|
||||
Generates a flat, rectangular-shaped structuring element of a
|
||||
@@ -58,6 +60,7 @@ def rectangle(width, height, dtype=np.uint8):
|
||||
"""
|
||||
return np.ones((width, height), dtype=dtype)
|
||||
|
||||
|
||||
def diamond(radius, dtype=np.uint8):
|
||||
"""
|
||||
Generates a flat, diamond-shaped structuring element of a given
|
||||
@@ -75,22 +78,23 @@ def diamond(radius, dtype=np.uint8):
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
half = radius
|
||||
(I, J) = np.meshgrid(xrange(0, radius*2+1), xrange(0, radius*2+1))
|
||||
s = np.abs(I-half)+np.abs(J-half)
|
||||
(I, J) = np.meshgrid(xrange(0, radius * 2 + 1), xrange(0, radius * 2 + 1))
|
||||
s = np.abs(I - half) + np.abs(J - half)
|
||||
return np.array(s <= radius, dtype=dtype)
|
||||
|
||||
|
||||
|
||||
def disk(radius, dtype=np.uint8):
|
||||
"""
|
||||
Generates a flat, disk-shaped structuring element of a given radius.
|
||||
A pixel is within the neighborhood if the euclidean distance between
|
||||
it and the origin is no greater than a radius.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
@@ -103,9 +107,9 @@ def disk(radius, dtype=np.uint8):
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
L = np.linspace(-radius, radius, 2*radius+1)
|
||||
L = np.linspace(-radius, radius, 2 * radius + 1)
|
||||
(X, Y) = np.meshgrid(L, L)
|
||||
s = X**2
|
||||
s += Y**2
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
|
||||
@@ -35,11 +36,11 @@ def configuration(parent_package='', top_path=None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'scikits-image Developers',
|
||||
author = 'Damian Eads',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Morphology Wrapper',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'SciPy License (BSD Style)',
|
||||
setup(maintainer='scikits-image Developers',
|
||||
author='Damian Eads',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Morphology Wrapper',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -9,31 +9,32 @@ from ._skeletonize import _skeletonize_loop, _table_lookup_index
|
||||
|
||||
# --------- Skeletonization by morphological thinning ---------
|
||||
|
||||
|
||||
def skeletonize(image):
|
||||
"""Return the skeleton of a binary image.
|
||||
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : numpy.ndarray
|
||||
A binary image containing the objects to be skeletonized. '1'
|
||||
represents foreground, and '0' represents background. It
|
||||
image : numpy.ndarray
|
||||
A binary image containing the objects to be skeletonized. '1'
|
||||
represents foreground, and '0' represents background. It
|
||||
also accepts arrays of boolean values where True is foreground.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
A matrix containing the thinned image.
|
||||
|
||||
|
||||
See also
|
||||
--------
|
||||
medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm [1] works by making successive passes of the image,
|
||||
The algorithm [1] works by making successive passes of the image,
|
||||
removing pixels on object borders. This continues until no
|
||||
more pixels can be removed. The image is correlated with a
|
||||
mask that assigns each pixel a number in the range [0...255]
|
||||
@@ -41,18 +42,18 @@ def skeletonize(image):
|
||||
pixels. A look up table is then used to assign the pixels a
|
||||
value of 0, 1, 2 or 3, which are selectively removed during
|
||||
the iterations.
|
||||
|
||||
Note that this algorithm will give different results than a
|
||||
|
||||
Note that this algorithm will give different results than a
|
||||
medial axis transform, which is also often referred to as
|
||||
"skeletonization".
|
||||
|
||||
"skeletonization".
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A fast parallel algorithm for thinning digital patterns,
|
||||
.. [1] A fast parallel algorithm for thinning digital patterns,
|
||||
T. Y. ZHANG and C. Y. SUEN, Communications of the ACM,
|
||||
March 1984, Volume 27, Number 3
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> X, Y = np.ogrid[0:9, 0:9]
|
||||
@@ -78,7 +79,7 @@ def skeletonize(image):
|
||||
[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]], dtype=uint8)
|
||||
|
||||
|
||||
"""
|
||||
# look up table - there is one entry for each of the 2^8=256 possible
|
||||
# combinations of 8 binary neighbours. 1's, 2's and 3's are candidates
|
||||
@@ -97,38 +98,38 @@ def skeletonize(image):
|
||||
|
||||
# check some properties of the input image:
|
||||
# - 2D
|
||||
# - binary image with only 0's and 1's
|
||||
# - binary image with only 0's and 1's
|
||||
if skeleton.ndim != 2:
|
||||
raise ValueError('Skeletonize requires a 2D array')
|
||||
raise ValueError('Skeletonize requires a 2D array')
|
||||
if not np.all(np.in1d(skeleton.flat, (0, 1))):
|
||||
raise ValueError('Image contains values other than 0 and 1')
|
||||
|
||||
|
||||
# create the mask that will assign a unique value based on the
|
||||
# arrangement of neighbouring pixels
|
||||
mask = np.array([[ 1, 2, 4],
|
||||
mask = np.array([[1, 2, 4],
|
||||
[128, 0, 8],
|
||||
[ 64, 32, 16]], np.uint8)
|
||||
[64, 32, 16]], np.uint8)
|
||||
|
||||
pixelRemoved = True
|
||||
while pixelRemoved:
|
||||
pixelRemoved = False;
|
||||
pixelRemoved = False
|
||||
|
||||
# assign each pixel a unique value based on its foreground neighbours
|
||||
neighbours = ndimage.correlate(skeleton, mask, mode='constant')
|
||||
|
||||
|
||||
# ignore background
|
||||
neighbours *= skeleton
|
||||
|
||||
|
||||
# use LUT to categorize each foreground pixel as a 0, 1, 2 or 3
|
||||
codes = np.take(lut, neighbours)
|
||||
|
||||
|
||||
# pass 1 - remove the 1's and 3's
|
||||
code_mask = (codes == 1)
|
||||
if np.any(code_mask):
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[code_mask] = 0
|
||||
code_mask = (codes == 3)
|
||||
if np.any(code_mask):
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[code_mask] = 0
|
||||
|
||||
@@ -137,11 +138,11 @@ def skeletonize(image):
|
||||
neighbours *= skeleton
|
||||
codes = np.take(lut, neighbours)
|
||||
code_mask = (codes == 2)
|
||||
if np.any(code_mask):
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[code_mask] = 0
|
||||
code_mask = (codes == 3)
|
||||
if np.any(code_mask):
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[code_mask] = 0
|
||||
|
||||
@@ -159,8 +160,8 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
Parameters
|
||||
----------
|
||||
|
||||
image : binary ndarray
|
||||
|
||||
image : binary ndarray
|
||||
|
||||
mask : binary ndarray, optional
|
||||
If a mask is given, only those elements with a true value in `mask`
|
||||
are used for computing the medial axis.
|
||||
@@ -177,18 +178,18 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
dist : ndarray of ints
|
||||
Distance transform of the image (only returned if `return_distance`
|
||||
is True)
|
||||
|
||||
|
||||
See also
|
||||
--------
|
||||
skeletonize
|
||||
skeletonize
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm computes the medial axis transform of an image
|
||||
as the ridges of its distance transform.
|
||||
|
||||
as the ridges of its distance transform.
|
||||
|
||||
The different steps of the algorithm are as follows
|
||||
* A lookup table is used, that assigns 0 or 1 to each configuration of
|
||||
* A lookup table is used, that assigns 0 or 1 to each configuration of
|
||||
the 3x3 binary square, whether the central pixel should be removed
|
||||
or kept. We want a point to be removed if it has more than one neighbor
|
||||
and if removing it does not change the number of connected components.
|
||||
@@ -197,12 +198,12 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
the cornerness of the pixel.
|
||||
|
||||
* The foreground (value of 1) points are ordered by
|
||||
the distance transform, then the cornerness.
|
||||
|
||||
* A cython function is called to reduce the image to its skeleton. It
|
||||
processes pixels in the order determined at the previous step, and
|
||||
removes or maintains a pixel according to the lookup table. Because
|
||||
of the ordering, it is possible to process all pixels in only one
|
||||
the distance transform, then the cornerness.
|
||||
|
||||
* A cython function is called to reduce the image to its skeleton. It
|
||||
processes pixels in the order determined at the previous step, and
|
||||
removes or maintains a pixel according to the lookup table. Because
|
||||
of the ordering, it is possible to process all pixels in only one
|
||||
pass.
|
||||
|
||||
Examples
|
||||
@@ -234,7 +235,7 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
masked_image = image.astype(bool).copy()
|
||||
masked_image[~mask] = False
|
||||
#
|
||||
# Build lookup table - three conditions
|
||||
# Build lookup table - three conditions
|
||||
# 1. Keep only positive pixels (center_is_foreground array).
|
||||
# AND
|
||||
# 2. Keep if removing the pixel results in a different connectivity
|
||||
@@ -249,30 +250,29 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
(np.array([ndimage.label(_pattern_of(index), _eight_connect)[1] !=
|
||||
ndimage.label(_pattern_of(index & ~ 2**4),
|
||||
_eight_connect)[1]
|
||||
for index in range(512)]) # condition 2
|
||||
|
|
||||
for index in range(512)]) # condition 2
|
||||
|
|
||||
np.array([np.sum(_pattern_of(index)) < 3 for index in range(512)]))
|
||||
# condition 3
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Build distance transform
|
||||
distance = ndimage.distance_transform_edt(masked_image)
|
||||
if return_distance:
|
||||
store_distance = distance.copy()
|
||||
|
||||
|
||||
# Corners
|
||||
# The processing order along the edge is critical to the shape of the
|
||||
# resulting skeleton: if you process a corner first, that corner will
|
||||
# be eroded and the skeleton will miss the arm from that corner. Pixels
|
||||
# with fewer neighbors are more "cornery" and should be processed last.
|
||||
# We use a cornerness_table lookup table where the score of a
|
||||
# We use a cornerness_table lookup table where the score of a
|
||||
# configuration is the number of background (0-value) pixels in the
|
||||
# 3x3 neighbourhood
|
||||
cornerness_table = np.array([9 - np.sum(_pattern_of(index))
|
||||
for index in range(512)])
|
||||
corner_score = _table_lookup(masked_image, cornerness_table)
|
||||
|
||||
|
||||
# Define arrays for inner loop
|
||||
i, j = np.mgrid[0:image.shape[0], 0:image.shape[1]]
|
||||
result = masked_image.copy()
|
||||
@@ -280,7 +280,7 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
i = np.ascontiguousarray(i[result], np.int32)
|
||||
j = np.ascontiguousarray(j[result], np.int32)
|
||||
result = np.ascontiguousarray(result, np.uint8)
|
||||
|
||||
|
||||
# Determine the order in which pixels are processed.
|
||||
# We use a random # for tiebreaking. Assign each pixel in the image a
|
||||
# predictable, random # so that masking doesn't affect arbitrary choices
|
||||
@@ -305,6 +305,7 @@ def medial_axis(image, mask=None, return_distance=False):
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def _pattern_of(index):
|
||||
"""
|
||||
Return the pattern represented by an index value
|
||||
@@ -317,9 +318,9 @@ def _pattern_of(index):
|
||||
|
||||
def _table_lookup(image, table):
|
||||
"""
|
||||
Perform a morphological transform on an image, directed by its
|
||||
Perform a morphological transform on an image, directed by its
|
||||
neighbors
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
@@ -329,7 +330,7 @@ def _table_lookup(image, table):
|
||||
the values of that pixel and its 8-connected neighbors.
|
||||
border_value : bool
|
||||
The value of pixels beyond the border of the image.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : ndarray of same shape as `image`
|
||||
@@ -338,13 +339,14 @@ def _table_lookup(image, table):
|
||||
Notes
|
||||
-----
|
||||
The pixels are numbered like this::
|
||||
|
||||
|
||||
|
||||
0 1 2
|
||||
3 4 5
|
||||
6 7 8
|
||||
|
||||
The index at a pixel is the sum of 2**<pixel-number> for pixels
|
||||
that evaluate to true.
|
||||
that evaluate to true.
|
||||
"""
|
||||
#
|
||||
# We accumulate into the indexer to get the index into the table
|
||||
@@ -356,11 +358,11 @@ def _table_lookup(image, table):
|
||||
indexer[1:, 1:] += image[:-1, :-1] * 2**0
|
||||
indexer[1:, :] += image[:-1, :] * 2**1
|
||||
indexer[1:, :-1] += image[:-1, 1:] * 2**2
|
||||
|
||||
|
||||
indexer[:, 1:] += image[:, :-1] * 2**3
|
||||
indexer[:, :] += image[:, :] * 2**4
|
||||
indexer[:, :-1] += image[:, 1:] * 2**5
|
||||
|
||||
|
||||
indexer[:-1, 1:] += image[1:, :-1] * 2**6
|
||||
indexer[:-1, :] += image[1:, :] * 2**7
|
||||
indexer[:-1, :-1] += image[1:, 1:] * 2**8
|
||||
@@ -368,4 +370,3 @@ def _table_lookup(image, table):
|
||||
indexer = _table_lookup_index(np.ascontiguousarray(image, np.uint8))
|
||||
image = table[indexer]
|
||||
return image
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from numpy.testing import assert_array_equal, run_module_suite
|
||||
|
||||
from skimage.morphology import label
|
||||
|
||||
|
||||
class TestConnectedComponents:
|
||||
def setup(self):
|
||||
self.x = np.array([[0, 0, 3, 2, 1, 9],
|
||||
|
||||
@@ -10,6 +10,7 @@ try:
|
||||
except ImportError:
|
||||
scipy_spatial = False
|
||||
|
||||
|
||||
@skipif(not scipy_spatial)
|
||||
def test_basic():
|
||||
image = np.array(
|
||||
@@ -30,6 +31,7 @@ def test_basic():
|
||||
|
||||
assert_array_equal(convex_hull_image(image), expected)
|
||||
|
||||
|
||||
@skipif(not scipy_spatial)
|
||||
def test_possible_hull():
|
||||
image = np.array(
|
||||
|
||||
@@ -11,6 +11,7 @@ from skimage.morphology import selem
|
||||
|
||||
lena = np.load(os.path.join(data_dir, 'lena_GRAY_U8.npy'))
|
||||
|
||||
|
||||
class TestMorphology():
|
||||
|
||||
def morph_worker(self, img, fn, morph_func, strel_func):
|
||||
@@ -155,4 +156,3 @@ class TestDTypes():
|
||||
|
||||
if __name__ == '__main__':
|
||||
testing.run_module_suite()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from numpy.testing import assert_array_equal
|
||||
from skimage.morphology._pnpoly import points_inside_poly, \
|
||||
grid_points_inside_poly
|
||||
|
||||
|
||||
class test_npnpoly():
|
||||
def test_square(self):
|
||||
v = np.array([[0, 0],
|
||||
@@ -24,13 +25,14 @@ class test_npnpoly():
|
||||
def test_type(self):
|
||||
assert(points_inside_poly([[0, 0]], [[0, 0]]).dtype == np.bool)
|
||||
|
||||
|
||||
def test_grid_points_inside_poly():
|
||||
v = np.array([[0, 0],
|
||||
[5, 0],
|
||||
[5, 5]])
|
||||
|
||||
expected = np.tril(np.ones((5, 5), dtype=bool))
|
||||
|
||||
|
||||
assert_array_equal(grid_points_inside_poly((5, 5), v),
|
||||
expected)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from skimage.io import *
|
||||
from skimage import data_dir
|
||||
from skimage.morphology import *
|
||||
|
||||
|
||||
class TestSElem():
|
||||
|
||||
def test_square_selem(self):
|
||||
@@ -32,13 +33,12 @@ class TestSElem():
|
||||
expected_mask = matlab_masks[arrname]
|
||||
actual_mask = func(k)
|
||||
if (expected_mask.shape == (1,)):
|
||||
expected_mask = expected_mask[:,np.newaxis]
|
||||
expected_mask = expected_mask[:, np.newaxis]
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
k = k + 1
|
||||
|
||||
|
||||
def test_selem_disk(self):
|
||||
self.strel_worker("disk-matlab-output.npz", selem.disk)
|
||||
|
||||
def test_selem_diamond(self):
|
||||
self.strel_worker("diamond-matlab-output.npz", selem.diamond)
|
||||
|
||||
|
||||
@@ -7,75 +7,77 @@ from skimage.io import imread
|
||||
from skimage import data_dir
|
||||
import os.path
|
||||
|
||||
|
||||
class TestSkeletonize():
|
||||
def test_skeletonize_no_foreground(self):
|
||||
im = np.zeros((5,5))
|
||||
im = np.zeros((5, 5))
|
||||
result = skeletonize(im)
|
||||
numpy.testing.assert_array_equal(result, np.zeros((5,5)))
|
||||
|
||||
numpy.testing.assert_array_equal(result, np.zeros((5, 5)))
|
||||
|
||||
def test_skeletonize_wrong_dim1(self):
|
||||
im = np.zeros((5))
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_wrong_dim2(self):
|
||||
im = np.zeros((5, 5, 5))
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_not_binary(self):
|
||||
im = np.zeros((5, 5))
|
||||
im[0, 0] = 1
|
||||
im[0, 1] = 2
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_unexpected_value(self):
|
||||
im = np.zeros((5, 5))
|
||||
im[0, 0] = 2
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_all_foreground(self):
|
||||
im = np.ones((3,4))
|
||||
im = np.ones((3, 4))
|
||||
result = skeletonize(im)
|
||||
|
||||
|
||||
def test_skeletonize_single_point(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im)
|
||||
numpy.testing.assert_array_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_already_thinned(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3,1:-1] = 1
|
||||
im[3, 1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize(im)
|
||||
numpy.testing.assert_array_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_output(self):
|
||||
im = imread(os.path.join(data_dir, "bw_text.png"), as_grey=True)
|
||||
|
||||
|
||||
# make black the foreground
|
||||
im = (im==0)
|
||||
im = (im == 0)
|
||||
result = skeletonize(im)
|
||||
|
||||
|
||||
expected = np.load(os.path.join(data_dir, "bw_text_skeleton.npy"))
|
||||
numpy.testing.assert_array_equal(result, expected)
|
||||
|
||||
|
||||
|
||||
def test_skeletonize_num_neighbours(self):
|
||||
# an empty image
|
||||
image = np.zeros((300, 300))
|
||||
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 1
|
||||
image[10:-10, -100:-10] = 1
|
||||
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.bresenham(250, 150, 10, 280)
|
||||
for i in range(10): image[rs+i, cs] = 1
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 1
|
||||
rs, cs = draw.bresenham(10, 150, 250, 280)
|
||||
for i in range(20): image[rs+i, cs] = 1
|
||||
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 1
|
||||
|
||||
# foreground object 3
|
||||
ir, ic = np.indices(image.shape)
|
||||
circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2
|
||||
@@ -83,13 +85,14 @@ class TestSkeletonize():
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize(image)
|
||||
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels in a skeleton
|
||||
mask = np.array([[1, 1],
|
||||
[1, 1]], np.uint8)
|
||||
[1, 1]], np.uint8)
|
||||
blocks = correlate(result, mask, mode='constant')
|
||||
assert not numpy.any(blocks == 4)
|
||||
|
||||
|
||||
|
||||
class TestMedialAxis():
|
||||
def test_00_00_zeros(self):
|
||||
'''Test skeletonize on an array of all zeros'''
|
||||
@@ -101,7 +104,7 @@ class TestMedialAxis():
|
||||
result = medial_axis(np.zeros((10, 10), bool),
|
||||
np.zeros((10, 10), bool))
|
||||
assert np.all(result == False)
|
||||
|
||||
|
||||
def test_01_01_rectangle(self):
|
||||
'''Test skeletonize on a rectangle'''
|
||||
image = np.zeros((9, 15), bool)
|
||||
|
||||
@@ -43,7 +43,6 @@ Original author: Lee Kamentsky
|
||||
|
||||
|
||||
import math
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
@@ -54,6 +53,7 @@ from skimage.morphology.watershed import watershed, \
|
||||
|
||||
eps = 1e-12
|
||||
|
||||
|
||||
def diff(a, b):
|
||||
if not isinstance(a, np.ndarray):
|
||||
a = np.asarray(a)
|
||||
@@ -61,7 +61,7 @@ def diff(a, b):
|
||||
b = np.asarray(b)
|
||||
if (0 in a.shape) and (0 in b.shape):
|
||||
return 0.0
|
||||
b[a==0] = 0
|
||||
b[a == 0] = 0
|
||||
if (a.dtype in [np.complex64, np.complex128] or
|
||||
b.dtype in [np.complex64, np.complex128]):
|
||||
a = np.asarray(a, np.complex128)
|
||||
@@ -75,8 +75,10 @@ def diff(a, b):
|
||||
t = ((a - b)**2).sum()
|
||||
return math.sqrt(t)
|
||||
|
||||
|
||||
class TestWatershed(unittest.TestCase):
|
||||
eight = np.ones((3, 3),bool)
|
||||
eight = np.ones((3, 3), bool)
|
||||
|
||||
def test_watershed01(self):
|
||||
"watershed 1"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -100,7 +102,7 @@ class TestWatershed(unittest.TestCase):
|
||||
[ 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0]],
|
||||
np.int8)
|
||||
out = watershed(data, markers,self.eight)
|
||||
out = watershed(data, markers, self.eight)
|
||||
expected = np.array([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
@@ -120,28 +122,27 @@ class TestWatershed(unittest.TestCase):
|
||||
def test_watershed02(self):
|
||||
"watershed 2"
|
||||
data = 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, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[-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, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[ -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, 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],
|
||||
[ 0, 0, 0, 0, 0, 0, 0]],
|
||||
np.int8)
|
||||
[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],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.int8)
|
||||
out = watershed(data, markers)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
@@ -159,26 +160,25 @@ class TestWatershed(unittest.TestCase):
|
||||
def test_watershed03(self):
|
||||
"watershed 3"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 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, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 2, 0, 3, 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, 0, 0, 0, 0, -1]],
|
||||
np.int8)
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 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, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 2, 0, 3, 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, 0, 0, 0, 0, -1]], np.int8)
|
||||
out = watershed(data, markers)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 0, 2, 0, 3, 0, -1],
|
||||
@@ -200,21 +200,20 @@ class TestWatershed(unittest.TestCase):
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 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, 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]], np.uint8)
|
||||
markers = 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, 2, 0, 3, 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, 0, 0, 0, 0, -1]],
|
||||
np.int8)
|
||||
markers = 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, 2, 0, 3, 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, 0, 0, 0, 0, -1]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
@@ -222,35 +221,34 @@ class TestWatershed(unittest.TestCase):
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]], out)
|
||||
self.failUnless(error < eps)
|
||||
|
||||
def test_watershed05(self):
|
||||
"watershed 5"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 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, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 3, 0, 2, 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, 0, 0, 0, 0, -1]],
|
||||
np.int8)
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 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, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 3, 0, 2, 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, 0, 0, 0, 0, -1]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
@@ -267,24 +265,23 @@ class TestWatershed(unittest.TestCase):
|
||||
def test_watershed06(self):
|
||||
"watershed 6"
|
||||
data = np.array([[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 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, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[ 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],
|
||||
[ 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]],
|
||||
np.int8)
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 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, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[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],
|
||||
[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]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
@@ -300,28 +297,28 @@ class TestWatershed(unittest.TestCase):
|
||||
def test_watershed07(self):
|
||||
"A regression test of a competitive case that failed"
|
||||
data = np.array([[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,255,255,204,153,103,103,153,204,255,255,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]])
|
||||
mask = (data!=255)
|
||||
markers = np.zeros(data.shape,int)
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,255,255,204,153,103,103,153,204,255,255,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]])
|
||||
mask = (data != 255)
|
||||
markers = np.zeros(data.shape, int)
|
||||
markers[6, 7] = 1
|
||||
markers[14, 7] = 2
|
||||
out = watershed(data, markers, self.eight, mask=mask)
|
||||
@@ -331,35 +328,35 @@ class TestWatershed(unittest.TestCase):
|
||||
#
|
||||
size1 = np.sum(out == 1)
|
||||
size2 = np.sum(out == 2)
|
||||
self.assertTrue(abs(size1-size2) <= 6)
|
||||
|
||||
self.assertTrue(abs(size1 - size2) <= 6)
|
||||
|
||||
def test_watershed08(self):
|
||||
"The border pixels + an edge are all the same value"
|
||||
data = np.array([[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,255,255,204,153,141,141,153,204,255,255,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]])
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,255,255,204,153,141,141,153,204,255,255,255,255,255],
|
||||
[255,255,255,255,204,183,141, 94, 94,141,183,204,255,255,255,255],
|
||||
[255,255,255,204,183,141,111, 72, 72,111,141,183,204,255,255,255],
|
||||
[255,255,204,183,141,111, 72, 39, 39, 72,111,141,183,204,255,255],
|
||||
[255,255,204,153,111, 72, 39, 1, 1, 39, 72,111,153,204,255,255],
|
||||
[255,255,204,153,111, 94, 72, 52, 52, 72, 94,111,153,204,255,255],
|
||||
[255,255,204,183,153,141,111,103,103,111,141,153,183,204,255,255],
|
||||
[255,255,255,204,204,183,153,153,153,153,183,204,204,255,255,255],
|
||||
[255,255,255,255,255,204,204,204,204,204,204,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
|
||||
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]])
|
||||
mask = (data != 255)
|
||||
markers = np.zeros(data.shape,int)
|
||||
markers[6,7] = 1
|
||||
markers[14,7] = 2
|
||||
markers = np.zeros(data.shape, int)
|
||||
markers[6, 7] = 1
|
||||
markers[14, 7] = 2
|
||||
out = watershed(data, markers, self.eight, mask=mask)
|
||||
#
|
||||
# The two objects should be the same size, except possibly for the
|
||||
@@ -367,30 +364,27 @@ class TestWatershed(unittest.TestCase):
|
||||
#
|
||||
size1 = np.sum(out == 1)
|
||||
size2 = np.sum(out == 2)
|
||||
self.assertTrue(abs(size1-size2) <= 6)
|
||||
|
||||
self.assertTrue(abs(size1 - size2) <= 6)
|
||||
|
||||
def test_watershed09(self):
|
||||
"""Test on an image of reasonable size
|
||||
|
||||
|
||||
This is here both for timing (does it take forever?) and to
|
||||
ensure that the memory constraints are reasonable
|
||||
"""
|
||||
image = np.zeros((1000, 1000))
|
||||
coords = np.random.uniform(0, 1000, (100, 2)).astype(int)
|
||||
markers = np.zeros((1000, 1000),int)
|
||||
markers = np.zeros((1000, 1000), int)
|
||||
idx = 1
|
||||
for x,y in coords:
|
||||
image[x,y] = 1
|
||||
for x, y in coords:
|
||||
image[x, y] = 1
|
||||
markers[x, y] = idx
|
||||
idx += 1
|
||||
|
||||
|
||||
image = scipy.ndimage.gaussian_filter(image, 4)
|
||||
before = time.clock()
|
||||
out = watershed(image, markers, self.eight)
|
||||
elapsed = time.clock() - before
|
||||
before = time.clock()
|
||||
out = scipy.ndimage.watershed_ift(image.astype(np.uint16), markers, self.eight)
|
||||
elapsed = time.clock() - before
|
||||
watershed(image, markers, self.eight)
|
||||
scipy.ndimage.watershed_ift(image.astype(np.uint16), markers,
|
||||
self.eight)
|
||||
|
||||
|
||||
class TestIsLocalMaximum(unittest.TestCase):
|
||||
@@ -399,7 +393,7 @@ class TestIsLocalMaximum(unittest.TestCase):
|
||||
labels = np.zeros((10, 20), int)
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(~ result))
|
||||
|
||||
|
||||
def test_01_01_one_point(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
@@ -407,7 +401,7 @@ class TestIsLocalMaximum(unittest.TestCase):
|
||||
labels[5, 5] = 1
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(result == (labels == 1)))
|
||||
|
||||
|
||||
def test_01_02_adjacent_and_same(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
@@ -415,7 +409,7 @@ class TestIsLocalMaximum(unittest.TestCase):
|
||||
labels[5, 5:6] = 1
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(result == (labels == 1)))
|
||||
|
||||
|
||||
def test_01_03_adjacent_and_different(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
@@ -427,67 +421,67 @@ class TestIsLocalMaximum(unittest.TestCase):
|
||||
self.assertTrue(np.all(result == expected))
|
||||
result = is_local_maximum(image, labels)
|
||||
self.assertTrue(np.all(result == expected))
|
||||
|
||||
|
||||
def test_01_04_not_adjacent_and_different(self):
|
||||
image = np.zeros((10,20))
|
||||
labels = np.zeros((10,20), int)
|
||||
image[5,5] = 1
|
||||
image[5,8] = .5
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 8] = .5
|
||||
labels[image > 0] = 1
|
||||
expected = (labels == 1)
|
||||
result = is_local_maximum(image, labels, np.ones((3,3), bool))
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(result == expected))
|
||||
|
||||
|
||||
def test_01_05_two_objects(self):
|
||||
image = np.zeros((10,20))
|
||||
labels = np.zeros((10,20), int)
|
||||
image[5,5] = 1
|
||||
image[5,15] = .5
|
||||
labels[5,5] = 1
|
||||
labels[5,15] = 2
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 15] = .5
|
||||
labels[5, 5] = 1
|
||||
labels[5, 15] = 2
|
||||
expected = (labels > 0)
|
||||
result = is_local_maximum(image, labels, np.ones((3,3), bool))
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(result == expected))
|
||||
|
||||
def test_01_06_adjacent_different_objects(self):
|
||||
image = np.zeros((10,20))
|
||||
labels = np.zeros((10,20), int)
|
||||
image[5,5] = 1
|
||||
image[5,6] = .5
|
||||
labels[5,5] = 1
|
||||
labels[5,6] = 2
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 6] = .5
|
||||
labels[5, 5] = 1
|
||||
labels[5, 6] = 2
|
||||
expected = (labels > 0)
|
||||
result = is_local_maximum(image, labels, np.ones((3,3), bool))
|
||||
result = is_local_maximum(image, labels, np.ones((3, 3), bool))
|
||||
self.assertTrue(np.all(result == expected))
|
||||
|
||||
|
||||
def test_02_01_four_quadrants(self):
|
||||
np.random.seed(21)
|
||||
image = np.random.uniform(size=(40,60))
|
||||
i,j = np.mgrid[0:40,0:60]
|
||||
image = np.random.uniform(size=(40, 60))
|
||||
i, j = np.mgrid[0:40, 0:60]
|
||||
labels = 1 + (i >= 20) + (j >= 30) * 2
|
||||
i,j = np.mgrid[-3:4,-3:4]
|
||||
footprint = (i*i + j*j <=9)
|
||||
i, j = np.mgrid[-3:4, -3:4]
|
||||
footprint = (i * i + j * j <= 9)
|
||||
expected = np.zeros(image.shape, float)
|
||||
for imin, imax in ((0, 20), (20, 40)):
|
||||
for jmin, jmax in ((0, 30), (30, 60)):
|
||||
expected[imin:imax,jmin:jmax] = scipy.ndimage.maximum_filter(
|
||||
image[imin:imax, jmin:jmax], footprint = footprint)
|
||||
expected[imin:imax, jmin:jmax] = scipy.ndimage.maximum_filter(
|
||||
image[imin:imax, jmin:jmax], footprint=footprint)
|
||||
expected = (expected == image)
|
||||
result = is_local_maximum(image, labels, footprint)
|
||||
self.assertTrue(np.all(result == expected))
|
||||
|
||||
|
||||
def test_03_01_disk_1(self):
|
||||
'''regression test of img-1194, footprint = [1]
|
||||
|
||||
|
||||
Test is_local_maximum when every point is a local maximum
|
||||
'''
|
||||
np.random.seed(31)
|
||||
image = np.random.uniform(size=(10,20))
|
||||
image = np.random.uniform(size=(10, 20))
|
||||
footprint = np.array([[1]])
|
||||
result = is_local_maximum(image, np.ones((10,20)), footprint)
|
||||
result = is_local_maximum(image, np.ones((10, 20)), footprint)
|
||||
self.assertTrue(np.all(result))
|
||||
result = is_local_maximum(image, footprint=footprint)
|
||||
self.assertTrue(np.all(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
||||
|
||||
@@ -24,13 +24,13 @@ All rights reserved.
|
||||
Original author: Lee Kamentsky
|
||||
"""
|
||||
|
||||
from _heapq import heapify, heappush, heappop
|
||||
from _heapq import heappush, heappop
|
||||
import numpy as np
|
||||
import scipy.ndimage
|
||||
from ..filter import rank_order
|
||||
|
||||
from . import _watershed
|
||||
import warnings
|
||||
|
||||
|
||||
def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
"""
|
||||
@@ -87,8 +87,8 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
|
||||
This implementation converts all arguments to specific, lowest common
|
||||
denominator types, then passes these to a C algorithm.
|
||||
|
||||
Markers can be determined manually, or automatically using for example
|
||||
|
||||
Markers can be determined manually, or automatically using for example
|
||||
the local minima of the gradient of the image, or the local maxima of the
|
||||
distance function to the background for separating overlapping objects
|
||||
(see example).
|
||||
@@ -122,28 +122,28 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
The algorithm works also for 3-D images, and can be used for example to
|
||||
separate overlapping spheres.
|
||||
"""
|
||||
|
||||
|
||||
if connectivity == None:
|
||||
c_connectivity = scipy.ndimage.generate_binary_structure(image.ndim, 1)
|
||||
else:
|
||||
c_connectivity = np.array(connectivity, bool)
|
||||
if c_connectivity.ndim != image.ndim:
|
||||
raise ValueError,"Connectivity dimension must be same as image"
|
||||
raise ValueError("Connectivity dimension must be same as image")
|
||||
if offset == None:
|
||||
if any([x%2==0 for x in c_connectivity.shape]):
|
||||
raise ValueError,"Connectivity array must have an unambiguous \
|
||||
center"
|
||||
if any([x % 2 == 0 for x in c_connectivity.shape]):
|
||||
raise ValueError("Connectivity array must have an unambiguous "
|
||||
"center")
|
||||
#
|
||||
# offset to center of connectivity array
|
||||
#
|
||||
offset = np.array(c_connectivity.shape) // 2
|
||||
|
||||
# pad the image, markers, and mask so that we can use the mask to
|
||||
# pad the image, markers, and mask so that we can use the mask to
|
||||
# keep from running off the edges
|
||||
pads = offset
|
||||
|
||||
def pad(im):
|
||||
new_im = np.zeros([i + 2*p for i, p in zip(im.shape, pads)], im.dtype)
|
||||
new_im = np.zeros([i + 2 * p for i, p in zip(im.shape, pads)], im.dtype)
|
||||
new_im[[slice(p, -p, None) for p in pads]] = im
|
||||
return new_im
|
||||
|
||||
@@ -157,18 +157,17 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
c_image = rank_order(image)[0].astype(np.int32)
|
||||
c_markers = np.ascontiguousarray(markers, dtype=np.int32)
|
||||
if c_markers.ndim != c_image.ndim:
|
||||
raise ValueError,\
|
||||
"markers (ndim=%d) must have same # of dimensions "\
|
||||
"as image (ndim=%d)"%(c_markers.ndim, cimage.ndim)
|
||||
raise ValueError("markers (ndim=%d) must have same # of dimensions "
|
||||
"as image (ndim=%d)" % (c_markers.ndim, c_image.ndim))
|
||||
if c_markers.shape != c_image.shape:
|
||||
raise ValueError("image and markers must have the same shape")
|
||||
if mask != None:
|
||||
c_mask = np.ascontiguousarray(mask, dtype=bool)
|
||||
if c_mask.ndim != c_markers.ndim:
|
||||
raise ValueError, "mask must have same # of dimensions as image"
|
||||
raise ValueError("mask must have same # of dimensions as image")
|
||||
if c_markers.shape != c_mask.shape:
|
||||
raise ValueError, "mask must have same shape as image"
|
||||
c_markers[np.logical_not(mask)]=0
|
||||
raise ValueError("mask must have same shape as image")
|
||||
c_markers[np.logical_not(mask)] = 0
|
||||
else:
|
||||
c_mask = None
|
||||
c_output = c_markers.copy()
|
||||
@@ -189,9 +188,8 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
indexes = []
|
||||
ignore = True
|
||||
for j in range(len(c_connectivity.shape)):
|
||||
elems = c_image.shape[j]
|
||||
idx = (i // multiplier) % c_connectivity.shape[j]
|
||||
off = idx - offset[j]
|
||||
idx = (i // multiplier) % c_connectivity.shape[j]
|
||||
off = idx - offset[j]
|
||||
if off:
|
||||
ignore = False
|
||||
offs.append(off)
|
||||
@@ -214,10 +212,10 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
else:
|
||||
c_mask = c_mask.astype(np.int8).flatten()
|
||||
_watershed.watershed(c_image.flatten(),
|
||||
pq, age, c,
|
||||
c_image.ndim,
|
||||
pq, age, c,
|
||||
c_image.ndim,
|
||||
c_mask,
|
||||
np.array(c_image.shape,np.int32),
|
||||
np.array(c_image.shape, np.int32),
|
||||
c_output)
|
||||
c_output = c_output.reshape(c_image.shape)[[slice(1, -1, None)] *
|
||||
image.ndim]
|
||||
@@ -230,18 +228,18 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None):
|
||||
def is_local_maximum(image, labels=None, footprint=None):
|
||||
"""
|
||||
Return a boolean array of points that are local maxima
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image: ndarray (2-D, 3-D, ...)
|
||||
intensity image
|
||||
|
||||
labels: ndarray, optional
|
||||
|
||||
labels: ndarray, optional
|
||||
find maxima only within labels. Zero is reserved for background.
|
||||
|
||||
|
||||
footprint: ndarray of bools, optional
|
||||
binary mask indicating the neighborhood to be examined
|
||||
`footprint` must be a matrix with odd dimensions, the center is taken
|
||||
`footprint` must be a matrix with odd dimensions, the center is taken
|
||||
to be the point in question.
|
||||
|
||||
Returns
|
||||
@@ -289,7 +287,7 @@ def is_local_maximum(image, labels=None, footprint=None):
|
||||
footprint = np.ones([3] * image.ndim, dtype=np.uint8)
|
||||
assert((np.all(footprint.shape) & 1) == 1)
|
||||
footprint = (footprint != 0)
|
||||
footprint_extent = (np.array(footprint.shape)-1) // 2
|
||||
footprint_extent = (np.array(footprint.shape) - 1) // 2
|
||||
if np.all(footprint_extent == 0):
|
||||
return labels > 0
|
||||
result = (labels > 0).copy()
|
||||
@@ -297,17 +295,17 @@ def is_local_maximum(image, labels=None, footprint=None):
|
||||
# Create a labels matrix with zeros at the borders that might be
|
||||
# hit by the footprint.
|
||||
#
|
||||
big_labels = np.zeros(np.array(labels.shape) + footprint_extent*2,
|
||||
big_labels = np.zeros(np.array(labels.shape) + footprint_extent * 2,
|
||||
labels.dtype)
|
||||
big_labels[[slice(fe,-fe) for fe in footprint_extent]] = labels
|
||||
big_labels[[slice(fe, -fe) for fe in footprint_extent]] = labels
|
||||
#
|
||||
# Find the relative indexes of each footprint element
|
||||
#
|
||||
image_strides = np.array(image.strides) // image.dtype.itemsize
|
||||
big_strides = np.array(big_labels.strides) // big_labels.dtype.itemsize
|
||||
result_strides = np.array(result.strides) // result.dtype.itemsize
|
||||
footprint_offsets = np.mgrid[[slice(-fe,fe+1) for fe in footprint_extent]]
|
||||
|
||||
footprint_offsets = np.mgrid[[slice(-fe, fe + 1) for fe in footprint_extent]]
|
||||
|
||||
fp_image_offsets = np.sum(image_strides[:, np.newaxis] *
|
||||
footprint_offsets[:, footprint], 0)
|
||||
fp_big_offsets = np.sum(big_strides[:, np.newaxis] *
|
||||
@@ -315,9 +313,9 @@ def is_local_maximum(image, labels=None, footprint=None):
|
||||
#
|
||||
# Get the index of each labeled pixel in the image and big_labels arrays
|
||||
#
|
||||
indexes = np.mgrid[[slice(0,x) for x in labels.shape]][:, labels > 0]
|
||||
indexes = np.mgrid[[slice(0, x) for x in labels.shape]][:, labels > 0]
|
||||
image_indexes = np.sum(image_strides[:, np.newaxis] * indexes, 0)
|
||||
big_indexes = np.sum(big_strides[:, np.newaxis] *
|
||||
big_indexes = np.sum(big_strides[:, np.newaxis] *
|
||||
(indexes + footprint_extent[:, np.newaxis]), 0)
|
||||
result_indexes = np.sum(result_strides[:, np.newaxis] * indexes, 0)
|
||||
#
|
||||
@@ -335,18 +333,15 @@ def is_local_maximum(image, labels=None, footprint=None):
|
||||
same_label = (big_labels_raveled[big_indexes + fp_big_offset] ==
|
||||
big_labels_raveled[big_indexes])
|
||||
less_than = (image_raveled[image_indexes[same_label]] <
|
||||
image_raveled[image_indexes[same_label]+ fp_image_offset])
|
||||
image_raveled[image_indexes[same_label] + fp_image_offset])
|
||||
result_raveled[result_indexes[same_label][less_than]] = False
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
# ---------------------- deprecated ------------------------------
|
||||
# Deprecate slower pure-Python code, that we keep only for
|
||||
# Deprecate slower pure-Python code, that we keep only for
|
||||
# pedagogical purposes
|
||||
|
||||
|
||||
def __heapify_markers(markers, image):
|
||||
"""Create a priority queue heap with the markers on it"""
|
||||
stride = np.array(image.strides) // image.itemsize
|
||||
@@ -354,24 +349,25 @@ def __heapify_markers(markers, image):
|
||||
ncoords = coords.shape[0]
|
||||
if ncoords > 0:
|
||||
pixels = image[markers != 0]
|
||||
age = np.arange(ncoords)
|
||||
age = np.arange(ncoords)
|
||||
offset = np.zeros(coords.shape[0], int)
|
||||
for i in range(image.ndim):
|
||||
offset = offset + stride[i] * coords[:, i]
|
||||
pq = np.column_stack((pixels, age, offset, coords))
|
||||
# pixels = top priority, age=second
|
||||
ordering = np.lexsort((age, pixels))
|
||||
ordering = np.lexsort((age, pixels))
|
||||
pq = pq[ordering, :]
|
||||
else:
|
||||
pq = np.zeros((0, markers.ndim + 3), int)
|
||||
return (pq, ncoords)
|
||||
|
||||
|
||||
|
||||
def _slow_watershed(image, markers, connectivity=8, mask=None):
|
||||
"""Return a matrix labeled using the watershed algorithm
|
||||
|
||||
|
||||
Use the `watershed` function for a faster execution.
|
||||
This pure Python function is solely for pedagogical purposes.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image: 2-d ndarray of integers
|
||||
@@ -380,16 +376,16 @@ def _slow_watershed(image, markers, connectivity=8, mask=None):
|
||||
markers: 2-d ndarray of integers
|
||||
a two-dimensional matrix marking the basins with the values
|
||||
to be assigned in the label matrix. Zero means not a marker.
|
||||
connectivity: {4, 8}, optional
|
||||
connectivity: {4, 8}, optional
|
||||
either 4 for four-connected or 8 (default) for eight-connected
|
||||
mask: 2-d ndarray of bools, optional
|
||||
mask: 2-d ndarray of bools, optional
|
||||
don't label points in the mask
|
||||
|
||||
Returns
|
||||
Returns
|
||||
-------
|
||||
out: ndarray
|
||||
A labeled matrix of the same type and shape as markers
|
||||
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -409,20 +405,20 @@ def _slow_watershed(image, markers, connectivity=8, mask=None):
|
||||
|
||||
This implementation converts all arguments to specific, lowest common
|
||||
denominator types, then passes these to a C algorithm.
|
||||
|
||||
Markers can be determined manually, or automatically using for example
|
||||
|
||||
Markers can be determined manually, or automatically using for example
|
||||
the local minima of the gradient of the image, or the local maxima of the
|
||||
distance function to the background for separating overlapping objects.
|
||||
"""
|
||||
if connectivity not in (4, 8):
|
||||
raise ValueError("Connectivity was %d: it should be either \
|
||||
four or eight" %(connectivity))
|
||||
|
||||
four or eight" % (connectivity))
|
||||
|
||||
image = np.array(image)
|
||||
markers = np.array(markers)
|
||||
labels = markers.copy()
|
||||
max_x = markers.shape[0]
|
||||
max_y = markers.shape[1]
|
||||
max_x = markers.shape[0]
|
||||
max_y = markers.shape[1]
|
||||
if connectivity == 4:
|
||||
connect_increments = ((1, 0), (0, 1), (-1, 0), (0, -1))
|
||||
else:
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
if __name__ == "__main__":
|
||||
from skimage.scripts import skivi
|
||||
skivi.main()
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""skimage viewer"""
|
||||
|
||||
|
||||
def main():
|
||||
import skimage.io as io
|
||||
import sys
|
||||
@@ -10,4 +12,3 @@ def main():
|
||||
io.use_plugin('qt')
|
||||
io.imshow(io.imread(sys.argv[1]), fancy=True)
|
||||
io.show()
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ def _make_graph_edges_3d(n_x, n_y, n_z):
|
||||
|
||||
|
||||
def _compute_weights_3d(data, beta=130, eps=1.e-6):
|
||||
gradients = _compute_gradients_3d(data) ** 2
|
||||
gradients = _compute_gradients_3d(data)**2
|
||||
beta /= 10 * data.std()
|
||||
gradients *= beta
|
||||
weights = np.exp(- gradients)
|
||||
|
||||
+8
-8
@@ -1,21 +1,22 @@
|
||||
import os
|
||||
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
|
||||
config = Configuration('skimage', parent_package, top_path)
|
||||
|
||||
config.add_subpackage('graph')
|
||||
config.add_subpackage('io')
|
||||
config.add_subpackage('morphology')
|
||||
config.add_subpackage('filter')
|
||||
config.add_subpackage('transform')
|
||||
config.add_subpackage('data')
|
||||
config.add_subpackage('util')
|
||||
config.add_subpackage('color')
|
||||
config.add_subpackage('data')
|
||||
config.add_subpackage('draw')
|
||||
config.add_subpackage('feature')
|
||||
config.add_subpackage('filter')
|
||||
config.add_subpackage('graph')
|
||||
config.add_subpackage('io')
|
||||
config.add_subpackage('measure')
|
||||
config.add_subpackage('morphology')
|
||||
config.add_subpackage('transform')
|
||||
config.add_subpackage('util')
|
||||
|
||||
def add_test_directories(arg, dirname, fnames):
|
||||
if dirname.split(os.path.sep)[-1] == 'tests':
|
||||
@@ -37,4 +38,3 @@ if __name__ == "__main__":
|
||||
|
||||
config = configuration(top_path='').todict()
|
||||
setup(**config)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ def _swirl_mapping(xy, center, rotation, strength, radius):
|
||||
|
||||
return xy
|
||||
|
||||
|
||||
def swirl(image, center=None, strength=1, radius=100, rotation=0,
|
||||
output_shape=None, order=1, mode='constant', cval=0):
|
||||
"""Perform a swirl transformation.
|
||||
|
||||
@@ -9,6 +9,7 @@ __docformat__ = "restructuredtext en"
|
||||
import numpy as np
|
||||
from numpy import roll, newaxis
|
||||
|
||||
|
||||
def frt2(a):
|
||||
"""Compute the 2-dimensional finite radon transform (FRT) for an n x n
|
||||
integer array.
|
||||
@@ -65,7 +66,7 @@ def frt2(a):
|
||||
|
||||
ai = a.copy()
|
||||
n = ai.shape[0]
|
||||
f = np.empty((n+1, n), np.uint32)
|
||||
f = np.empty((n + 1, n), np.uint32)
|
||||
f[0] = ai.sum(axis=0)
|
||||
for m in range(1, n):
|
||||
# Roll the pth row of ai left by p places
|
||||
@@ -125,7 +126,7 @@ def ifrt2(a):
|
||||
and Electron Physics, 139 (2006)
|
||||
|
||||
"""
|
||||
if a.ndim != 2 or a.shape[0] != a.shape[1]+1:
|
||||
if a.ndim != 2 or a.shape[0] != a.shape[1] + 1:
|
||||
raise ValueError("Input must be an (n+1) row x n column, 2-D array")
|
||||
|
||||
ai = a.copy()[:-1]
|
||||
@@ -138,5 +139,5 @@ def ifrt2(a):
|
||||
ai[row] = roll(ai[row], row)
|
||||
f[m] = ai.sum(axis=0)
|
||||
f += a[-1][newaxis].T
|
||||
f = (f - ai[0].sum())/n
|
||||
f = (f - ai[0].sum()) / n
|
||||
return f
|
||||
|
||||
@@ -3,7 +3,8 @@ __all__ = ['hough', 'probabilistic_hough']
|
||||
from itertools import izip as zip
|
||||
|
||||
import numpy as np
|
||||
from ._hough_transform import _probabilistic_hough
|
||||
from ._hough_transform import _probabilistic_hough
|
||||
|
||||
|
||||
def _hough(img, theta=None):
|
||||
if img.ndim != 2:
|
||||
@@ -68,28 +69,28 @@ def probabilistic_hough(img, threshold=10, line_length=50, line_gap=10, theta=No
|
||||
img : (M, N) ndarray
|
||||
Input image with nonzero values representing edges.
|
||||
threshold : int
|
||||
Threshold
|
||||
Threshold
|
||||
line_length : int, optional (default 50)
|
||||
Minimum accepted length of detected lines.
|
||||
Increase the parameter to extract longer lines.
|
||||
line_gap : int, optional, (default 10)
|
||||
Maximum gap between pixels to still form a line.
|
||||
Maximum gap between pixels to still form a line.
|
||||
Increase the parameter to merge broken lines more aggresively.
|
||||
theta : 1D ndarray, dtype=double, optional, default (-pi/2 .. pi/2)
|
||||
Angles at which to compute the transform, in radians.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : list
|
||||
List of lines identified, lines in format ((x0, y0), (x1, y0)), indicating
|
||||
List of lines identified, lines in format ((x0, y0), (x1, y0)), indicating
|
||||
line start and end.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] C. Galamhos, J. Matas and J. Kittler,"Progressive probabilistic Hough
|
||||
transform for line detection", in IEEE Computer Society Conference on
|
||||
Computer Vision and Pattern Recognition, 1999.
|
||||
"""
|
||||
.. [1] C. Galamhos, J. Matas and J. Kittler,"Progressive probabilistic Hough
|
||||
transform for line detection", in IEEE Computer Society Conference on
|
||||
Computer Vision and Pattern Recognition, 1999.
|
||||
"""
|
||||
return _probabilistic_hough(img, threshold, line_length, line_gap, theta)
|
||||
|
||||
|
||||
@@ -141,5 +142,3 @@ def hough(img, theta=None):
|
||||
|
||||
"""
|
||||
return _hough(img, theta)
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ def integral_image(x):
|
||||
"""
|
||||
return x.cumsum(1).cumsum(0)
|
||||
|
||||
|
||||
def integrate(ii, r0, c0, r1, c1):
|
||||
"""Use an integral image to integrate over a given window.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ __all__ = ['homography']
|
||||
|
||||
eps = np.finfo(float).eps
|
||||
|
||||
|
||||
def homography(image, H, output_shape=None, order=1,
|
||||
mode='constant', cval=0.):
|
||||
"""Perform a projective transformation (homography) on an image.
|
||||
@@ -86,7 +87,7 @@ def homography(image, H, output_shape=None, order=1,
|
||||
[ 0, 0, 0, 255, 0],
|
||||
[ 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
|
||||
"""
|
||||
if image.ndim < 2:
|
||||
raise ValueError("Input must have more than 1 dimension.")
|
||||
@@ -120,13 +121,13 @@ def homography(image, H, output_shape=None, order=1,
|
||||
tf_coords[..., :2] /= tf_coords[..., 2, np.newaxis]
|
||||
|
||||
# y-coordinate mapping
|
||||
_stackcopy(coords[0,...], tf_coords[...,1])
|
||||
_stackcopy(coords[0, ...], tf_coords[..., 1])
|
||||
|
||||
# x-coordinate mapping
|
||||
_stackcopy(coords[1,...], tf_coords[...,0])
|
||||
_stackcopy(coords[1, ...], tf_coords[..., 0])
|
||||
|
||||
# colour-coordinate mapping
|
||||
coords[2,...] = range(bands)
|
||||
coords[2, ...] = range(bands)
|
||||
|
||||
# Prefilter not necessary for order 1 interpolation
|
||||
prefilter = order > 1
|
||||
|
||||
@@ -43,7 +43,7 @@ def radon(image, theta=None):
|
||||
if theta == None:
|
||||
theta = np.arange(180)
|
||||
height, width = image.shape
|
||||
diagonal = np.sqrt(height ** 2 + width ** 2)
|
||||
diagonal = np.sqrt(height**2 + width**2)
|
||||
heightpad = np.ceil(diagonal - height)
|
||||
widthpad = np.ceil(diagonal - width)
|
||||
padded_image = np.zeros((int(height + heightpad),
|
||||
@@ -66,7 +66,6 @@ def radon(image, theta=None):
|
||||
[0, 1, dh],
|
||||
[0, 0, 1]])
|
||||
|
||||
|
||||
def build_rotation(theta):
|
||||
T = -np.deg2rad(theta)
|
||||
|
||||
@@ -80,7 +79,7 @@ def radon(image, theta=None):
|
||||
rotated = homography(padded_image,
|
||||
build_rotation(-theta[i]))
|
||||
|
||||
out[:,i] = rotated.sum(0)[::-1]
|
||||
out[:, i] = rotated.sum(0)[::-1]
|
||||
|
||||
return out
|
||||
|
||||
@@ -131,17 +130,16 @@ def iradon(radon_image, theta=None, output_size=None,
|
||||
th = (np.pi / 180.0) * theta
|
||||
# if output size not specified, estimate from input radon image
|
||||
if not output_size:
|
||||
output_size = int(np.floor(np.sqrt((radon_image.shape[0]) ** 2 / 2.0)))
|
||||
output_size = int(np.floor(np.sqrt((radon_image.shape[0])**2 / 2.0)))
|
||||
n = radon_image.shape[0]
|
||||
|
||||
img = radon_image.copy()
|
||||
# resize image to next power of two for fourier analysis
|
||||
# speeds up fourier and lessens artifacts
|
||||
order = max(64., 2 ** np.ceil(np.log(2 * n) / np.log(2)))
|
||||
order = max(64., 2**np.ceil(np.log(2 * n) / np.log(2)))
|
||||
# zero pad input image
|
||||
img.resize((order, img.shape[1]))
|
||||
# construct the fourier filter
|
||||
freqs = np.zeros((order, 1))
|
||||
|
||||
f = fftshift(abs(np.mgrid[-1:1:2 / order])).reshape(-1, 1)
|
||||
w = 2 * np.pi * f
|
||||
@@ -151,11 +149,11 @@ def iradon(radon_image, theta=None, output_size=None,
|
||||
elif filter == "shepp-logan":
|
||||
f[1:] = f[1:] * np.sin(w[1:] / 2) / (w[1:] / 2)
|
||||
elif filter == "cosine":
|
||||
f[1:] = f[1:] * np.cos(w[1:] / 2)
|
||||
f[1:] = f[1:] * np.cos(w[1:] / 2)
|
||||
elif filter == "hamming":
|
||||
f[1:] = f[1:] * (0.54 + 0.46 * np.cos(w[1:]))
|
||||
f[1:] = f[1:] * (0.54 + 0.46 * np.cos(w[1:]))
|
||||
elif filter == "hann":
|
||||
f[1:] = f[1:] * (1 + np.cos(w[1:])) / 2
|
||||
f[1:] = f[1:] * (1 + np.cos(w[1:])) / 2
|
||||
elif filter == None:
|
||||
f[1:] = 1
|
||||
else:
|
||||
@@ -184,13 +182,13 @@ def iradon(radon_image, theta=None, output_size=None,
|
||||
((((k > 0) & (k < n)) * k) - 1).astype(np.int), i]
|
||||
elif interpolation == "linear":
|
||||
for i in range(len(theta)):
|
||||
t = xpr*np.sin(th[i]) - ypr*np.cos(th[i])
|
||||
a = np.floor(t)
|
||||
b = mid_index + a
|
||||
b0 = ((((b + 1 > 0) & (b + 1 < n)) * (b + 1)) - 1).astype(np.int)
|
||||
b1 = ((((b > 0) & (b < n)) * b) - 1).astype(np.int)
|
||||
reconstructed += (t - a) * radon_filtered[b0, i] + \
|
||||
(a - t + 1) * radon_filtered[b1, i]
|
||||
t = xpr * np.sin(th[i]) - ypr * np.cos(th[i])
|
||||
a = np.floor(t)
|
||||
b = mid_index + a
|
||||
b0 = ((((b + 1 > 0) & (b + 1 < n)) * (b + 1)) - 1).astype(np.int)
|
||||
b1 = ((((b > 0) & (b < n)) * b) - 1).astype(np.int)
|
||||
reconstructed += (t - a) * radon_filtered[b0, i] + \
|
||||
(a - t + 1) * radon_filtered[b1, i]
|
||||
else:
|
||||
raise ValueError("Unknown interpolation: %s" % interpolation)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
|
||||
@@ -21,16 +22,15 @@ def configuration(parent_package='', top_path=None):
|
||||
config.add_extension('_project', sources=['_project.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
|
||||
|
||||
return config
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer = 'Scikits-image Developers',
|
||||
author = 'Scikits-image Developers',
|
||||
maintainer_email = 'scikits-image@googlegroups.com',
|
||||
description = 'Transforms',
|
||||
url = 'https://github.com/scikits-image/scikits-image',
|
||||
license = 'SciPy License (BSD Style)',
|
||||
setup(maintainer='Scikits-image Developers',
|
||||
author='Scikits-image Developers',
|
||||
maintainer_email='scikits-image@googlegroups.com',
|
||||
description='Transforms',
|
||||
url='https://github.com/scikits-image/scikits-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ from numpy.testing import *
|
||||
|
||||
from skimage.transform import *
|
||||
|
||||
|
||||
def test_frt():
|
||||
SIZE = 59
|
||||
try:
|
||||
@@ -15,4 +16,4 @@ def test_frt():
|
||||
L = np.tri(SIZE, dtype=np.int32) + np.tri(SIZE, dtype=np.int32)[::-1]
|
||||
f = frt2(L)
|
||||
fi = ifrt2(f)
|
||||
assert len(np.nonzero(L-fi)[0]) == 0
|
||||
assert len(np.nonzero(L - fi)[0]) == 0
|
||||
|
||||
@@ -5,6 +5,7 @@ import skimage.transform as tf
|
||||
import skimage.transform.hough_transform as ht
|
||||
from skimage.transform import probabilistic_hough
|
||||
|
||||
|
||||
def append_desc(func, description):
|
||||
"""Append the test function ``func`` and append
|
||||
``description`` to its name.
|
||||
@@ -15,6 +16,7 @@ def append_desc(func, description):
|
||||
|
||||
from skimage.transform import *
|
||||
|
||||
|
||||
def test_hough():
|
||||
# Generate a test image
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
@@ -39,6 +41,7 @@ def test_hough_angles():
|
||||
|
||||
assert_equal(len(angles), 10)
|
||||
|
||||
|
||||
def test_py_hough():
|
||||
ht._hough, fast_hough = ht._py_hough, ht._hough
|
||||
|
||||
@@ -47,6 +50,7 @@ def test_py_hough():
|
||||
|
||||
tf._hough = fast_hough
|
||||
|
||||
|
||||
def test_probabilistic_hough():
|
||||
# Generate a test image
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
@@ -55,8 +59,9 @@ def test_probabilistic_hough():
|
||||
img[i, i] = 100
|
||||
# decrease default theta sampling because similar orientations may confuse
|
||||
# as mentioned in article of Galambos et al
|
||||
theta=np.linspace(0, np.pi, 45)
|
||||
lines = probabilistic_hough(img, theta=theta, threshold=10, line_length=10, line_gap=1)
|
||||
theta = np.linspace(0, np.pi, 45)
|
||||
lines = probabilistic_hough(img, theta=theta, threshold=10, line_length=10,
|
||||
line_gap=1)
|
||||
# sort the lines according to the x-axis
|
||||
sorted_lines = []
|
||||
for line in lines:
|
||||
@@ -69,4 +74,3 @@ def test_probabilistic_hough():
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_module_suite()
|
||||
|
||||
|
||||
@@ -6,19 +6,22 @@ from skimage.transform import integral_image, integrate
|
||||
x = (np.random.random((50, 50)) * 255).astype(np.uint8)
|
||||
s = integral_image(x)
|
||||
|
||||
|
||||
def test_validity():
|
||||
y = np.arange(12).reshape((4,3))
|
||||
y = np.arange(12).reshape((4, 3))
|
||||
|
||||
y = (np.random.random((50, 50)) * 255).astype(np.uint8)
|
||||
assert_equal(integral_image(y)[-1, -1],
|
||||
y.sum())
|
||||
|
||||
|
||||
def test_basic():
|
||||
assert_equal(x[12:24, 10:20].sum(), integrate(s, 12, 10, 23, 19))
|
||||
assert_equal(x[:20, :20].sum(), integrate(s, 0, 0, 19, 19))
|
||||
assert_equal(x[:20, 10:20].sum(), integrate(s, 0, 10, 19, 19))
|
||||
assert_equal(x[10:20, :20].sum(), integrate(s, 10, 0, 19, 19))
|
||||
|
||||
|
||||
def test_single():
|
||||
assert_equal(x[0, 0], integrate(s, 0, 0, 0, 0))
|
||||
assert_equal(x[10, 10], integrate(s, 10, 10, 10, 10))
|
||||
|
||||
@@ -6,27 +6,30 @@ from skimage.transform import homography, fast_homography
|
||||
from skimage import data
|
||||
from skimage.color import rgb2gray
|
||||
|
||||
|
||||
def test_stackcopy():
|
||||
layers = 4
|
||||
x = np.empty((3, 3, layers))
|
||||
y = np.eye(3, 3)
|
||||
_stackcopy(x, y)
|
||||
for i in range(layers):
|
||||
assert_array_almost_equal(x[...,i], y)
|
||||
assert_array_almost_equal(x[..., i], y)
|
||||
|
||||
|
||||
def test_homography():
|
||||
x = np.arange(9, dtype=np.uint8).reshape((3, 3)) + 1
|
||||
theta = -np.pi/2
|
||||
M = np.array([[np.cos(theta),-np.sin(theta),0],
|
||||
[np.sin(theta), np.cos(theta),2],
|
||||
[0, 0, 1]])
|
||||
theta = -np.pi / 2
|
||||
M = np.array([[np.cos(theta), -np.sin(theta), 0],
|
||||
[np.sin(theta), np.cos(theta), 2],
|
||||
[0, 0, 1]])
|
||||
x90 = homography(x, M, order=1)
|
||||
assert_array_almost_equal(x90, np.rot90(x))
|
||||
|
||||
|
||||
def test_fast_homography():
|
||||
img = rgb2gray(data.lena()).astype(np.uint8)
|
||||
img = img[:, :100]
|
||||
|
||||
|
||||
theta = np.deg2rad(30)
|
||||
scale = 0.5
|
||||
tx, ty = 50, 50
|
||||
@@ -53,7 +56,7 @@ def test_fast_homography():
|
||||
|
||||
d = np.mean(np.abs(p0 - p1))
|
||||
assert d < 0.2
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from numpy.testing import run_module_suite
|
||||
|
||||
@@ -4,12 +4,14 @@ import numpy as np
|
||||
from numpy.testing import *
|
||||
from skimage.transform import *
|
||||
|
||||
|
||||
def rescale(x):
|
||||
x = x.astype(float)
|
||||
x -= x.min()
|
||||
x /= x.max()
|
||||
return x
|
||||
|
||||
|
||||
def test_radon_iradon():
|
||||
size = 100
|
||||
debug = False
|
||||
@@ -38,6 +40,7 @@ def test_radon_iradon():
|
||||
image = np.tri(size) + np.tri(size)[::-1]
|
||||
reconstructed = iradon(radon(image), filter="ramp", interpolation="nearest")
|
||||
|
||||
|
||||
def test_iradon_angles():
|
||||
"""
|
||||
Test with different number of projections
|
||||
@@ -60,10 +63,12 @@ def test_iradon_angles():
|
||||
s = radon_image_80.sum(axis=0)
|
||||
assert np.allclose(s, s[0], rtol=0.01)
|
||||
reconstructed = iradon(radon_image_80)
|
||||
delta_80 = np.mean(abs(image/np.max(image) - reconstructed/np.max(reconstructed)))
|
||||
delta_80 = np.mean(abs(image / np.max(image) -
|
||||
reconstructed / np.max(reconstructed)))
|
||||
# Loss of quality when the number of projections is reduced
|
||||
assert delta_80 > delta_200
|
||||
|
||||
|
||||
def test_radon_minimal():
|
||||
"""
|
||||
Test for small images for various angles
|
||||
|
||||
@@ -94,7 +94,7 @@ def convert(image, dtype, force_copy=False, uniform=False):
|
||||
def _dtype2(kind, bits, itemsize=1):
|
||||
# Return dtype of `kind` that can store a `bits` wide unsigned int
|
||||
c = lambda x, y: x <= y if kind == 'u' else x < y
|
||||
s = next(i for i in (itemsize, ) + (2, 4, 8) if c(bits, i*8))
|
||||
s = next(i for i in (itemsize, ) + (2, 4, 8) if c(bits, i * 8))
|
||||
return np.dtype(kind + str(s))
|
||||
|
||||
def _scale(a, n, m, copy=True):
|
||||
@@ -205,26 +205,26 @@ def convert(image, dtype, force_copy=False, uniform=False):
|
||||
if kind_in == 'u':
|
||||
if kind == 'i':
|
||||
# unsigned integer -> signed integer
|
||||
image = _scale(image, 8*itemsize_in, 8*itemsize-1)
|
||||
image = _scale(image, 8 * itemsize_in, 8 * itemsize - 1)
|
||||
return image.view(dtype)
|
||||
else:
|
||||
# unsigned integer -> unsigned integer
|
||||
return _scale(image, 8*itemsize_in, 8*itemsize)
|
||||
return _scale(image, 8 * itemsize_in, 8 * itemsize)
|
||||
|
||||
if kind == 'u':
|
||||
# signed integer -> unsigned integer
|
||||
sign_loss()
|
||||
image = _scale(image, 8*itemsize_in-1, 8*itemsize)
|
||||
image = _scale(image, 8 * itemsize_in - 1, 8 * itemsize)
|
||||
result = np.empty(image.shape, dtype)
|
||||
np.maximum(image, 0, out=result, dtype=image.dtype, casting='unsafe')
|
||||
return result
|
||||
|
||||
# signed integer -> signed integer
|
||||
if itemsize_in > itemsize:
|
||||
return _scale(image, 8*itemsize_in-1, 8*itemsize-1)
|
||||
image = image.astype(_dtype2('i', itemsize*8))
|
||||
return _scale(image, 8 * itemsize_in - 1, 8 * itemsize - 1)
|
||||
image = image.astype(_dtype2('i', itemsize * 8))
|
||||
image -= imin_in
|
||||
image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False)
|
||||
image = _scale(image, 8 * itemsize_in, 8 * itemsize, copy=False)
|
||||
image += imin
|
||||
return dtype(image)
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ def montage2d(arr_in, fill='mean', rescale_intensity=False):
|
||||
if fill == 'mean':
|
||||
fill = arr_in.mean()
|
||||
|
||||
n_missing = int((alpha ** 2.) - n_images)
|
||||
n_missing = int((alpha**2.) - n_images)
|
||||
missing = np.ones((n_missing, height, width), dtype=arr_in.dtype) * fill
|
||||
arr_out = np.vstack((arr_in, missing))
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user