From e1751f91e29de999f878322461d7db8f8e616670 Mon Sep 17 00:00:00 2001 From: emmanuelle Date: Sat, 9 May 2015 12:20:18 +0200 Subject: [PATCH 1/7] Some PEP8 in test module --- skimage/measure/tests/test_regionprops.py | 38 +++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 01330cdf..87e9a4a8 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -68,8 +68,8 @@ def test_moments_central(): assert_almost_equal(mu[0, 3], -737.333333333333) assert_almost_equal(mu[1, 1], -87.33333333333303) assert_almost_equal(mu[1, 2], -127.5555555555593) - assert_almost_equal(mu[2, 0], 1259.7777777777774) - assert_almost_equal(mu[2, 1], 2000.296296296291) + assert_almost_equal(mu[2, 0], 1259.7777777777774) + assert_almost_equal(mu[2, 1], 2000.296296296291) assert_almost_equal(mu[3, 0], -760.0246913580195) @@ -232,11 +232,11 @@ def test_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[0, 2], 0.08410493827160502) assert_almost_equal(nu[1, 1], -0.016846707818929982) assert_almost_equal(nu[1, 2], -0.002899800614433943) - assert_almost_equal(nu[2, 0], 0.24301268861454037) - assert_almost_equal(nu[2, 1], 0.045473992910668816) + assert_almost_equal(nu[2, 0], 0.24301268861454037) + assert_almost_equal(nu[2, 1], 0.045473992910668816) assert_almost_equal(nu[3, 0], -0.017278118992041805) @@ -277,14 +277,14 @@ def test_weighted_moments_central(): wmu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE )[0].weighted_moments_central ref = np.array( - [[ 7.4000000000e+01, -2.1316282073e-13, - 4.7837837838e+02, -7.5943608473e+02], - [ 3.7303493627e-14, -8.7837837838e+01, - -1.4801314828e+02, -1.2714707125e+03], - [ 1.2602837838e+03, 2.1571526662e+03, - 6.6989799420e+03, 1.5304076361e+04], - [-7.6561796932e+02, -4.2385971907e+03, - -9.9501164076e+03, -3.3156729271e+04]] + [[7.4000000000e+01, -2.1316282073e-13, 4.7837837838e+02, + -7.5943608473e+02], + [3.7303493627e-14, -8.7837837838e+01, -1.4801314828e+02, + -1.2714707125e+03], + [1.2602837838e+03, 2.1571526662e+03, 6.6989799420e+03, + 1.5304076361e+04], + [-7.6561796932e+02, -4.2385971907e+03, -9.9501164076e+03, + -3.3156729271e+04]] ) np.set_printoptions(precision=10) assert_array_almost_equal(wmu, ref) @@ -315,10 +315,14 @@ def test_weighted_moments(): wm = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE )[0].weighted_moments ref = np.array( - [[7.4000000e+01, 4.1000000e+02, 2.7500000e+03, 1.9778000e+04], - [6.9900000e+02, 3.7850000e+03, 2.4855000e+04, 1.7500100e+05], - [7.8630000e+03, 4.4063000e+04, 2.9347700e+05, 2.0810510e+06], - [9.7317000e+04, 5.7256700e+05, 3.9007170e+06, 2.8078871e+07]] + [[7.4000000000e+01, 4.1000000000e+02, 2.7500000000e+03, + 1.9778000000e+04], + [6.9900000000e+02, 3.7850000000e+03, 2.4855000000e+04, + 1.7500100000e+05], + [7.8630000000e+03, 4.4063000000e+04, 2.9347700000e+05, + 2.0810510000e+06], + [9.7317000000e+04, 5.7256700000e+05, 3.9007170000e+06, + 2.8078871000e+07]] ) assert_array_almost_equal(wm, ref) From 035928462316937eb00c4723b3d73d5992eeff57 Mon Sep 17 00:00:00 2001 From: emmanuelle Date: Sun, 10 May 2015 23:14:48 +0200 Subject: [PATCH 2/7] Modified some methods in _RegionProperties class to make them dimension-agnostic. Removed the NotImplementedError for 3-D images in regionprops function. --- skimage/measure/_regionprops.py | 77 ++++++++++++--- skimage/measure/tests/test_regionprops.py | 112 ++++++++++++++-------- 2 files changed, 135 insertions(+), 54 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index dc01fc24..3d5d6c6b 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -17,6 +17,7 @@ STREL_4 = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.uint8) STREL_8 = np.ones((3, 3), dtype=np.uint8) +STREL_26_3D = np.ones((3, 3, 3), dtype=np.uint8) PROPS = { 'Area': 'area', 'BoundingBox': 'bbox', @@ -93,39 +94,51 @@ class _RegionProperties(object): self._cache_active = cache_active self._cache = {} + self._ndim = label_image.ndim def area(self): - return self.moments[0, 0] + return self.image.sum() def bbox(self): - return (self._slice[0].start, self._slice[1].start, - self._slice[0].stop, self._slice[1].stop) + return tuple([self._slice[i].start for i in range(self._ndim)] + + [self._slice[i].stop for i in range(self._ndim)]) def centroid(self): - row, col = self.local_centroid - return row + self._slice[0].start, col + self._slice[1].start + centroid_coords = self.local_centroid + return tuple(centroid_coords + + np.array([self._slice[i].start + for i in range(self._ndim)])) def convex_area(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return np.sum(self.convex_image) @_cached def convex_image(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') from ..morphology.convex_hull import convex_hull_image return convex_hull_image(self.image) def coords(self): - rr, cc = np.nonzero(self.image) - return np.vstack((rr + self._slice[0].start, - cc + self._slice[1].start)).T + indices = np.nonzero(self.image) + return np.vstack([indices[i] + self._slice[i].start + for i in range(self._ndim)]).T def eccentricity(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') l1, l2 = self.inertia_tensor_eigvals if l1 == 0: return 0 return sqrt(1 - l2 / l1) def equivalent_diameter(self): - return sqrt(4 * self.moments[0, 0] / PI) + if self._ndim == 2: + return sqrt(4 * self.area / PI) + elif self._ndim == 3: + return (6 * self.area / PI) ** (1. / 3) def euler_number(self): euler_array = self.filled_image != self.image @@ -134,15 +147,17 @@ class _RegionProperties(object): return -num + 1 def extent(self): - rows, cols = self.image.shape - return self.moments[0, 0] / (rows * cols) + return float(self.area) / self.image.size def filled_area(self): return np.sum(self.filled_image) @_cached def filled_image(self): - return ndi.binary_fill_holes(self.image, STREL_8) + if self._ndim == 2: + return ndi.binary_fill_holes(self.image, STREL_8) + else: + return ndi.binary_fill_holes(self.image, STREL_26_3D) @_cached def image(self): @@ -150,6 +165,8 @@ class _RegionProperties(object): @_cached def inertia_tensor(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') mu = self.moments_central a = mu[2, 0] / mu[0, 0] b = -mu[1, 1] / mu[0, 0] @@ -158,6 +175,8 @@ class _RegionProperties(object): @_cached def inertia_tensor_eigvals(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') a, b, b, c = self.inertia_tensor.flat # eigen values of inertia tensor l1 = (a + c) / 2 + sqrt(4 * b ** 2 + (a - c) ** 2) / 2 @@ -174,6 +193,8 @@ class _RegionProperties(object): return self.intensity_image.astype(np.double) def local_centroid(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') m = self.moments row = m[0, 1] / m[0, 0] col = m[1, 0] / m[0, 0] @@ -189,31 +210,45 @@ class _RegionProperties(object): return np.min(self.intensity_image[self.image]) def major_axis_length(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') l1, _ = self.inertia_tensor_eigvals return 4 * sqrt(l1) def minor_axis_length(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') _, l2 = self.inertia_tensor_eigvals return 4 * sqrt(l2) @_cached def moments(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return _moments.moments(self.image.astype(np.uint8), 3) @_cached def moments_central(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') row, col = self.local_centroid return _moments.moments_central(self.image.astype(np.uint8), row, col, 3) def moments_hu(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return _moments.moments_hu(self.moments_normalized) @_cached def moments_normalized(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return _moments.moments_normalized(self.moments_central, 3) def orientation(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') a, b, b, c = self.inertia_tensor.flat b = -b if a - c == 0: @@ -225,16 +260,24 @@ class _RegionProperties(object): return - 0.5 * atan2(2 * b, (a - c)) def perimeter(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return perimeter(self.image, 4) def solidity(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return self.moments[0, 0] / np.sum(self.convex_image) def weighted_centroid(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') row, col = self.weighted_local_centroid return row + self._slice[0].start, col + self._slice[1].start def weighted_local_centroid(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') m = self.weighted_moments row = m[0, 1] / m[0, 0] col = m[1, 0] / m[0, 0] @@ -247,15 +290,21 @@ class _RegionProperties(object): @_cached def weighted_moments_central(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') row, col = self.weighted_local_centroid return _moments.moments_central(self._intensity_image_double(), row, col, 3) def weighted_moments_hu(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return _moments.moments_hu(self.weighted_moments_normalized) @_cached def weighted_moments_normalized(self): + if self._ndim > 2: + raise NotImplementedError('not implemented for 3D images') return _moments.moments_normalized(self.weighted_moments_central, 3) def __iter__(self): @@ -476,8 +525,8 @@ def regionprops(label_image, intensity_image=None, cache=True): label_image = np.squeeze(label_image) - if label_image.ndim != 2: - raise TypeError('Only 2-D images supported.') + if label_image.ndim not in (2, 3): + raise TypeError('Only 2-D and 3-D images supported.') if not np.issubdtype(label_image.dtype, np.integer): raise TypeError('Label image must be of integral type.') diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 87e9a4a8..2c6f2449 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -22,6 +22,10 @@ SAMPLE = np.array( INTENSITY_SAMPLE = SAMPLE.copy() INTENSITY_SAMPLE[1, 9:11] = 2 +SAMPLE_3D = np.zeros((6, 6, 6), dtype=np.uint8) +SAMPLE_3D[1:3, 1:3, 1:3] = 1 +SAMPLE_3D[3, 2, 2] = 1 +INTENSITY_SAMPLE_3D = SAMPLE_3D.copy() def test_all_props(): region = regionprops(SAMPLE, INTENSITY_SAMPLE)[0] @@ -29,6 +33,14 @@ def test_all_props(): assert_almost_equal(region[prop], getattr(region, PROPS[prop])) +def test_all_props_3d(): + region = regionprops(SAMPLE_3D, INTENSITY_SAMPLE_3D)[0] + for prop in PROPS: + try: + assert_almost_equal(region[prop], getattr(region, PROPS[prop])) + except NotImplementedError: + pass + def test_dtype(): regionprops(np.zeros((10, 10), dtype=np.int)) regionprops(np.zeros((10, 10), dtype=np.uint)) @@ -42,12 +54,15 @@ def test_ndim(): regionprops(np.zeros((10, 10), dtype=np.int)) regionprops(np.zeros((10, 10, 1), dtype=np.int)) regionprops(np.zeros((10, 10, 1, 1), dtype=np.int)) - assert_raises(TypeError, regionprops, np.zeros((10, 10, 2), dtype=np.int)) + regionprops(np.zeros((10, 10, 10), dtype=np.int)) + assert_raises(TypeError, regionprops, np.zeros((10, 10, 10, 2), dtype=np.int)) def test_area(): area = regionprops(SAMPLE)[0].area assert area == np.sum(SAMPLE) + area = regionprops(SAMPLE_3D)[0].area + assert area == np.sum(SAMPLE_3D) def test_bbox(): @@ -59,18 +74,21 @@ def test_bbox(): bbox = regionprops(SAMPLE_mod)[0].bbox assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1)) + bbox = regionprops(SAMPLE_3D)[0].bbox + assert_array_almost_equal(bbox, (1, 1, 1, 4, 3, 3)) + def test_moments_central(): mu = regionprops(SAMPLE)[0].moments_central # determined with OpenCV - assert_almost_equal(mu[0, 2], 436.00000000000045) + assert_almost_equal(mu[0,2], 436.00000000000045) # different from OpenCV results, bug in OpenCV - assert_almost_equal(mu[0, 3], -737.333333333333) - assert_almost_equal(mu[1, 1], -87.33333333333303) - assert_almost_equal(mu[1, 2], -127.5555555555593) - assert_almost_equal(mu[2, 0], 1259.7777777777774) - assert_almost_equal(mu[2, 1], 2000.296296296291) - assert_almost_equal(mu[3, 0], -760.0246913580195) + assert_almost_equal(mu[0,3], -737.333333333333) + assert_almost_equal(mu[1,1], -87.33333333333303) + assert_almost_equal(mu[1,2], -127.5555555555593) + assert_almost_equal(mu[2,0], 1259.7777777777774) + assert_almost_equal(mu[2,1], 2000.296296296291) + assert_almost_equal(mu[3,0], -760.0246913580195) def test_centroid(): @@ -110,6 +128,11 @@ def test_coordinates(): prop_coords = regionprops(sample)[0].coords assert_array_equal(prop_coords, coords) + sample = np.zeros((6, 6, 6), dtype=np.int8) + coords = np.array([[1, 1, 1], [1, 2, 1], [1, 3, 1]]) + sample[coords[:, 0], coords[:, 1], coords[:, 2]] = 1 + prop_coords = regionprops(sample)[0].coords + assert_array_equal(prop_coords, coords) def test_eccentricity(): eps = regionprops(SAMPLE)[0].eccentricity @@ -136,6 +159,9 @@ def test_euler_number(): en = regionprops(SAMPLE_mod)[0].euler_number assert en == 0 + en = regionprops(SAMPLE_3D)[0].euler_number + assert en == 1 + def test_extent(): extent = regionprops(SAMPLE)[0].extent @@ -161,11 +187,17 @@ def test_image(): img = regionprops(SAMPLE)[0].image assert_array_equal(img, SAMPLE) + img = regionprops(SAMPLE_3D)[0].image + assert_array_equal(img, SAMPLE_3D[1:4, 1:3, 1:3]) + def test_label(): label = regionprops(SAMPLE)[0].label assert_array_equal(label, 1) + label = regionprops(SAMPLE_3D)[0].label + assert_array_equal(label, 1) + def test_filled_area(): area = regionprops(SAMPLE)[0].filled_area @@ -217,27 +249,27 @@ def test_minor_axis_length(): def test_moments(): m = regionprops(SAMPLE)[0].moments # determined with OpenCV - assert_almost_equal(m[0, 0], 72.0) - assert_almost_equal(m[0, 1], 408.0) - assert_almost_equal(m[0, 2], 2748.0) - assert_almost_equal(m[0, 3], 19776.0) - assert_almost_equal(m[1, 0], 680.0) - assert_almost_equal(m[1, 1], 3766.0) - assert_almost_equal(m[1, 2], 24836.0) - assert_almost_equal(m[2, 0], 7682.0) - assert_almost_equal(m[2, 1], 43882.0) - assert_almost_equal(m[3, 0], 95588.0) + assert_almost_equal(m[0,0], 72.0) + assert_almost_equal(m[0,1], 408.0) + assert_almost_equal(m[0,2], 2748.0) + assert_almost_equal(m[0,3], 19776.0) + assert_almost_equal(m[1,0], 680.0) + assert_almost_equal(m[1,1], 3766.0) + assert_almost_equal(m[1,2], 24836.0) + assert_almost_equal(m[2,0], 7682.0) + assert_almost_equal(m[2,1], 43882.0) + assert_almost_equal(m[3,0], 95588.0) def test_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) - assert_almost_equal(nu[1, 2], -0.002899800614433943) - assert_almost_equal(nu[2, 0], 0.24301268861454037) - assert_almost_equal(nu[2, 1], 0.045473992910668816) - assert_almost_equal(nu[3, 0], -0.017278118992041805) + assert_almost_equal(nu[0,2], 0.08410493827160502) + assert_almost_equal(nu[1,1], -0.016846707818929982) + assert_almost_equal(nu[1,2], -0.002899800614433943) + assert_almost_equal(nu[2,0], 0.24301268861454037) + assert_almost_equal(nu[2,1], 0.045473992910668816) + assert_almost_equal(nu[3,0], -0.017278118992041805) def test_orientation(): @@ -277,14 +309,14 @@ def test_weighted_moments_central(): wmu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE )[0].weighted_moments_central ref = np.array( - [[7.4000000000e+01, -2.1316282073e-13, 4.7837837838e+02, - -7.5943608473e+02], - [3.7303493627e-14, -8.7837837838e+01, -1.4801314828e+02, - -1.2714707125e+03], - [1.2602837838e+03, 2.1571526662e+03, 6.6989799420e+03, - 1.5304076361e+04], - [-7.6561796932e+02, -4.2385971907e+03, -9.9501164076e+03, - -3.3156729271e+04]] + [[ 7.4000000000e+01, -2.1316282073e-13, 4.7837837838e+02, + -7.5943608473e+02], + [ 3.7303493627e-14, -8.7837837838e+01, -1.4801314828e+02, + -1.2714707125e+03], + [ 1.2602837838e+03, 2.1571526662e+03, 6.6989799420e+03, + 1.5304076361e+04], + [ -7.6561796932e+02, -4.2385971907e+03, -9.9501164076e+03, + -3.3156729271e+04]] ) np.set_printoptions(precision=10) assert_array_almost_equal(wmu, ref) @@ -315,14 +347,14 @@ def test_weighted_moments(): wm = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE )[0].weighted_moments ref = np.array( - [[7.4000000000e+01, 4.1000000000e+02, 2.7500000000e+03, - 1.9778000000e+04], - [6.9900000000e+02, 3.7850000000e+03, 2.4855000000e+04, - 1.7500100000e+05], - [7.8630000000e+03, 4.4063000000e+04, 2.9347700000e+05, - 2.0810510000e+06], - [9.7317000000e+04, 5.7256700000e+05, 3.9007170000e+06, - 2.8078871000e+07]] + [[ 7.4000000000e+01, 4.1000000000e+02, 2.7500000000e+03, + 1.9778000000e+04], + [ 6.9900000000e+02, 3.7850000000e+03, 2.4855000000e+04, + 1.7500100000e+05], + [ 7.8630000000e+03, 4.4063000000e+04, 2.9347700000e+05, + 2.0810510000e+06], + [ 9.7317000000e+04, 5.7256700000e+05, 3.9007170000e+06, + 2.8078871000e+07]] ) assert_array_almost_equal(wm, ref) From d8cc148d8c19337be987bd2e72787303eb08e20c Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 3 Sep 2015 20:12:35 +0200 Subject: [PATCH 3/7] Replace individual errors by 2D decorator --- skimage/measure/_regionprops.py | 105 ++++++++++++++++---------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index 3d5d6c6b..a130601b 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -74,6 +74,16 @@ def _cached(f): return wrapper +def only2d(method): + def func2d(self, *args, **kwargs): + if self._ndim > 2: + raise NotImplementedError('Property %s is not implemented for ' + '3D images' % method.__name__) + return method(self, *args, **kwargs) + func2d.__name__ = method.__name__ + return func2d + + class _RegionProperties(object): """Please refer to `skimage.measure.regionprops` for more information on the available region properties. @@ -109,15 +119,14 @@ class _RegionProperties(object): np.array([self._slice[i].start for i in range(self._ndim)])) + @property + @only2d def convex_area(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return np.sum(self.convex_image) - @_cached + @_cached_property + @only2d def convex_image(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') from ..morphology.convex_hull import convex_hull_image return convex_hull_image(self.image) @@ -126,9 +135,9 @@ class _RegionProperties(object): return np.vstack([indices[i] + self._slice[i].start for i in range(self._ndim)]).T + @property + @only2d def eccentricity(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') l1, l2 = self.inertia_tensor_eigvals if l1 == 0: return 0 @@ -163,20 +172,19 @@ class _RegionProperties(object): def image(self): return self._label_image[self._slice] == self.label - @_cached + @_cached_property + @only2d def inertia_tensor(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') 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] return np.array([[a, b], [b, c]]) - @_cached + @_cached_property + @only2d +>>>>>>> Replace individual errors by 2D decorator def inertia_tensor_eigvals(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') a, b, b, c = self.inertia_tensor.flat # eigen values of inertia tensor l1 = (a + c) / 2 + sqrt(4 * b ** 2 + (a - c) ** 2) / 2 @@ -192,9 +200,9 @@ class _RegionProperties(object): def _intensity_image_double(self): return self.intensity_image.astype(np.double) + @property + @only2d def local_centroid(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') m = self.moments row = m[0, 1] / m[0, 0] col = m[1, 0] / m[0, 0] @@ -209,46 +217,43 @@ class _RegionProperties(object): def min_intensity(self): return np.min(self.intensity_image[self.image]) + @property + @only2d def major_axis_length(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') l1, _ = self.inertia_tensor_eigvals return 4 * sqrt(l1) + @property + @only2d def minor_axis_length(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') _, l2 = self.inertia_tensor_eigvals return 4 * sqrt(l2) - @_cached + @_cached_property + @only2d def moments(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return _moments.moments(self.image.astype(np.uint8), 3) - @_cached + @_cached_property + @only2d def moments_central(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') row, col = self.local_centroid return _moments.moments_central(self.image.astype(np.uint8), row, col, 3) + @property + @only2d def moments_hu(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return _moments.moments_hu(self.moments_normalized) - @_cached + @_cached_property + @only2d def moments_normalized(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return _moments.moments_normalized(self.moments_central, 3) + @property + @only2d def orientation(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') a, b, b, c = self.inertia_tensor.flat b = -b if a - c == 0: @@ -259,52 +264,50 @@ class _RegionProperties(object): else: return - 0.5 * atan2(2 * b, (a - c)) + @property + @only2d def perimeter(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return perimeter(self.image, 4) + @property + @only2d def solidity(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return self.moments[0, 0] / np.sum(self.convex_image) + @property + @only2d def weighted_centroid(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') row, col = self.weighted_local_centroid return row + self._slice[0].start, col + self._slice[1].start + @property + @only2d def weighted_local_centroid(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') m = self.weighted_moments row = m[0, 1] / m[0, 0] col = m[1, 0] / m[0, 0] return row, col - @_cached + @_cached_property + @only2d def weighted_moments(self): - return _moments.moments_central(self._intensity_image_double(), - 0, 0, 3) + return _moments.moments_central(self._intensity_image_double, 0, 0, 3) - @_cached + @_cached_property + @only2d def weighted_moments_central(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') row, col = self.weighted_local_centroid return _moments.moments_central(self._intensity_image_double(), row, col, 3) + @property + @only2d def weighted_moments_hu(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return _moments.moments_hu(self.weighted_moments_normalized) - @_cached + @_cached_property + @only2d def weighted_moments_normalized(self): - if self._ndim > 2: - raise NotImplementedError('not implemented for 3D images') return _moments.moments_normalized(self.weighted_moments_central, 3) def __iter__(self): From 9317b7be346472f49706781685472dd68a65fbbc Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 3 Sep 2015 20:15:29 +0200 Subject: [PATCH 4/7] Make area a cached property; use np.sum --- skimage/measure/_regionprops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index a130601b..297515cb 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -106,8 +106,9 @@ class _RegionProperties(object): self._cache = {} self._ndim = label_image.ndim + @_cached_property def area(self): - return self.image.sum() + return np.sum(self.image) def bbox(self): return tuple([self._slice[i].start for i in range(self._ndim)] + From add335228493e4cb7e8f738279b34c8c230f757a Mon Sep 17 00:00:00 2001 From: emmanuelle Date: Fri, 25 Dec 2015 11:38:23 +0100 Subject: [PATCH 5/7] Euler number is only2d --- skimage/measure/_regionprops.py | 48 +++++++++++------------ skimage/measure/tests/test_regionprops.py | 3 -- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index 297515cb..d9054989 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -106,7 +106,7 @@ class _RegionProperties(object): self._cache = {} self._ndim = label_image.ndim - @_cached_property + @_cached def area(self): return np.sum(self.image) @@ -120,12 +120,12 @@ class _RegionProperties(object): np.array([self._slice[i].start for i in range(self._ndim)])) - @property + #@property @only2d def convex_area(self): return np.sum(self.convex_image) - @_cached_property + @_cached @only2d def convex_image(self): from ..morphology.convex_hull import convex_hull_image @@ -136,7 +136,7 @@ class _RegionProperties(object): return np.vstack([indices[i] + self._slice[i].start for i in range(self._ndim)]).T - @property + #@property @only2d def eccentricity(self): l1, l2 = self.inertia_tensor_eigvals @@ -150,6 +150,7 @@ class _RegionProperties(object): elif self._ndim == 3: return (6 * self.area / PI) ** (1. / 3) + @only2d def euler_number(self): euler_array = self.filled_image != self.image _, num = label(euler_array, neighbors=8, return_num=True, @@ -173,7 +174,7 @@ class _RegionProperties(object): def image(self): return self._label_image[self._slice] == self.label - @_cached_property + @_cached @only2d def inertia_tensor(self): mu = self.moments_central @@ -182,9 +183,8 @@ class _RegionProperties(object): c = mu[0, 2] / mu[0, 0] return np.array([[a, b], [b, c]]) - @_cached_property + @_cached @only2d ->>>>>>> Replace individual errors by 2D decorator def inertia_tensor_eigvals(self): a, b, b, c = self.inertia_tensor.flat # eigen values of inertia tensor @@ -201,7 +201,7 @@ class _RegionProperties(object): def _intensity_image_double(self): return self.intensity_image.astype(np.double) - @property + #@property @only2d def local_centroid(self): m = self.moments @@ -218,41 +218,41 @@ class _RegionProperties(object): def min_intensity(self): return np.min(self.intensity_image[self.image]) - @property + #@property @only2d def major_axis_length(self): l1, _ = self.inertia_tensor_eigvals return 4 * sqrt(l1) - @property + #@property @only2d def minor_axis_length(self): _, l2 = self.inertia_tensor_eigvals return 4 * sqrt(l2) - @_cached_property + @_cached @only2d def moments(self): return _moments.moments(self.image.astype(np.uint8), 3) - @_cached_property + @_cached @only2d def moments_central(self): row, col = self.local_centroid return _moments.moments_central(self.image.astype(np.uint8), row, col, 3) - @property + #@property @only2d def moments_hu(self): return _moments.moments_hu(self.moments_normalized) - @_cached_property + @_cached @only2d def moments_normalized(self): return _moments.moments_normalized(self.moments_central, 3) - @property + #@property @only2d def orientation(self): a, b, b, c = self.inertia_tensor.flat @@ -265,23 +265,23 @@ class _RegionProperties(object): else: return - 0.5 * atan2(2 * b, (a - c)) - @property + #@property @only2d def perimeter(self): return perimeter(self.image, 4) - @property + #@property @only2d def solidity(self): return self.moments[0, 0] / np.sum(self.convex_image) - @property + #@property @only2d def weighted_centroid(self): row, col = self.weighted_local_centroid return row + self._slice[0].start, col + self._slice[1].start - @property + #@property @only2d def weighted_local_centroid(self): m = self.weighted_moments @@ -289,24 +289,24 @@ class _RegionProperties(object): col = m[1, 0] / m[0, 0] return row, col - @_cached_property + @_cached @only2d def weighted_moments(self): - return _moments.moments_central(self._intensity_image_double, 0, 0, 3) + return _moments.moments_central(self._intensity_image_double(), 0, 0, 3) - @_cached_property + @_cached @only2d def weighted_moments_central(self): row, col = self.weighted_local_centroid return _moments.moments_central(self._intensity_image_double(), row, col, 3) - @property + #@property @only2d def weighted_moments_hu(self): return _moments.moments_hu(self.weighted_moments_normalized) - @_cached_property + @_cached @only2d def weighted_moments_normalized(self): return _moments.moments_normalized(self.weighted_moments_central, 3) diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 2c6f2449..f724577d 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -159,9 +159,6 @@ def test_euler_number(): en = regionprops(SAMPLE_mod)[0].euler_number assert en == 0 - en = regionprops(SAMPLE_3D)[0].euler_number - assert en == 1 - def test_extent(): extent = regionprops(SAMPLE)[0].extent From fab4265f47e847eecb0f6c4369fa9ac943e392dc Mon Sep 17 00:00:00 2001 From: emmanuelle Date: Fri, 25 Dec 2015 12:37:41 +0100 Subject: [PATCH 6/7] Removed @property decorator, that made _install_properties_doc crash --- skimage/measure/_regionprops.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index d9054989..09e0a1e6 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -120,7 +120,6 @@ class _RegionProperties(object): np.array([self._slice[i].start for i in range(self._ndim)])) - #@property @only2d def convex_area(self): return np.sum(self.convex_image) @@ -136,7 +135,6 @@ class _RegionProperties(object): return np.vstack([indices[i] + self._slice[i].start for i in range(self._ndim)]).T - #@property @only2d def eccentricity(self): l1, l2 = self.inertia_tensor_eigvals @@ -201,7 +199,6 @@ class _RegionProperties(object): def _intensity_image_double(self): return self.intensity_image.astype(np.double) - #@property @only2d def local_centroid(self): m = self.moments @@ -218,13 +215,11 @@ class _RegionProperties(object): def min_intensity(self): return np.min(self.intensity_image[self.image]) - #@property @only2d def major_axis_length(self): l1, _ = self.inertia_tensor_eigvals return 4 * sqrt(l1) - #@property @only2d def minor_axis_length(self): _, l2 = self.inertia_tensor_eigvals @@ -242,7 +237,6 @@ class _RegionProperties(object): return _moments.moments_central(self.image.astype(np.uint8), row, col, 3) - #@property @only2d def moments_hu(self): return _moments.moments_hu(self.moments_normalized) @@ -252,7 +246,6 @@ class _RegionProperties(object): def moments_normalized(self): return _moments.moments_normalized(self.moments_central, 3) - #@property @only2d def orientation(self): a, b, b, c = self.inertia_tensor.flat @@ -265,23 +258,19 @@ class _RegionProperties(object): else: return - 0.5 * atan2(2 * b, (a - c)) - #@property @only2d def perimeter(self): return perimeter(self.image, 4) - #@property @only2d def solidity(self): return self.moments[0, 0] / np.sum(self.convex_image) - #@property @only2d def weighted_centroid(self): row, col = self.weighted_local_centroid return row + self._slice[0].start, col + self._slice[1].start - #@property @only2d def weighted_local_centroid(self): m = self.weighted_moments @@ -301,7 +290,6 @@ class _RegionProperties(object): return _moments.moments_central(self._intensity_image_double(), row, col, 3) - #@property @only2d def weighted_moments_hu(self): return _moments.moments_hu(self.weighted_moments_normalized) From 6c9325aadfc02fe135853274a6b0d88ffe950641 Mon Sep 17 00:00:00 2001 From: emmanuelle Date: Mon, 28 Dec 2015 21:13:22 +0100 Subject: [PATCH 7/7] Minor fixes: errant indent, functools.wraps and python3 style --- skimage/measure/_regionprops.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index 09e0a1e6..297a6390 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -1,4 +1,5 @@ # coding: utf-8 +from __future__ import division from math import sqrt, atan2, pi as PI import numpy as np from scipy import ndimage as ndi @@ -75,12 +76,12 @@ def _cached(f): def only2d(method): + @wraps(method) def func2d(self, *args, **kwargs): if self._ndim > 2: raise NotImplementedError('Property %s is not implemented for ' '3D images' % method.__name__) return method(self, *args, **kwargs) - func2d.__name__ = method.__name__ return func2d @@ -91,11 +92,11 @@ class _RegionProperties(object): def __init__(self, slice, label, label_image, intensity_image, cache_active): - + if intensity_image is not None: if not intensity_image.shape == label_image.shape: raise ValueError('Label and intensity image must have the same shape.') - + self.label = label self._slice = slice @@ -156,17 +157,15 @@ class _RegionProperties(object): return -num + 1 def extent(self): - return float(self.area) / self.image.size + return self.area / self.image.size def filled_area(self): return np.sum(self.filled_image) @_cached def filled_image(self): - if self._ndim == 2: - return ndi.binary_fill_holes(self.image, STREL_8) - else: - return ndi.binary_fill_holes(self.image, STREL_26_3D) + structure = STREL_8 if self._ndim == 2 else STREL_26_3D + return ndi.binary_fill_holes(self.image, structure) @_cached def image(self): @@ -518,7 +517,7 @@ def regionprops(label_image, intensity_image=None, cache=True): label_image = np.squeeze(label_image) if label_image.ndim not in (2, 3): - raise TypeError('Only 2-D and 3-D images supported.') + raise TypeError('Only 2-D and 3-D images supported.') if not np.issubdtype(label_image.dtype, np.integer): raise TypeError('Label image must be of integral type.')