#cython: cdivision=True #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False import numpy as np cimport numpy as np from libc.math cimport sin, cos, abs from skimage._shared.interpolation cimport bilinear_interpolation def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] image, np.ndarray[dtype=np.float64_t, ndim=1, negative_indices=False, mode='c'] distances, np.ndarray[dtype=np.float64_t, ndim=1, negative_indices=False, mode='c'] angles, int levels, np.ndarray[dtype=np.uint32_t, ndim=4, negative_indices=False, mode='c'] out ): """Perform co-occurrence matrix accumulation. Parameters ---------- image : ndarray Input image, which is converted to the uint8 data type. distances : ndarray List of pixel pair distance offsets. angles : ndarray List of pixel pair angles in radians. levels : int The input image should contain integers in [0, levels-1], where levels indicate the number of grey-levels counted (typically 256 for an 8-bit image) out : ndarray On input a 4D array of zeros, and on output it contains the results of the GLCM computation. """ cdef: np.int32_t a_inx, d_idx np.int32_t r, c, rows, cols, row, col np.int32_t i, j rows = image.shape[0] cols = image.shape[1] for a_idx, angle in enumerate(angles): for d_idx, distance in enumerate(distances): for r in range(rows): for c in range(cols): i = image[r, c] # compute the location of the offset pixel row = r + (sin(angle) * distance + 0.5) col = c + (cos(angle) * distance + 0.5); # make sure the offset is within bounds if row >= 0 and row < rows and \ col >= 0 and col < cols: j = image[row, col] if i >= 0 and i < levels and \ j >= 0 and j < levels: out[i, j, d_idx, a_idx] += 1 cdef inline int _bit_rotate_right(int value, int length): """Cyclic bit shift to the right. Parameters ---------- value : int integer value to shift length : int number of bits of integer """ return (value >> 1) | ((value & 1) << (length - 1)) def _local_binary_pattern(np.ndarray[double, ndim=2] image, int P, float R, char method='D'): """Gray scale and rotation invariant LBP (Local Binary Patterns). LBP is an invariant descriptor that can be used for texture classification. Parameters ---------- image : (N, M) double array Graylevel image. P : int Number of circularly symmetric neighbour set points (quantization of the angular space). R : float Radius of circle (spatial resolution of the operator). method : {'D', 'R', 'U', 'V'} Method to determine the pattern. * 'D': 'default' * 'R': 'ror' * 'U': 'uniform' * 'V': 'var' Returns ------- output : (N, M) array LBP image. """ # texture weights cdef np.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32) # local position of texture elements rp = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.double) / P) cp = R * np.cos(2 * np.pi * np.arange(P, dtype=np.double) / P) cdef np.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5) # pre allocate arrays for computation cdef np.ndarray[double, ndim=1] texture = np.zeros(P, np.double) cdef np.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8) cdef np.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32) output_shape = (image.shape[0], image.shape[1]) cdef np.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) cdef int rows = image.shape[0] cdef int cols = image.shape[1] cdef double lbp cdef int r, c, changes, i for r in range(image.shape[0]): for c in range(image.shape[1]): for i in range(P): texture[i] = bilinear_interpolation(image.data, rows, cols, r + coords[i, 0], c + coords[i, 1], 'C', 0) # signed / thresholded texture for i in range(P): if texture[i] - image[r, c] >= 0: signed_texture[i] = 1 else: signed_texture[i] = 0 lbp = 0 # if method == 'uniform' or method == 'var': if method == 'U' or method == 'V': # determine number of 0 - 1 changes changes = 0 for i in range(P - 1): changes += abs(signed_texture[i] - signed_texture[i + 1]) if changes <= 2: for i in range(P): lbp += signed_texture[i] else: lbp = P + 1 if method == 'V': var = np.var(texture) if var != 0: lbp /= var else: lbp = np.nan else: # method == 'default' for i in range(P): lbp += signed_texture[i] * weights[i] # method == 'ror' if method == 'R': # shift LBP P times to the right and get minimum value rotation_chain[0] = lbp for i in range(1, P): rotation_chain[i] = \ _bit_rotate_right(rotation_chain[i - 1], P) lbp = rotation_chain[0] for i in range(1, P): lbp = min(lbp, rotation_chain[i]) output[r, c] = lbp return output