From 79616815ff7e2e2a6f82117c95157afc44dc0362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 29 Nov 2013 23:32:50 +0100 Subject: [PATCH] Implement CenSurE detector in object oriented interface --- skimage/feature/__init__.py | 6 +- skimage/feature/{_brief.py => brief.py} | 1 + .../feature/{_brief_cy.pyx => brief_cy.pyx} | 0 skimage/feature/censure.py | 171 ++++++++++-------- skimage/feature/orb.py | 71 +++++--- skimage/feature/setup.py | 4 +- skimage/feature/tests/_test_censure.py | 89 --------- skimage/feature/tests/test_censure.py | 89 +++++++++ 8 files changed, 231 insertions(+), 200 deletions(-) rename skimage/feature/{_brief.py => brief.py} (99%) rename skimage/feature/{_brief_cy.pyx => brief_cy.pyx} (100%) delete mode 100644 skimage/feature/tests/_test_censure.py create mode 100644 skimage/feature/tests/test_censure.py diff --git a/skimage/feature/__init__.py b/skimage/feature/__init__.py index c7a5a42f..6c9c0df5 100644 --- a/skimage/feature/__init__.py +++ b/skimage/feature/__init__.py @@ -9,10 +9,10 @@ from .corner import (corner_kitchen_rosenfeld, corner_harris, hessian_matrix_eigvals) from .corner_cy import corner_moravec, corner_orientations from .template import match_template -from ._brief import BRIEF +from .brief import BRIEF +from .censure import CenSurE from .match import match_binary_descriptors from .util import pairwise_hamming_distance -from .censure import keypoints_censure from .orb import keypoints_orb, descriptor_orb __all__ = ['daisy', @@ -30,9 +30,9 @@ __all__ = ['daisy', 'corner_moravec', 'match_template', 'BRIEF', + 'CenSurE', 'pairwise_hamming_distance', 'match_binary_descriptors', - 'keypoints_censure', 'corner_fast', 'corner_orientations', 'structure_tensor', diff --git a/skimage/feature/_brief.py b/skimage/feature/brief.py similarity index 99% rename from skimage/feature/_brief.py rename to skimage/feature/brief.py index 048cd7bb..e29dd230 100644 --- a/skimage/feature/_brief.py +++ b/skimage/feature/brief.py @@ -101,6 +101,7 @@ class BRIEF(DescriptorExtractor): def __init__(self, descriptor_size=256, patch_size=49, mode='normal', sigma=1, sample_seed=1): + mode = mode.lower() if mode not in ('normal', 'uniform'): raise ValueError("`mode` must be 'normal' or 'uniform'.") diff --git a/skimage/feature/_brief_cy.pyx b/skimage/feature/brief_cy.pyx similarity index 100% rename from skimage/feature/_brief_cy.pyx rename to skimage/feature/brief_cy.pyx diff --git a/skimage/feature/censure.py b/skimage/feature/censure.py index dbe8daf5..dc9cd58c 100644 --- a/skimage/feature/censure.py +++ b/skimage/feature/censure.py @@ -1,7 +1,7 @@ import numpy as np from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve -from .util import _prepare_grayscale_input_2D +from skimage.feature.util import FeatureDetector, _prepare_grayscale_input_2D from skimage.transform import integral_image from skimage.feature import structure_tensor @@ -66,19 +66,19 @@ def _filter_image(image, min_scale, max_scale, mode): mo, no = OCTAGON_OUTER_SHAPE[min_scale + i - 1] mi, ni = OCTAGON_INNER_SHAPE[min_scale + i - 1] response[:, :, i] = convolve(image, - _octagon_filter_kernel(mo, no, mi, ni)) + _octagon_kernel(mo, no, mi, ni)) elif mode == 'star': for i in range(max_scale - min_scale + 1): m = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][0]] n = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][1]] - response[:, :, i] = convolve(image, _star_filter_kernel(m, n)) + response[:, :, i] = convolve(image, _star_kernel(m, n)) return response -def _octagon_filter_kernel(mo, no, mi, ni): +def _octagon_kernel(mo, no, mi, ni): outer = (mo + 2 * no)**2 - 2 * no * (no + 1) inner = (mi + 2 * ni)**2 - 2 * ni * (ni + 1) outer_weight = 1.0 / (outer - inner) @@ -92,7 +92,7 @@ def _octagon_filter_kernel(mo, no, mi, ni): return bfilter -def _star_filter_kernel(m, n): +def _star_kernel(m, n): c = m + m // 2 - n - n // 2 outer_star = star(m) inner_star = np.zeros_like(outer_star) @@ -106,21 +106,15 @@ def _star_filter_kernel(m, n): def _suppress_lines(feature_mask, image, sigma, line_threshold): Axx, Axy, Ayy = structure_tensor(image, sigma) - feature_mask[(Axx + Ayy) * (Axx + Ayy) - > line_threshold * (Axx * Ayy - Axy * Axy)] = False + feature_mask[(Axx + Ayy) ** 2 + > line_threshold * (Axx * Ayy - Axy ** 2)] = False -def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', - non_max_threshold=0.15, line_threshold=10): - """**Experimental function**. - Extracts CenSurE keypoints along with the corresponding scale using - either Difference of Boxes, Octagon or STAR bi-level filter. +class CenSurE(FeatureDetector): + + """CenSurE keypoint detector. - Parameters - ---------- - image : 2D ndarray - Input image. min_scale : int Minimum scale to extract keypoints from. max_scale : int @@ -145,13 +139,6 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', Threshold for rejecting interest points which have ratio of principal curvatures greater than this value. - Returns - ------- - keypoints : (N, 2) array - Location of the extracted keypoints in the ``(row, col)`` format. - scales : (N, 1) array - The corresponding scale of the N extracted keypoints. - References ---------- .. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas @@ -166,69 +153,101 @@ def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB', """ - # (1) First we generate the required scales on the input grayscale image - # using a bi-level filter and stack them up in `filter_response`. - # (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on the - # filter_response to suppress points that are neither minima or maxima in - # 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray `feature_mask` - # containing all the minimas and maximas in `filter_response` as True. - # (3) Then we suppress all the points in the `feature_mask` for which the - # corresponding point in the image at a particular scale has the ratio of - # principal curvatures greater than `line_threshold`. - # (4) Finally, we remove the border keypoints and return the keypoints - # along with its corresponding scale. + def __init__(self, min_scale=1, max_scale=7, mode='DoB', + non_max_threshold=0.15, line_threshold=10): - image = _prepare_grayscale_input_2D(image) + mode = mode.lower() + if mode not in ('dob', 'octagon', 'star'): + raise ValueError("`mode` must be one of 'DoB', 'Octagon', 'STAR'.") - mode = mode.lower() - if mode not in ('dob', 'octagon', 'star'): - raise ValueError('Mode must be one of "DoB", "Octagon", "STAR".') + if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2: + raise ValueError('The scales must be >= 1 and the number of ' + 'scales should be >= 3.') - if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2: - raise ValueError('The scales must be >= 1 and the number of scales ' - 'should be >= 3.') + self.min_scale = min_scale + self.max_scale = max_scale + self.mode = mode + self.non_max_threshold = non_max_threshold + self.line_threshold = line_threshold - image = np.ascontiguousarray(image) + def detect(self, image): + """Detect CenSurE keypoints along with the corresponding scale. - # Generating all the scales - filter_response = _filter_image(image, min_scale, max_scale, mode) + Parameters + ---------- + image : 2D ndarray + Input image. - # Suppressing points that are neither minima or maxima in their 3 x 3 x 3 - # neighbourhood to zero - minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response - maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response + Returns + ------- + keypoints : (N, 2) array + Keypoint coordinates as ``(row, col)``. + scales : (N, 1) array + Corresponding scales of the N extracted keypoints. - feature_mask = minimas | maximas - feature_mask[filter_response < non_max_threshold] = False + """ - for i in range(1, max_scale - min_scale): - # sigma = (window_size - 1) / 6.0, so the window covers > 99% of the - # kernel's distribution - # window_size = 7 + 2 * (min_scale - 1 + i) - # Hence sigma = 1 + (min_scale - 1 + i)/ 3.0 - _suppress_lines(feature_mask[:, :, i], image, - (1 + (min_scale + i - 1) / 3.0), line_threshold) + # (1) First we generate the required scales on the input grayscale + # image using a bi-level filter and stack them up in `filter_response`. - rows, cols, scales = np.nonzero(feature_mask[..., 1:max_scale - min_scale]) - keypoints = np.column_stack([rows, cols]) - scales = scales + min_scale + 1 + # (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on + # the filter_response to suppress points that are neither minima or + # maxima in 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray + # `feature_mask` containing all the minimas and maximas in + # `filter_response` as True. + # (3) Then we suppress all the points in the `feature_mask` for which + # the corresponding point in the image at a particular scale has the + # ratio of principal curvatures greater than `line_threshold`. + # (4) Finally, we remove the border keypoints and return the keypoints + # along with its corresponding scale. - if mode == 'dob': - return keypoints, scales + num_scales = self.max_scale - self.min_scale - cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) + image = np.ascontiguousarray(_prepare_grayscale_input_2D(image)) - if mode == 'octagon': - for i in range(min_scale + 1, max_scale): - c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \ - + OCTAGON_OUTER_SHAPE[i - 1][1] - cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \ - & (scales == i) - elif mode == 'star': - for i in range(min_scale + 1, max_scale): - c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \ - + STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2 - cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \ - & (scales == i) + # Generating all the scales + filter_response = _filter_image(image, self.min_scale, self.max_scale, + self.mode) - return keypoints[cumulative_mask], scales[cumulative_mask] + # Suppressing points that are neither minima or maxima in their + # 3 x 3 x 3 neighborhood to zero + minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response + maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response + + feature_mask = minimas | maximas + feature_mask[filter_response < self.non_max_threshold] = False + + for i in range(1, num_scales): + # sigma = (window_size - 1) / 6.0, so the window covers > 99% of + # the kernel's distribution + # window_size = 7 + 2 * (min_scale - 1 + i) + # Hence sigma = 1 + (min_scale - 1 + i)/ 3.0 + _suppress_lines(feature_mask[:, :, i], image, + (1 + (self.min_scale + i - 1) / 3.0), + self.line_threshold) + + rows, cols, scales = np.nonzero(feature_mask[..., 1:num_scales]) + keypoints = np.column_stack([rows, cols]) + scales = scales + self.min_scale + 1 + + if self.mode == 'dob': + return keypoints, scales + + cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool) + + if self.mode == 'octagon': + for i in range(self.min_scale + 1, self.max_scale): + c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \ + + OCTAGON_OUTER_SHAPE[i - 1][1] + cumulative_mask |= ( + _mask_border_keypoints(image.shape, keypoints, c) + & (scales == i)) + elif self.mode == 'star': + for i in range(self.min_scale + 1, self.max_scale): + c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \ + + STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2 + cumulative_mask |= ( + _mask_border_keypoints(image.shape, keypoints, c) + & (scales == i)) + + return keypoints[cumulative_mask], scales[cumulative_mask] diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py index fb52e40e..74414465 100644 --- a/skimage/feature/orb.py +++ b/skimage/feature/orb.py @@ -1,8 +1,7 @@ import numpy as np from skimage.feature.util import (_mask_border_keypoints, - _prepare_grayscale_input_2D, - create_keypoint_recarray) + _prepare_grayscale_input_2D) from skimage.feature import (corner_fast, corner_orientations, corner_peaks, corner_harris) @@ -12,36 +11,39 @@ from .orb_cy import _orb_loop OFAST_MASK = np.zeros((31, 31)) -umax = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3] +OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3] for i in range(-15, 16): - for j in range(-umax[np.abs(i)], umax[np.abs(i)] + 1): + for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1): OFAST_MASK[15 + j, 15 + i] = 1 + + + def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, harris_k=0.04, downscale=1.2, n_scales=8): - """Detect Oriented Fast keypoints. + """Detect oriented FAST keypoints. Parameters ---------- image : 2D ndarray - Input grayscale image. + Input image. n_keypoints : int - Number of keypoints to be returned from this function. The function - will return best ``n_keypoints`` if more than n_keypoints are detected - based on the values of other parameters. If not, then all the detected - keypoints are returned. + Number of keypoints to be returned. The function will return the best + ``n_keypoints`` according to the Harris corner response if more than + ``n_keypoints`` are detected. If not, then all the detected keypoints + are returned. fast_n : int The ``n`` parameter in ``feature.corner_fast``. Minimum number of consecutive pixels out of 16 pixels on the circle that should all be - either brighter or darker w.r.t testpixel. A point c on the circle is + either brighter or darker w.r.t test-pixel. A point c on the circle is darker w.r.t test pixel p if ``Ic < Ip - threshold`` and brighter if ``Ic > Ip + threshold``. Also stands for the n in ``FAST-n`` corner detector. fast_threshold : float - The ``threshold`` parameter in ``feature.corner_fast``. Threshold used to - decide whether the pixels on the circle are brighter, darker or + The ``threshold`` parameter in ``feature.corner_fast``. Threshold used + to decide whether the pixels on the circle are brighter, darker or similar w.r.t. the test pixel. Decrease the threshold when more corners are desired and vice-versa. harris_k : float @@ -50,15 +52,17 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, values of k result in detection of sharp corners. downscale : float Downscale factor for the image pyramid. Default value 1.2 is chosen so - that we have more dense scales that enable robust scale invariance. + that there are more dense scales which enable robust scale invariance + for a subsequent feature description. n_scales : int - Number of scales from the bottom of the image pyramid to extract - the features from. + Maximum number of scales from the bottom of the image pyramid to + extract the features from. Returns ------- - keypoints : record array - Record array with fields row, col, octave, orientation, response. + keypoints : (N, ...) recarray + Record array as returned by `skimage.feature.create_keypoint_recarray` + with the fields: `row`, `col`, `scale`, `orientation` and `response`. References ---------- @@ -97,46 +101,53 @@ def keypoints_orb(image, n_keypoints=500, fast_n=9, fast_threshold=0.08, scales_list = [] harris_response_list = [] - for scale in range(n_scales): + for octave in range(len(pyramid)): - corners = corner_peaks(corner_fast(pyramid[scale], fast_n, + # Extract keypoints for current octave + corners = corner_peaks(corner_fast(pyramid[octave], fast_n, fast_threshold), min_distance=1) - keypoints_list.append(corners * downscale ** scale) - orientations_list.append(corner_orientations(pyramid[scale], corners, + # Scale keypoint coordinates so they correspond to the original + # image shape + keypoints_list.append(corners * downscale ** octave) + + orientations_list.append(corner_orientations(pyramid[octave], corners, OFAST_MASK)) - scales_list.append(scale * np.ones(corners.shape[0], dtype=np.intp)) + scales_list.append(octave * np.ones(corners.shape[0], dtype=np.intp)) - harris_response = corner_harris(pyramid[scale], method='k', k=harris_k) + harris_response = corner_harris(pyramid[octave], method='k', + k=harris_k) harris_response_list.append(harris_response[corners[:, 0], corners[:, 1]]) keypoints_array = np.vstack(keypoints_list) orientations = np.hstack(orientations_list) - octaves = downscale ** np.hstack(scales_list) + scales = downscale ** np.hstack(scales_list) harris_measure = np.hstack(harris_response_list) keypoints = create_keypoint_recarray(keypoints_array[:, 0], keypoints_array[:, 1], - octaves, orientations, + scales, orientations, harris_measure) if keypoints.shape[0] < n_keypoints: return keypoints else: + # Choose best n_keypoints according to Harris corner response best_indices = harris_measure.argsort()[::-1][:n_keypoints] return keypoints[best_indices] def descriptor_orb(image, keypoints, downscale=1.2, n_scales=8): - """Compute rBRIEF descriptors of input keypoints. + """Compute rBRIEF descriptors for keypoints. Parameters ---------- image : 2D ndarray - Input grayscale image. - keypoints : record array - Record array with fields row, col, octave, orientation, response. + Input image. + keypoints : (N, ...) recarray + Record array as returned by `skimage.feature.create_keypoint_recarray` + with the fields: `row`, `col`, `scale`, `orientation` and `response`. downscale : float Downscale factor for the image pyramid. Should be the same as that used in ``keypoints_orb``. diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 9bd72ca3..9dc35643 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -15,7 +15,7 @@ def configuration(parent_package='', top_path=None): cython(['corner_cy.pyx'], working_path=base_path) cython(['censure_cy.pyx'], working_path=base_path) cython(['orb_cy.pyx'], working_path=base_path) - cython(['_brief_cy.pyx'], working_path=base_path) + cython(['brief_cy.pyx'], working_path=base_path) cython(['match_cy.pyx'], working_path=base_path) cython(['_texture.pyx'], working_path=base_path) cython(['_template.pyx'], working_path=base_path) @@ -26,7 +26,7 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('orb_cy', sources=['orb_cy.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('_brief_cy', sources=['_brief_cy.c'], + config.add_extension('brief_cy', sources=['brief_cy.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('match_cy', sources=['match_cy.c'], include_dirs=[get_numpy_include_dirs()]) diff --git a/skimage/feature/tests/_test_censure.py b/skimage/feature/tests/_test_censure.py deleted file mode 100644 index 4cd2ad68..00000000 --- a/skimage/feature/tests/_test_censure.py +++ /dev/null @@ -1,89 +0,0 @@ -import numpy as np -from numpy.testing import assert_array_equal, assert_raises -from skimage.data import moon -from skimage.feature import keypoints_censure - - -def test_keypoints_censure_color_image_unsupported_error(): - """Censure keypoints can be extracted from gray-scale images only.""" - img = np.zeros((20, 20, 3)) - assert_raises(ValueError, keypoints_censure, img) - - -def test_keypoints_censure_mode_validity_error(): - """Mode argument in keypoints_censure can be either DoB, Octagon or - STAR.""" - img = np.zeros((20, 20)) - assert_raises(ValueError, keypoints_censure, img, mode='dummy') - - -def test_keypoints_censure_scale_range_error(): - """Difference between the the max_scale and min_scale parameters in - keypoints_censure should be greater than or equal to two.""" - img = np.zeros((20, 20)) - assert_raises(ValueError, keypoints_censure, img, min_scale=1, max_scale=2) - - -def test_keypoints_censure_moon_image_dob(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for DoB filter.""" - img = moon() - actual_kp_dob, actual_scale = keypoints_censure(img, 1, 7, 'DoB', 0.15) - expected_kp_dob = np.array([[ 21, 497], - [ 36, 46], - [119, 350], - [185, 177], - [287, 250], - [357, 239], - [463, 116], - [464, 132], - [467, 260]]) - expected_scale = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) - - assert_array_equal(expected_kp_dob, actual_kp_dob) - assert_array_equal(expected_scale, actual_scale) - - -def test_keypoints_censure_moon_image_octagon(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for Octagon filter.""" - img = moon() - actual_kp_octagon, actual_scale = keypoints_censure(img, 1, 7, 'Octagon', - 0.15) - expected_kp_octagon = np.array([[ 21, 496], - [ 35, 46], - [287, 250], - [356, 239], - [463, 116]]) - - expected_scale = np.array([3, 4, 2, 2, 2]) - - assert_array_equal(expected_kp_octagon, actual_kp_octagon) - assert_array_equal(expected_scale, actual_scale) - - -def test_keypoints_censure_moon_image_star(): - """Verify the actual Censure keypoints and their corresponding scale with - the expected values for STAR filter.""" - img = moon() - actual_kp_star, actual_scale = keypoints_censure(img, 1, 7, 'STAR', 0.15) - expected_kp_star = np.array([[ 21, 497], - [ 36, 46], - [117, 356], - [185, 177], - [260, 227], - [287, 250], - [357, 239], - [451, 281], - [463, 116], - [467, 260]]) - - expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) - - assert_array_equal(expected_kp_star, actual_kp_star) - assert_array_equal(expected_scale, actual_scale) - - -if __name__ == '__main__': - from numpy import testing - testing.run_module_suite() diff --git a/skimage/feature/tests/test_censure.py b/skimage/feature/tests/test_censure.py new file mode 100644 index 00000000..611604b6 --- /dev/null +++ b/skimage/feature/tests/test_censure.py @@ -0,0 +1,89 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_raises +from skimage.data import moon +from skimage.feature import CenSurE + + +img = moon() + + +def test_keypoints_censure_color_image_unsupported_error(): + """Censure keypoints can be extracted from gray-scale images only.""" + assert_raises(ValueError, CenSurE().detect, np.zeros((20, 20, 3))) + + +def test_keypoints_censure_mode_validity_error(): + """Mode argument in keypoints_censure can be either DoB, Octagon or + STAR.""" + assert_raises(ValueError, CenSurE, mode='dummy') + + +def test_keypoints_censure_scale_range_error(): + """Difference between the the max_scale and min_scale parameters in + keypoints_censure should be greater than or equal to two.""" + assert_raises(ValueError, CenSurE, min_scale=1, max_scale=2) + + +def test_keypoints_censure_moon_image_dob(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for DoB filter.""" + detector = CenSurE() + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 497], + [ 36, 46], + [119, 350], + [185, 177], + [287, 250], + [357, 239], + [463, 116], + [464, 132], + [467, 260]]) + expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scales, scales) + + +def test_keypoints_censure_moon_image_octagon(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for Octagon filter.""" + + detector = CenSurE(mode='octagon') + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 496], + [ 35, 46], + [287, 250], + [356, 239], + [463, 116]]) + + expected_scales = np.array([3, 4, 2, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scales, scales) + + +def test_keypoints_censure_moon_image_star(): + """Verify the actual Censure keypoints and their corresponding scale with + the expected values for STAR filter.""" + detector = CenSurE(mode='star') + keypoints, scales = detector.detect(img) + expected_keypoints = np.array([[ 21, 497], + [ 36, 46], + [117, 356], + [185, 177], + [260, 227], + [287, 250], + [357, 239], + [451, 281], + [463, 116], + [467, 260]]) + + expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2]) + + assert_array_equal(expected_keypoints, keypoints) + assert_array_equal(expected_scale, scales) + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite()