From 13957123b7824b6520bb3887dc845082af26d413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 17 Aug 2013 18:00:53 +0200 Subject: [PATCH] Make moments functions public, rename, add tests --- skimage/measure/__init__.py | 7 +- skimage/measure/_moments.pyx | 135 +++++++++++++++++++++- skimage/measure/_regionprops.py | 46 ++++---- skimage/measure/tests/test_regionprops.py | 24 ++-- 4 files changed, 170 insertions(+), 42 deletions(-) diff --git a/skimage/measure/__init__.py b/skimage/measure/__init__.py index 423aa9a7..616071d3 100755 --- a/skimage/measure/__init__.py +++ b/skimage/measure/__init__.py @@ -2,6 +2,7 @@ from .find_contours import find_contours from ._regionprops import regionprops, perimeter from ._structural_similarity import structural_similarity from ._polygon import approximate_polygon, subdivide_polygon +from ._moments import moments, moments_central, moments_normalized, moments_hu from .fit import LineModel, CircleModel, EllipseModel, ransac from .block import block_reduce @@ -16,4 +17,8 @@ __all__ = ['find_contours', 'CircleModel', 'EllipseModel', 'ransac', - 'block_reduce'] + 'block_reduce', + 'moments', + 'moments_central', + 'moments_normalized', + 'moments_hu'] diff --git a/skimage/measure/_moments.pyx b/skimage/measure/_moments.pyx index f58f9d63..104acf8f 100644 --- a/skimage/measure/_moments.pyx +++ b/skimage/measure/_moments.pyx @@ -4,15 +4,79 @@ #cython: wraparound=False import numpy as np -cimport numpy as cnp - def moments(double[:, :] image, Py_ssize_t order=3): - return central_moments(image, 0, 0, order) + """Calculate all raw image moments up to a certain order. + + The following properties can be calculated from raw image moments: + * Area as ``m[0, 0]``. + * Centroid as {``m[0, 1] / m[0, 0]``, ``m[1, 0] / m[0, 0]``}. + + Note that raw moments are whether translation, scale nor rotation + invariant. + + Parameters + ---------- + image : 2D double array + Rasterized shape as image. + order : int, optional + Maximum order of moments. Default is 3. + + Returns + ------- + m : (``order + 1``, ``order + 1``) array + Raw image moments. + + References + ---------- + .. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing: + Core Algorithms. Springer-Verlag, London, 2009. + .. [2] B. Jähne. Digital Image Processing. Springer-Verlag, + Berlin-Heidelberg, 6. edition, 2005. + .. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image + Features, from Lecture notes in computer science, p. 676. Springer, + Berlin, 1993. + .. [4] http://en.wikipedia.org/wiki/Image_moment + + """ + return moments_central(image, 0, 0, order) -def central_moments(double[:, :] image, double cr, double cc, +def moments_central(double[:, :] image, double cr, double cc, Py_ssize_t order=3): + """Calculate all central image moments up to a certain order. + + Note that central moments are translation invariant but not scale and + rotation invariant. + + Parameters + ---------- + image : 2D double array + Rasterized shape as image. + cr : double + Center row coordinate. + cc : double + Center column coordinate. + order : int, optional + Maximum order of moments. Default is 3. + + Returns + ------- + mu : (``order + 1``, ``order + 1``) array + Central image moments. + + References + ---------- + .. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing: + Core Algorithms. Springer-Verlag, London, 2009. + .. [2] B. Jähne. Digital Image Processing. Springer-Verlag, + Berlin-Heidelberg, 6. edition, 2005. + .. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image + Features, from Lecture notes in computer science, p. 676. Springer, + Berlin, 1993. + .. [4] http://en.wikipedia.org/wiki/Image_moment + + """ cdef Py_ssize_t p, q, r, c cdef double[:, ::1] mu = np.zeros((order + 1, order + 1), dtype=np.double) for p in range(order + 1): @@ -23,7 +87,36 @@ def central_moments(double[:, :] image, double cr, double cc, return np.asarray(mu) -def normalized_moments(double[:, :] mu, Py_ssize_t order=3): +def moments_normalized(double[:, :] mu, Py_ssize_t order=3): + """Calculate all normalized central image moments up to a certain order. + + Note that normalized central moments are translation and scale invariant + but not rotation invariant. + + Parameters + ---------- + mu : (M, M) array + Central image moments, where M must be > ``order``. + order : int, optional + Maximum order of moments. Default is 3. + + Returns + ------- + nu : (``order + 1``, ``order + 1``) array + Normalized central image moments. + + References + ---------- + .. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing: + Core Algorithms. Springer-Verlag, London, 2009. + .. [2] B. Jähne. Digital Image Processing. Springer-Verlag, + Berlin-Heidelberg, 6. edition, 2005. + .. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image + Features, from Lecture notes in computer science, p. 676. Springer, + Berlin, 1993. + .. [4] http://en.wikipedia.org/wiki/Image_moment + + """ cdef Py_ssize_t p, q cdef double[:, ::1] nu = np.zeros((order + 1, order + 1), dtype=np.double) for p in range(order + 1): @@ -35,7 +128,37 @@ def normalized_moments(double[:, :] mu, Py_ssize_t order=3): return np.asarray(nu) -def hu_moments(double[:, :] nu): +def moments_hu(double[:, :] nu): + """Calculate Hu's set of image moments. + + Note that this set of moments is proofed to be translation, scale and + rotation invariant. + + Parameters + ---------- + nu : (M, M) array + Normalized central image moments, where M must be > 4. + + Returns + ------- + nu : (7, 1) array + Hu's set of image moments. + + References + ---------- + .. [1] M. K. Hu, "Visual Pattern Recognition by Moment Invariants", + IRE Trans. Info. Theory, vol. IT-8, pp. 179-187, 1962 + .. [2] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing: + Core Algorithms. Springer-Verlag, London, 2009. + .. [3] B. Jähne. Digital Image Processing. Springer-Verlag, + Berlin-Heidelberg, 6. edition, 2005. + .. [4] T. H. Reiss. Recognizing Planar Objects Using Invariant Image + Features, from Lecture notes in computer science, p. 676. Springer, + Berlin, 1993. + .. [5] http://en.wikipedia.org/wiki/Image_moment + + + """ cdef double[::1] hu = np.zeros((7, ), dtype=np.double) cdef double t0 = nu[3, 0] + nu[1, 2] cdef double t1 = nu[2, 1] + nu[0, 3] diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index dc9ff644..35952b48 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -18,7 +18,7 @@ STREL_8 = np.ones((3, 3), 'int8') PROPS = { 'Area': 'area', 'BoundingBox': 'bbox', - 'CentralMoments': 'central_moments', + 'CentralMoments': 'moments_central', 'Centroid': 'centroid', 'ConvexArea': 'convex_area', # 'ConvexHull', @@ -31,7 +31,7 @@ PROPS = { # 'Extrema', 'FilledArea': 'filled_area', 'FilledImage': 'filled_image', - 'HuMoments': 'hu_moments', + 'HuMoments': 'moments_hu', 'Image': 'image', 'Label': 'label', 'MajorAxisLength': 'major_axis_length', @@ -40,7 +40,7 @@ PROPS = { 'MinIntensity': 'min_intensity', 'MinorAxisLength': 'minor_axis_length', 'Moments': 'moments', - 'NormalizedMoments': 'normalized_moments', + 'NormalizedMoments': 'moments_normalized', 'Orientation': 'orientation', 'Perimeter': 'perimeter', # 'PixelIdxList', @@ -49,9 +49,9 @@ PROPS = { # 'SubarrayIdx' 'WeightedCentralMoments': 'weighted_central_moments', 'WeightedCentroid': 'weighted_centroid', - 'WeightedHuMoments': 'weighted_hu_moments', + 'WeightedHuMoments': 'weighted_moments_hu', 'WeightedMoments': 'weighted_moments', - 'WeightedNormalizedMoments': 'weighted_normalized_moments' + 'WeightedNormalizedMoments': 'weighted_moments_normalized' } @@ -128,9 +128,9 @@ class _RegionProperties(object): return row + self._slice[0].start, col + self._slice[1].start @_cached_property - def central_moments(self): + def moments_central(self): row, col = self.local_centroid - return _moments.central_moments(self._image_double, row, col, 3) + return _moments.moments_central(self._image_double, row, col, 3) @_cached_property def convex_area(self): @@ -177,8 +177,8 @@ class _RegionProperties(object): return ndimage.binary_fill_holes(self.image, STREL_8) @_cached_property - def hu_moments(self): - return _moments.hu_moments(self.normalized_moments) + def moments_hu(self): + return _moments.moments_hu(self.moments_normalized) @_cached_property def image(self): @@ -190,7 +190,7 @@ class _RegionProperties(object): @_cached_property def inertia_tensor(self): - mu = self.central_moments + mu = self.moments_central a = mu[2, 0] / mu[0, 0] b = -mu[1, 1] / mu[0, 0] c = mu[0, 2] / mu[0, 0] @@ -248,8 +248,8 @@ class _RegionProperties(object): return 4 * sqrt(l2) @_cached_property - def normalized_moments(self): - return _moments.normalized_moments(self.central_moments, 3) + def moments_normalized(self): + return _moments.moments_normalized(self.moments_central, 3) @_cached_property def orientation(self): @@ -274,7 +274,7 @@ class _RegionProperties(object): @_cached_property def weighted_central_moments(self): row, col = self.weighted_local_centroid - return _moments.central_moments(self._intensity_image_double, + return _moments.moments_central(self._intensity_image_double, row, col, 3) @_cached_property @@ -290,16 +290,16 @@ class _RegionProperties(object): return row, col @_cached_property - def weighted_hu_moments(self): - return _moments.hu_moments(self.weighted_normalized_moments) + def weighted_moments_hu(self): + return _moments.moments_hu(self.weighted_moments_normalized) @_cached_property def weighted_moments(self): - return _moments.central_moments(self._intensity_image_double, 0, 0, 3) + return _moments.moments_central(self._intensity_image_double, 0, 0, 3) @_cached_property - def weighted_normalized_moments(self): - return _moments.normalized_moments(self.weighted_central_moments, 3) + def weighted_moments_normalized(self): + return _moments.moments_normalized(self.weighted_central_moments, 3) def __getitem__(self, key): value = getattr(self, key, None) @@ -347,7 +347,7 @@ def regionprops(label_image, properties=None, Number of pixels of region. **bbox** : tuple Bounding box `(min_row, min_col, max_row, max_col)` - **central_moments** : (3, 3) ndarray + **moments_central** : (3, 3) ndarray Central moments (translation invariant) up to 3rd order:: mu_ji = sum{ array(x, y) * (x - x_c)^j * (y - y_c)^i } @@ -379,7 +379,7 @@ def regionprops(label_image, properties=None, **filled_image** : (H, J) ndarray Binary region image with filled holes which has the same size as bounding box. - **hu_moments** : tuple + **moments_hu** : tuple Hu moments (translation, scale and rotation invariant). **image** : (H, J) ndarray Sliced binary region image which has the same size as bounding box. @@ -407,7 +407,7 @@ def regionprops(label_image, properties=None, m_ji = sum{ array(x, y) * x^j * y^i } where the sum is over the `x`, `y` coordinates of the region. - **normalized_moments** : (3, 3) ndarray + **moments_normalized** : (3, 3) ndarray Normalized moments (translation and scale invariant) up to 3rd order:: nu_ji = mu_ji / m_00^[(i+j)/2 + 1] @@ -433,7 +433,7 @@ def regionprops(label_image, properties=None, **weighted_centroid** : array Centroid coordinate tuple `(row, col)` weighted with intensity image. - **weighted_hu_moments** : tuple + **weighted_moments_hu** : tuple Hu moments (translation, scale and rotation invariant) of intensity image. **weighted_moments** : (3, 3) ndarray @@ -442,7 +442,7 @@ def regionprops(label_image, properties=None, wm_ji = sum{ array(x, y) * x^j * y^i } where the sum is over the `x`, `y` coordinates of the region. - **weighted_normalized_moments** : (3, 3) ndarray + **weighted_moments_normalized** : (3, 3) ndarray Normalized moments (translation and scale invariant) of intensity image up to 3rd order:: diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 29da0456..da6bb421 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -47,8 +47,8 @@ def test_bbox(): assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1)) -def test_central_moments(): - mu = regionprops(SAMPLE)[0].central_moments +def test_moments_central(): + mu = regionprops(SAMPLE)[0].moments_central # determined with OpenCV assert_almost_equal(mu[0,2], 436.00000000000045) # different from OpenCV results, bug in OpenCV @@ -129,8 +129,8 @@ def test_extent(): assert_almost_equal(extent, 0.4) -def test_hu_moments(): - hu = regionprops(SAMPLE)[0].hu_moments +def test_moments_hu(): + hu = regionprops(SAMPLE)[0].moments_hu ref = np.array([ 3.27117627e-01, 2.63869194e-02, @@ -216,8 +216,8 @@ def test_moments(): assert_almost_equal(m[3,0], 95588.0) -def test_normalized_moments(): - nu = regionprops(SAMPLE)[0].normalized_moments +def test_moments_normalized(): + nu = regionprops(SAMPLE)[0].moments_normalized # determined with OpenCV assert_almost_equal(nu[0,2], 0.08410493827160502) assert_almost_equal(nu[1,1], -0.016846707818929982) @@ -260,9 +260,9 @@ def test_solidity(): assert_almost_equal(solidity, 0.580645161290323) -def test_weighted_central_moments(): +def test_weighted_moments(): wmu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE - )[0].weighted_central_moments + )[0].weighted_moments_central ref = np.array( [[ 7.4000000000e+01, -2.1316282073e-13, 4.7837837838e+02, -7.5943608473e+02], @@ -283,9 +283,9 @@ def test_weighted_centroid(): assert_array_almost_equal(centroid, (5.540540540540, 9.445945945945)) -def test_weighted_hu_moments(): +def test_weighted_moments_hu(): whu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE - )[0].weighted_hu_moments + )[0].weighted_moments_hu ref = np.array([ 3.1750587329e-01, 2.1417517159e-02, @@ -314,9 +314,9 @@ def test_weighted_moments(): assert_array_almost_equal(wm, ref) -def test_weighted_normalized_moments(): +def test_weighted_moments_normalized(): wnu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE - )[0].weighted_normalized_moments + )[0].weighted_moments_normalized ref = np.array( [[ np.nan, np.nan, 0.0873590903, -0.0161217406], [ np.nan, -0.0160405109, -0.0031421072, -0.0031376984],