2

I'm trying to get texture properties from a GLCM I created using greycomatrix from skimage.feature. My input data is an image with multiple bands and I want the texture properties for each pixel (resulting in an image with the dimensions cols x rows x (properties *bands)), as it can be achieved using ENVI. But I'm too new to this to come to grips with greycomatrix and greycoprops. This is what I tried:

import numpy as np
from skimage import io
from skimage.feature import greycomatrix, greycoprops

array = io.imread('MYFILE.tif')
array = array.astype(np.int64)
props = ['contrast', 'dissimilarity', 'homogeneity', 'energy', 'correlation', 'ASM']
textures = np.zeros((array.shape[0], array.shape[1], array.shape[2] * len(props)), np.float32)
angles = [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4]
bands = array.shape[2]
for b in range(bands):
    glcm = greycomatrix(array[:, :, b], [1], angles, np.nanmax(array) + 1,
                        symmetric=True, normed=True)
    for p, prop in enumerate(props):
        textures[:, :, b] = greycoprops(glcm, prop)

Unfortunately, this gives me a 1 x 4 matrix per prop, which I guess is one value per angle FOR THE WHOLE IMAGE, but this is not what I want. I need it per pixel, like contrast for each single pixel, computed from its respective surroundings. What am I missing?

s6hebern
  • 725
  • 9
  • 18

1 Answers1

2

This snippet should get the job done:

import numpy as np
from skimage import io, util
from skimage.feature.texture import greycomatrix, greycoprops

img = io.imread('fourbandimg.tif')

rows, cols, bands = img.shape

radius = 5
side = 2*radius + 1

distances = [1]
angles = [0, np.pi/2]
props = ['contrast', 'dissimilarity', 'homogeneity']
dim = len(distances)*len(angles)*len(props)*bands

padded = np.pad(img, radius, mode='reflect')
windows = [util.view_as_windows(padded[:, :, band].copy(), (side, side))
           for band in range(bands)]
feats = np.zeros(shape=(rows, cols, dim))

for row in range(rows):
    for col in range(cols):
        pixel_feats = []
        for band in range(bands):
            glcm = greycomatrix(windows[band][row, col, :, :],
                                distances=distances,
                                angles=angles)
            pixel_feats.extend([greycoprops(glcm, prop).ravel()
                                for prop in props])
        feats[row, col, :] = np.concatenate(pixel_feats)

The sample image has 128 rows, 128 columns and 4 bands (click here to download). At each image pixel a square local neighbourhood of size 11 is used to compute the grayscale matrices corresponding to the pixel to the right and the pixel above for each band. Then, contrast, dissimilarity and homogeneity are computed for those matrices. Thus we have 4 bands, 1 distance, 2 angles and 3 properties. Hence for each pixel the feature vector has 4 × 1 × 2 × 3 = 24 components.

Notice that in order to preserve the number of rows and columns the image has been padded using the image itself mirrored along the edge of the array. It this approach does not fit your needs you could simply ignore the outer frame of the image.

As a final caveat, the code could take a while to run.

Demo

In [193]: img.shape
Out[193]: (128, 128, 4)

In [194]: feats.shape
Out[194]: (128, 128, 24)

In [195]: feats[64, 64, :]
Out[195]: 
array([  1.51690000e+04,   9.50100000e+03,   1.02300000e+03,
         8.53000000e+02,   1.25203577e+01,   9.38930575e+00,
         2.54300000e+03,   1.47800000e+03,   3.89000000e+02,
         3.10000000e+02,   2.95064854e+01,   3.38267222e+01,
         2.18970000e+04,   1.71690000e+04,   1.21900000e+03,
         1.06700000e+03,   1.09729371e+01,   1.11741654e+01,
         2.54300000e+03,   1.47800000e+03,   3.89000000e+02,
         3.10000000e+02,   2.95064854e+01,   3.38267222e+01])

In [196]: io.imshow(img)
Out[196]: <matplotlib.image.AxesImage at 0x2a74bc728d0>

Multispectral image

Edit

You could cast your data to the type required by greycomatrix through NumPy's uint8 or scikit-images's img_as_ubyte.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
  • Looks like a great solution, but my image is a float32 satellite image, while `greycomatrix` requires unsigned integer. Do you also have a workaround for that to include here? – s6hebern Jun 15 '18 at 11:48
  • Great stuff, thanks a lot. Do I get this correctly: `distance = 1` means 1 pixel offset in the direction of `angles`? So `angles = [0]` would then be 1 pixel up, while `angles = [np.pi/2]` would be 1 pixel down? To do it like it is done in ENVI, I could then use `angles = [np.pi/4]` for a comparison with the pixel to the topright (1 pixel up and 1 to the right)? Also, is there a possibility to speed up things, e.g. by using parallelization? – s6hebern Jun 18 '18 at 06:00
  • `distances=[1]` and `angles=[0, np.pi/2]` actually correspond to 1-pixel offset to the right, and 1-pixel offset upwards. – Tonechas Jun 18 '18 at 08:56
  • I think there is. One possible approach could be through http://scikit-image.org/docs/dev/api/skimage.util.html#skimage.util.apply_parallel – Tonechas Jun 18 '18 at 11:17
  • Great hint, I'll give it a shot! – s6hebern Jun 18 '18 at 11:23
  • Applying this function to float images - like reflectance images - returns: ValueError: The maximum grayscale value in the image should be smaller than the number of levels. How to convert the float image to apply this function without loosing spectral information? – G.L Sep 29 '21 at 14:43