Skip to content

Commit

Permalink
Add new GLCM properties to graycoprops (scikit-image#7375)
Browse files Browse the repository at this point in the history
* Add mean, variance, standard deviation, and entropy
* Explain that reference values where obtained by hand

Co-authored-by: Marianne Corvellec <[email protected]>
  • Loading branch information
SerodioJ and mkcor authored Jul 12, 2024
1 parent bd880a3 commit 20ca1f2
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 2 deletions.
40 changes: 40 additions & 0 deletions skimage/feature/tests/test_texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -188,6 +224,10 @@ def test_uniform_properties(self):
'energy',
'correlation',
'ASM',
'mean',
'variance',
'std',
'entropy',
]:
graycoprops(result, prop)

Expand Down
27 changes: 25 additions & 2 deletions skimage/feature/texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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))
Expand Down

0 comments on commit 20ca1f2

Please sign in to comment.