From 20ca1f2638778f9e6cbf2361b9fa106af1a40e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ser=C3=B3dio?= <48109057+SerodioJ@users.noreply.github.com> Date: Fri, 12 Jul 2024 06:11:42 -0300 Subject: [PATCH] Add new GLCM properties to graycoprops (#7375) * Add mean, variance, standard deviation, and entropy * Explain that reference values where obtained by hand Co-authored-by: Marianne Corvellec --- skimage/feature/tests/test_texture.py | 40 +++++++++++++++++++++++++++ skimage/feature/texture.py | 27 ++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/skimage/feature/tests/test_texture.py b/skimage/feature/tests/test_texture.py index 2d153cbd534..f26d154d01a 100644 --- a/skimage/feature/tests/test_texture.py +++ b/skimage/feature/tests/test_texture.py @@ -176,6 +176,42 @@ def test_correlation(self): np.testing.assert_almost_equal(energy[0, 0], 0.71953255) np.testing.assert_almost_equal(energy[1, 0], 0.41176470) + def test_mean(self): + result = graycomatrix( + self.image, [1], [0, np.pi / 2], 4, normed=True, symmetric=True + ) + mean = graycoprops(result, 'mean')[0, 0] + + # Reference value was calculated by hand and is close to original source if precision 3 is used. + np.testing.assert_almost_equal(mean, 1.29166667) + + def test_variance(self): + result = graycomatrix( + self.image, [1], [0, np.pi / 2], 4, normed=True, symmetric=True + ) + variance = graycoprops(result, 'variance')[0, 0] + + # Reference value was calculated by hand and is close to original source if precision 3 is used. + np.testing.assert_almost_equal(variance, 1.03993055) + + def test_std(self): + result = graycomatrix( + self.image, [1], [0, np.pi / 2], 4, normed=True, symmetric=True + ) + std = graycoprops(result, 'std')[0, 0] + + # Reference value was calculated by hand and is close to original source if precision 3 is used. + np.testing.assert_almost_equal(std, 1.01976985) + + def test_entropy(self): + result = graycomatrix( + self.image, [1], [0, np.pi / 2], 4, normed=True, symmetric=True + ) + entropy = graycoprops(result, 'entropy')[0, 0] + + # Reference value was calculated by hand and is close to original source if precision 3 is used. + np.testing.assert_almost_equal(entropy, 2.09472904) + def test_uniform_properties(self): im = np.ones((4, 4), dtype=np.uint8) result = graycomatrix( @@ -188,6 +224,10 @@ def test_uniform_properties(self): 'energy', 'correlation', 'ASM', + 'mean', + 'variance', + 'std', + 'entropy', ]: graycoprops(result, prop) diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index c3591bbbe36..417c0918fb6 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -180,6 +180,10 @@ def graycoprops(P, prop='contrast'): - 'correlation': .. math:: \\sum_{i,j=0}^{levels-1} P_{i,j}\\left[\\frac{(i-\\mu_i) \\ (j-\\mu_j)}{\\sqrt{(\\sigma_i^2)(\\sigma_j^2)}}\\right] + - 'mean': :math:`\\sum_{i=0}^{levels-1} i*P_{i}` + - 'variance': :math:`\\sum_{i=0}^{levels-1} P_{i}*(i-mean)^2` + - 'std': :math:`\\sqrt{variance}` + - 'entropy': :math:`\\sum_{i,j=0}^{levels-1} -P_{i,j}*log(P_{i,j})` Each GLCM is normalized to have a sum of 1 before the computation of texture properties. @@ -196,7 +200,7 @@ def graycoprops(P, prop='contrast'): occurs at a distance d and at an angle theta from gray-level i. prop : {'contrast', 'dissimilarity', 'homogeneity', 'energy', \ - 'correlation', 'ASM'}, optional + 'correlation', 'ASM', 'mean', 'variance', 'std', 'entropy'}, optional The property of the GLCM to compute. The default is 'contrast'. Returns @@ -229,6 +233,12 @@ def graycoprops(P, prop='contrast'): [1.25 , 2.75 ]]) """ + + def glcm_mean(): + I = np.arange(num_level).reshape((num_level, 1, 1, 1)) + mean = np.sum(I * P, axis=(0, 1)) + return I, mean + check_nD(P, 4, 'P') (num_level, num_level2, num_dist, num_angle) = P.shape @@ -253,7 +263,7 @@ def graycoprops(P, prop='contrast'): weights = np.abs(I - J) elif prop == 'homogeneity': weights = 1.0 / (1.0 + (I - J) ** 2) - elif prop in ['ASM', 'energy', 'correlation']: + elif prop in ['ASM', 'energy', 'correlation', 'entropy', 'variance', 'mean', 'std']: pass else: raise ValueError(f'{prop} is an invalid property') @@ -264,6 +274,19 @@ def graycoprops(P, prop='contrast'): results = np.sqrt(asm) elif prop == 'ASM': results = np.sum(P**2, axis=(0, 1)) + elif prop == 'mean': + _, results = glcm_mean() + elif prop == 'variance': + I, mean = glcm_mean() + results = np.sum(P * ((I - mean) ** 2), axis=(0, 1)) + elif prop == 'std': + I, mean = glcm_mean() + var = np.sum(P * ((I - mean) ** 2), axis=(0, 1)) + results = np.sqrt(var) + elif prop == 'entropy': + ln = -np.log(P, where=(P != 0), out=np.zeros_like(P)) + results = np.sum(P * ln, axis=(0, 1)) + 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))