diff --git a/doc/examples/plot_multiblock_local_binary_pattern.py b/doc/examples/plot_multiblock_local_binary_pattern.py index c359a1e9..5029c99c 100644 --- a/doc/examples/plot_multiblock_local_binary_pattern.py +++ b/doc/examples/plot_multiblock_local_binary_pattern.py @@ -28,17 +28,15 @@ import numpy as np from numpy.testing import assert_equal from skimage.transform import integral_image -# Create test matrix where first and fifth -# rectangles starting from top left clockwise -# have greater value than the central one. +# Create test matrix where first and fifth rectangles starting +# from top left clockwise have greater value than the central one. test_img = np.zeros((9, 9), dtype='uint8') test_img[3:6, 3:6] = 1 test_img[:3, :3] = 50 test_img[6:, 6:] = 50 -# First and fifth bits should be filled. -# This correct value will be compared to -# the computed one. +# First and fifth bits should be filled. This correct value will +# be compared to the computed one. correct_answer = 0b10001000 int_img = integral_image(test_img) @@ -48,7 +46,8 @@ lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3) assert_equal(correct_answer, lbp_code) """ -Now let's apply the operator to a real image and see how the visualization works. +Now let's apply the operator to a real image and see how the +visualization works. """ from skimage import data from matplotlib import pyplot as plt @@ -69,8 +68,8 @@ plt.imshow(img, interpolation='nearest') """ .. image:: PLOT2RST.current_figure -On the above plot we see the result of computing a MB-LBP and visualization -of the computed feature. The rectangles that have less intensities' sum than the central -rectangle are marked in cyan. The ones that have higher intensity values -are marked in white. The central rectangle is left untouched. +On the above plot we see the result of computing a MB-LBP and visualization of +the computed feature. The rectangles that have less intensities' sum than the +central rectangle are marked in cyan. The ones that have higher intensity +values are marked in white. The central rectangle is left untouched. """ diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 6ff5d60b..b90ab6dd 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -267,28 +267,29 @@ def _local_binary_pattern(double[:, ::1] image, return np.asarray(output) -# Constant values that are used by `multiblock_local_binary_pattern` function. +# Constant values that are used by `_multiblock_lbp` function. # Values represent offsets of neighbour rectangles relative to central one. # It has order starting from top left and going clockwise. cdef: - Py_ssize_t[::1] mlbp_x_offsets = np.asarray([-1, 0, 1, 1, 1, 0, -1, -1]) - Py_ssize_t[::1] mlbp_y_offsets = np.asarray([-1, -1, -1, 0, 1, 1, 1, 0]) + Py_ssize_t[::1] mlbp_r_offsets = np.asarray([-1, -1, -1, 0, 1, 1, 1, 0]) + Py_ssize_t[::1] mlbp_c_offsets = np.asarray([-1, 0, 1, 1, 1, 0, -1, -1]) + def _multiblock_lbp(float[:, ::1] int_image, - Py_ssize_t x, - Py_ssize_t y, + Py_ssize_t r, + Py_ssize_t c, Py_ssize_t width, Py_ssize_t height): - """Multi-block local binary pattern. + """Multi-block local binary pattern (MB-LBP) [1]_. Parameters ---------- int_image : (N, M) float array Integral image. - x : int - X-coordinate of top left corner of a rectangle containing feature. - y : int - Y-coordinate of top left corner of a rectangle containing feature. + r : int + Row-coordinate of top left corner of a rectangle containing feature. + c : int + Column-coordinate of top left corner of a rectangle containing feature. width : int Width of one of 9 equal rectangles that will be used to compute a feature. @@ -309,36 +310,43 @@ def _multiblock_lbp(float[:, ::1] int_image, http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf """ - # Top-left coordinates of central rectangle cdef: - Py_ssize_t central_rect_x = x + width - Py_ssize_t central_rect_y = y + height + # Top-left coordinates of central rectangle. + Py_ssize_t central_rect_r = r + height + Py_ssize_t central_rect_c = c + width - # Sum of intensity values of central rectangle - cdef float central_rect_val = integrate(int_image, central_rect_y, central_rect_x, - central_rect_y + height - 1, - central_rect_x + width - 1) + Py_ssize_t r_shift = height - 1 + Py_ssize_t c_shift = width - 1 - cdef: - Py_ssize_t element_num, offset_x, offset_y - Py_ssize_t current_rect_x, current_rect_y + # Copy offset array to multiply it by width and height later. + Py_ssize_t[::1] r_offsets = mlbp_r_offsets.copy() + Py_ssize_t[::1] c_offsets = mlbp_c_offsets.copy() + + Py_ssize_t current_rect_r, current_rect_c + Py_ssize_t element_num, i double current_rect_val int has_greater_value int lbp_code = 0 + # Pre-multiply offsets with width and height. + for i in range(8): + r_offsets[i] = r_offsets[i]*height + c_offsets[i] = c_offsets[i]*width + + # Sum of intensity values of central rectangle. + cdef float central_rect_val = integrate(int_image, central_rect_r, central_rect_c, + central_rect_r + r_shift, + central_rect_c + c_shift) + for element_num in range(8): - offset_x = mlbp_x_offsets[element_num] - offset_y = mlbp_y_offsets[element_num] + current_rect_r = central_rect_r + r_offsets[element_num] + current_rect_c = central_rect_c + c_offsets[element_num] - current_rect_x = central_rect_x + offset_x * width - current_rect_y = central_rect_y + offset_y * height - - - current_rect_val = integrate(int_image, current_rect_y, current_rect_x, - current_rect_y + height - 1, - current_rect_x + width - 1) + current_rect_val = integrate(int_image, current_rect_r, current_rect_c, + current_rect_r + r_shift, + current_rect_c + c_shift) has_greater_value = current_rect_val >= central_rect_val @@ -348,6 +356,3 @@ def _multiblock_lbp(float[:, ::1] int_image, lbp_code |= has_greater_value << (7 - element_num) return lbp_code - - - diff --git a/skimage/feature/tests/test_texture.py b/skimage/feature/tests/test_texture.py index 1bc44919..c095fdda 100644 --- a/skimage/feature/tests/test_texture.py +++ b/skimage/feature/tests/test_texture.py @@ -1,10 +1,8 @@ import numpy as np -from skimage.feature import ( - greycomatrix, +from skimage.feature import (greycomatrix, greycoprops, local_binary_pattern, - multiblock_lbp - ) + multiblock_lbp) from skimage._shared.testing import test_parallel from skimage.transform import integral_image @@ -239,17 +237,16 @@ class TestMBLBP(): def test_single_mblbp(self): - # Create dummy matrix where first and fifth - # rectangles have greater value than the central one - # Therefore, the following bits should be 1. + # Create dummy matrix where first and fifth rectangles have greater + # value than the central one. Therefore, the following bits + # should be 1. test_img = np.zeros((9, 9), dtype='uint8') test_img[3:6, 3:6] = 1 test_img[:3, :3] = 255 test_img[6:, 6:] = 255 - # MB-LBP is filled in reverse order. - # So the first and fifth bits from the end should - # be filled. + # MB-LBP is filled in reverse order. So the first and fifth bits from + # the end should be filled. correct_answer = 0b10001000 int_img = integral_image(test_img) diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index 0ec13cb3..c26eb59d 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -297,27 +297,27 @@ def local_binary_pattern(image, P, R, method='default'): return output -def multiblock_lbp(int_image, x, y, width, height): - """Multi-block local binary pattern. +def multiblock_lbp(int_image, r, c, width, height): + """Multi-block local binary pattern (MB-LBP) [1]_. The features are calculated similarly to local binary patterns (LBPs), - except that summed blocks are used instead of individual pixel values. + (See :py:meth:`local_binary_pattern`) except that summed blocks are + used instead of individual pixel values. MB-LBP is an extension of LBP that can be computed on multiple scales - in constant time using the integral image. - 9 equally-sized rectangles are used to compute a feature. - For each rectangle, the sum of the pixel intensities is computed. - Comparisons of these sums to that of the central rectangle determine - the feature, similarly to LBP. + in constant time using the integral image. Nine equally-sized rectangles + are used to compute a feature. For each rectangle, the sum of the pixel + intensities is computed. Comparisons of these sums to that of the central + rectangle determine the feature, similarly to LBP. Parameters ---------- int_image : (N, M) array Integral image. - x : int - X-coordinate of top left corner of a rectangle containing feature. - y : int - Y-coordinate of top left corner of a rectangle containing feature. + r : int + Row-coordinate of top left corner of a rectangle containing feature. + c : int + Column-coordinate of top left corner of a rectangle containing feature. width : int Width of one of the 9 equal rectangles that will be used to compute a feature. @@ -339,11 +339,11 @@ def multiblock_lbp(int_image, x, y, width, height): """ int_image = np.ascontiguousarray(int_image, dtype=np.float32) - lbp_code = _multiblock_lbp(int_image, x, y, width, height) + lbp_code = _multiblock_lbp(int_image, r, c, width, height) return lbp_code -def draw_multiblock_lbp(img, x, y, width, height, +def draw_multiblock_lbp(img, r, c, width, height, lbp_code=0, color_greater_block=[1, 1, 1], color_less_block=[0, 0.69, 0.96], @@ -351,19 +351,18 @@ def draw_multiblock_lbp(img, x, y, width, height, ): """Multi-block local binary pattern visualization. - Blocks with higher sums are colored with transparent white rectangles, - whereas blocks with lower sums are colored cyan. - The colors can also be specified. - Opacity of visualization is controlled with `alpha` parameter. + Blocks with higher sums are colored with alpha-blended white rectangles, + whereas blocks with lower sums are colored alpha-blended cyan. Colors + and the `alpha` parameter can be changed. Parameters ---------- - img : + img : ndarray of float or uint Image on which to visualize the pattern. - x : int - X-coordinate of top left corner of a rectangle containing feature. - y : int - Y-coordinate of top left corner of a rectangle containing feature. + r : int + Row-coordinate of top left corner of a rectangle containing feature. + c : int + Column-coordinate of top left corner of a rectangle containing feature. width : int Width of one of 9 equal rectangles that will be used to compute a feature. @@ -371,26 +370,25 @@ def draw_multiblock_lbp(img, x, y, width, height, Height of one of 9 equal rectangles that will be used to compute a feature. lbp_code : int - The descriptor of feature to visualize. If not provided, - the descriptor with 0 value will be used. + The descriptor of feature to visualize. If not provided, the + descriptor with 0 value will be used. color_greater_block : list of 3 floats - Floats specifying the color for the block that - has greater intensity value. They should be - in the range [0, 1]. Corresponding values define - (R, G, B) values. Default value is white [1, 1, 1]. + Floats specifying the color for the block that has greater + intensity value. They should be in the range [0, 1]. + Corresponding values define (R, G, B) values. Default value + is white [1, 1, 1]. color_greater_block : list of 3 floats - Floats specifying the color for the block that - has greater intensity value. They should be - in the range [0, 1]. Corresponding values define + Floats specifying the color for the block that has greater intensity + value. They should be in the range [0, 1]. Corresponding values define (R, G, B) values. Default value is cyan [0, 0.69, 0.96]. alpha : float - Value in the range [0, 1] that specifies opacity of - visualization. 1 - fully transparent, 0 - opaque. + Value in the range [0, 1] that specifies opacity of visualization. + 1 - fully transparent, 0 - opaque. Returns ------- output : ndarray of float - Image with visualization. + Image with MB-LBP visualization. References ---------- @@ -403,8 +401,8 @@ def draw_multiblock_lbp(img, x, y, width, height, # Default colors for regions. # White is for the blocks that are brighter. # Cyan is for the blocks that has less intensity. - color_greater_block = np.asarray(color_greater_block, dtype=np.double) - color_less_block = np.asarray(color_less_block, dtype=np.double) + color_greater_block = np.asarray(color_greater_block, dtype=np.float64) + color_less_block = np.asarray(color_less_block, dtype=np.float64) # Copy array to avoid the changes to the original one. output = np.copy(img) @@ -418,31 +416,36 @@ def draw_multiblock_lbp(img, x, y, width, height, # Offsets of neighbour rectangles relative to central one. # It has order starting from top left and going clockwise. - neighbour_rect_offsets = ((-1, -1), (0, -1), (1, -1), - (1, 0), (1, 1), (0, 1), - (-1, 1), (-1, 0)) + neighbour_rect_offsets = ((-1, -1), (-1, 0), (-1, 1), + (0, 1), (1, 1), (1, 0), + (1, -1), (0, -1)) + + # Pre-multiply the offsets with width and height. + neighbour_rect_offsets = np.array(neighbour_rect_offsets) + neighbour_rect_offsets[:, 0] *= height + neighbour_rect_offsets[:, 1] *= width # Top-left coordinates of central rectangle. - central_rect_x = x + width - central_rect_y = y + height + central_rect_r = r + height + central_rect_c = c + width for element_num, offset in enumerate(neighbour_rect_offsets): - offset_x, offset_y = offset + offset_r, offset_c = offset - curr_x = central_rect_x + offset_x * width - curr_y = central_rect_y + offset_y * height + curr_r = central_rect_r + offset_r + curr_c = central_rect_c + offset_c has_greater_value = lbp_code & (1 << (7-element_num)) # Mix-in the visualization colors. if has_greater_value: - new_value = ((1-alpha) * output[curr_y:curr_y+height, curr_x:curr_x+width] + new_value = ((1-alpha) * output[curr_r:curr_r+height, curr_c:curr_c+width] + alpha * color_greater_block) - output[curr_y:curr_y+height, curr_x:curr_x+width] = new_value + output[curr_r:curr_r+height, curr_c:curr_c+width] = new_value else: - new_value = ((1-alpha) * output[curr_y:curr_y+height, curr_x:curr_x+width] + new_value = ((1-alpha) * output[curr_r:curr_r+height, curr_c:curr_c+width] + alpha * color_less_block) - output[curr_y:curr_y+height, curr_x:curr_x+width] = new_value + output[curr_r:curr_r+height, curr_c:curr_c+width] = new_value return output