diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d582ab63..1293b0ae 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -132,3 +132,10 @@ - François Boulogne Andres Method for circle perimeter, ellipse perimeter drawing. + Circular Hough Transform + +- Thouis Jones + Vectorized operators for arrays of 16-bit ints. + +- Xavier Moles Lopez + Color separation (color deconvolution) for several stainings. diff --git a/DEVELOPMENT.txt b/DEVELOPMENT.txt index 8726394b..daffe705 100644 --- a/DEVELOPMENT.txt +++ b/DEVELOPMENT.txt @@ -81,6 +81,9 @@ Stylistic Guidelines hough(canny(my_image)) + * Use `Py_ssize_t` as data type for all indexing, shape and size variables in + C/C++ and Cython code. + Test coverage ------------- diff --git a/RELEASE.txt b/RELEASE.txt index ce05e24e..1131a91a 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -12,7 +12,7 @@ How to make a new release of ``skimage`` - Edit ``doc/source/themes/agogo/static/docversions.js`` and commit - Build a clean version of the docs. Run ``make`` in the root dir, then - ``rm build -rf; make html`` in the docs. + ``rm -rf build; make html`` in the docs. - Run ``make html`` again to copy the newly generated ``random.js`` into place. Double check ``random.js``, otherwise the skimage.org front page gets broken! @@ -49,8 +49,16 @@ How to make a new release of ``skimage`` - Build using ``make gh-pages``. - Push upstream: ``git push`` in ``gh-pages``. +- Update the development docs for the new version ``0.Xdev`` just like above + - Post release notes on mailing lists, blog, G+, etc. + - scikit-image@googlegroups.com + - scipy-user@scipy.org + - scikit-learn-general@lists.sourceforge.net + - pythonvision@googlegroups.com + + Debian ------ diff --git a/bento.info b/bento.info index 57d9f714..71ef558e 100644 --- a/bento.info +++ b/bento.info @@ -1,5 +1,5 @@ Name: scikit-image -Version: 0.8.dev0 +Version: 0.9.dev0 Summary: Image processing routines for SciPy Url: http://scikit-image.org DownloadUrl: http://github.com/scikit-image/scikit-image diff --git a/doc/Makefile b/doc/Makefile index 7552be6b..311ce3ca 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -90,7 +90,7 @@ devhelp: @echo "# ln -s build/devhelp $$HOME/.local/share/devhelp/scikitimage" @echo "# devhelp" -latex: +latex: api $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DEST)/latex @echo @echo "Build finished; the LaTeX files are in $(DEST)/latex." diff --git a/doc/examples/applications/plot_coins_segmentation.py b/doc/examples/applications/plot_coins_segmentation.py index f46bbd33..0d1d8a34 100644 --- a/doc/examples/applications/plot_coins_segmentation.py +++ b/doc/examples/applications/plot_coins_segmentation.py @@ -90,12 +90,8 @@ plt.title('Filling the holes') Small spurious objects are easily removed by setting a minimum size for valid objects. """ - -label_objects, nb_labels = ndimage.label(fill_coins) -sizes = np.bincount(label_objects.ravel()) -mask_sizes = sizes > 20 -mask_sizes[0] = 0 -coins_cleaned = mask_sizes[label_objects] +from skimage import morphology +coins_cleaned = morphology.remove_small_objects(fill_coins, 21) plt.figure(figsize=(4, 3)) plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest') @@ -149,8 +145,7 @@ plt.title('markers') Finally, we use the watershed transform to fill regions of the elevation map starting from the markers determined above: """ -from skimage.morphology import watershed -segmentation = watershed(elevation_map, markers) +segmentation = morphology.watershed(elevation_map, markers) plt.figure(figsize=(4, 3)) plt.imshow(segmentation, cmap=plt.cm.gray, interpolation='nearest') diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py new file mode 100755 index 00000000..d2f8f2ae --- /dev/null +++ b/doc/examples/plot_circular_hough_transform.py @@ -0,0 +1,72 @@ +""" +======================== +Circular Hough Transform +======================== + +The Hough transform in its simplest form is a `method to detect +straight lines `__ +but it can also be used to detect circles. + +In the following example, the Hough transform is used to detect +coin positions and match their edges. We provide a range of +plausible radii. For each radius, two circles are extracted and +we finally keep the five most prominent candidates. +The result shows that coin positions are well-detected. + + +Algorithm overview +------------------ + +Given a black circle on a white background, we first guess its +radius (or a range of radii) to construct a new circle. +This circle is applied on each black pixel of the original picture +and the coordinates of this circle are voting in an accumulator. +From this geometrical construction, the original circle center +position receives the highest score. + +Note that the accumulator size is built to be larger than the +original picture in order to detect centers outside the frame. +Its size is extended by two times the larger radius. + +""" + + +import numpy as np +import matplotlib.pyplot as plt + +from skimage import data, filter, color +from skimage.transform import hough_circle +from skimage.feature import peak_local_max +from skimage.draw import circle_perimeter + +# Load picture and detect edges +image = data.coins()[0:95, 70:370] +edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50) + +fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) + +# Detect two radii +hough_radii = np.arange(15, 30, 2) +hough_res = hough_circle(edges, hough_radii) + +centers = [] +accums = [] +radii = [] + +for radius, h in zip(hough_radii, hough_res): + # For each radius, extract two circles + peaks = peak_local_max(h, num_peaks=2) + centers.extend(peaks - hough_radii.max()) + accums.extend(h[peaks[:, 0], peaks[:, 1]]) + radii.extend([radius, radius]) + +# Draw the most prominent 5 circles +image = color.gray2rgb(image) +for idx in np.argsort(accums)[::-1][:5]: + center_x, center_y = centers[idx] + radius = radii[idx] + cx, cy = circle_perimeter(center_y, center_x, radius) + image[cy, cx] = (220, 20, 20) + +ax.imshow(image, cmap=plt.cm.gray) +plt.show() diff --git a/doc/examples/plot_corner.py b/doc/examples/plot_corner.py index 4d01d177..30f1f0cf 100644 --- a/doc/examples/plot_corner.py +++ b/doc/examples/plot_corner.py @@ -11,7 +11,6 @@ position of corners. """ -import numpy as np from matplotlib import pyplot as plt from skimage import data diff --git a/doc/examples/plot_equalize.py b/doc/examples/plot_equalize.py index f57cea9c..3569c4ee 100644 --- a/doc/examples/plot_equalize.py +++ b/doc/examples/plot_equalize.py @@ -19,7 +19,6 @@ that fall within the 2nd and 98th percentiles [2]_. """ from skimage import data, img_as_float -from skimage.util.dtype import dtype_range from skimage import exposure import matplotlib.pyplot as plt diff --git a/doc/examples/plot_ihc_color_separation.py b/doc/examples/plot_ihc_color_separation.py new file mode 100644 index 00000000..626d76ab --- /dev/null +++ b/doc/examples/plot_ihc_color_separation.py @@ -0,0 +1,70 @@ +""" +============================================== +Immunohistochemical staining colors separation +============================================== + +In this example we separate the immunohistochemical (IHC) staining +from the hematoxylin counterstaining. The separation is achieved with the +method described in [1]_, known as "color deconvolution". + +The IHC staining expression of the FHL2 protein is here revealed with +Diaminobenzidine (DAB) which gives a brown color. + + +.. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. +""" +import matplotlib.pyplot as plt + +from skimage import data +from skimage.color import rgb2hed + +ihc_rgb = data.immunohistochemistry() +ihc_hed = rgb2hed(ihc_rgb) + +fig, axes = plt.subplots(2, 2, figsize=(7, 6)) +ax0, ax1, ax2, ax3 = axes.ravel() + +ax0.imshow(ihc_rgb) +ax0.set_title("Original image") + +ax1.imshow(ihc_hed[:, :, 0], cmap=plt.cm.gray) +ax1.set_title("Hematoxylin") + +ax2.imshow(ihc_hed[:, :, 1], cmap=plt.cm.gray) +ax2.set_title("Eosin") + +ax3.imshow(ihc_hed[:, :, 2], cmap=plt.cm.gray) +ax3.set_title("DAB") + +for ax in axes.ravel(): + ax.axis('off') + +fig.subplots_adjust(hspace=0.3) + + +""" +.. image:: PLOT2RST.current_figure + +Now we can easily manipulate the hematoxylin and DAB "channels": +""" +import numpy as np + +from skimage.exposure import rescale_intensity + +# Rescale hematoxylin and DAB signals and give them a fluorescence look +h = rescale_intensity(ihc_hed[:, :, 0], out_range=(0, 1)) +d = rescale_intensity(ihc_hed[:, :, 2], out_range=(0, 1)) +zdh = np.dstack((np.zeros_like(h), d, h)) + +plt.figure() +plt.imshow(zdh) +plt.title("Stain separated image (rescaled)") +plt.axis('off') +plt.show() + +""" +.. image:: PLOT2RST.current_figure +""" diff --git a/doc/examples/plot_local_binary_pattern.py b/doc/examples/plot_local_binary_pattern.py index 85fd5b95..5429df3a 100644 --- a/doc/examples/plot_local_binary_pattern.py +++ b/doc/examples/plot_local_binary_pattern.py @@ -12,8 +12,8 @@ each other using the Kullback-Leibler-Divergence. import numpy as np import matplotlib import matplotlib.pyplot as plt -import scipy.ndimage as nd -import skimage.feature as ft +from skimage.transform import rotate +from skimage.feature import local_binary_pattern from skimage import data @@ -34,7 +34,7 @@ def kullback_leibler_divergence(p, q): def match(refs, img): best_score = 10 best_name = None - lbp = ft.local_binary_pattern(img, P, R, METHOD) + lbp = local_binary_pattern(img, P, R, METHOD) hist, _ = np.histogram(lbp, normed=True, bins=P + 2, range=(0, P + 2)) for name, ref in refs.items(): ref_hist, _ = np.histogram(ref, normed=True, bins=P + 2, @@ -51,19 +51,19 @@ grass = data.load('grass.png') wall = data.load('rough-wall.png') refs = { - 'brick': ft.local_binary_pattern(brick, P, R, METHOD), - 'grass': ft.local_binary_pattern(grass, P, R, METHOD), - 'wall': ft.local_binary_pattern(wall, P, R, METHOD) + 'brick': local_binary_pattern(brick, P, R, METHOD), + 'grass': local_binary_pattern(grass, P, R, METHOD), + 'wall': local_binary_pattern(wall, P, R, METHOD) } # classify rotated textures print 'Rotated images matched against references using LBP:' print 'original: brick, rotated: 30deg, match result:', -print match(refs, nd.rotate(brick, angle=30, reshape=False)) +print match(refs, rotate(brick, angle=30, resize=False)) print 'original: brick, rotated: 70deg, match result:', -print match(refs, nd.rotate(brick, angle=70, reshape=False)) +print match(refs, rotate(brick, angle=70, resize=False)) print 'original: grass, rotated: 145deg, match result:', -print match(refs, nd.rotate(grass, angle=145, reshape=False)) +print match(refs, rotate(grass, angle=145, resize=False)) # plot histograms of LBP of textures fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3, diff --git a/doc/examples/plot_local_equalize.py b/doc/examples/plot_local_equalize.py index bc458505..33b2c2e4 100644 --- a/doc/examples/plot_local_equalize.py +++ b/doc/examples/plot_local_equalize.py @@ -57,7 +57,7 @@ img = data.moon() # Contrast stretching p2 = np.percentile(img, 2) p98 = np.percentile(img, 98) -img_rescale = exposure.equalize(img) +img_rescale = exposure.equalize_hist(img) # Equalization selem = disk(30) diff --git a/doc/examples/plot_radon_transform.py b/doc/examples/plot_radon_transform.py index efd8a776..adca8162 100644 --- a/doc/examples/plot_radon_transform.py +++ b/doc/examples/plot_radon_transform.py @@ -22,11 +22,11 @@ import matplotlib.pyplot as plt from skimage.io import imread from skimage import data_dir -from skimage.transform import radon, iradon -from scipy.ndimage import zoom +from skimage.transform import radon, iradon, rescale + image = imread(data_dir + "/phantom.png", as_grey=True) -image = zoom(image, 0.4) +image = rescale(image, scale=0.4) plt.figure(figsize=(8, 8.5)) diff --git a/doc/examples/plot_random_walker_segmentation.py b/doc/examples/plot_random_walker_segmentation.py index 6d194293..7d9f4fa8 100644 --- a/doc/examples/plot_random_walker_segmentation.py +++ b/doc/examples/plot_random_walker_segmentation.py @@ -19,7 +19,6 @@ values, and use the random walker for the segmentation. .. [1] *Random walks for image segmentation*, Leo Grady, IEEE Trans. Pattern Anal. Mach. Intell. 2006 Nov; 28(11):1768-83 """ -print __doc__ import numpy as np from scipy import ndimage diff --git a/doc/examples/plot_regionprops.py b/doc/examples/plot_regionprops.py index 90b40a89..5a8ea01c 100644 --- a/doc/examples/plot_regionprops.py +++ b/doc/examples/plot_regionprops.py @@ -13,23 +13,15 @@ import numpy as np from skimage.draw import ellipse from skimage.morphology import label from skimage.measure import regionprops -from scipy.ndimage import geometric_transform +from skimage.transform import rotate -ANGLE = 0.2 - -def rotate(xy): - x, y = xy - out_x = math.cos(ANGLE) * x - math.sin(ANGLE) * y - out_y = math.sin(ANGLE) * x + math.cos(ANGLE) * y - return (out_x, out_y) - -image = np.zeros((600, 600), 'int') +image = np.zeros((600, 600)) rr, cc = ellipse(300, 350, 100, 220) image[rr,cc] = 1 -image = geometric_transform(image, rotate) +image = rotate(image, angle=15, order=0) label_img = label(image) props = regionprops(label_img, [ diff --git a/doc/ext/plot2rst.py b/doc/ext/plot2rst.py index 65c77521..00560be4 100644 --- a/doc/ext/plot2rst.py +++ b/doc/ext/plot2rst.py @@ -69,6 +69,7 @@ import os import shutil import token import tokenize +import traceback import numpy as np import matplotlib @@ -247,7 +248,16 @@ def write_gallery(gallery_index, src_dir, rst_dir, cfg, depth=0): gallery_index.write(TOCTREE_TEMPLATE % (sub_dir + '\n '.join(ex_names))) for src_name in examples: - write_example(src_name, src_dir, rst_dir, cfg) + + try: + write_example(src_name, src_dir, rst_dir, cfg) + except Exception: + print "Exception raised while running:" + print "%s in %s" % (src_name, src_dir) + print '~' * 60 + traceback.print_exc() + print '~' * 60 + continue link_name = sub_dir.pjoin(src_name) link_name = link_name.replace(os.path.sep, '_') diff --git a/doc/logo/scikit_image_logo.py b/doc/logo/scikit_image_logo.py index de460344..fc8ec006 100644 --- a/doc/logo/scikit_image_logo.py +++ b/doc/logo/scikit_image_logo.py @@ -1,17 +1,26 @@ """ Script to draw skimage logo using Scipy logo as stencil. The easiest -starting point is the `plot_colorized_logo`; the "if-main" demonstrates its use. +starting point is the `plot_colorized_logo`. Original snake image from pixabay [1]_ .. [1] http://pixabay.com/en/snake-green-toxic-close-yellow-3237/ """ -import numpy as np +import sys +if len(sys.argv) != 2 or sys.argv[1] != '--no-plot': + print "Run with '--no-plot' flag to generate logo silently." +else: + import matplotlib as mpl + mpl.use('Agg') import matplotlib.pyplot as plt -import scipy.misc + +import numpy as np import skimage.io as sio -import skimage.filter as imfilt +from skimage import img_as_float +from skimage.color import gray2rgb, rgb2gray +from skimage.exposure import rescale_intensity +from skimage.filter import sobel import scipy_logo @@ -19,42 +28,21 @@ import scipy_logo # Utility functions # ================= -def get_edges(img): - edge = np.empty(img.shape) - if len(img.shape) == 3: - for i in range(3): - edge[:, :, i] = imfilt.sobel(img[:, :, i]) - else: - edge = imfilt.sobel(img) - edge = rescale_intensity(edge) - return edge +def colorize(image, color, whiten=False): + """Return colorized image from gray scale image. -def rescale_intensity(img): - i_range = float(img.max() - img.min()) - img = (img - img.min()) / i_range * 255 - return np.uint8(img) - -def colorize(img, color, whiten=False): - """Return colorized image from gray scale image - - Parameters - ---------- - img : N x M array - grayscale image - color : length-3 sequence of floats - RGB color spec. Float values should be between 0 and 1. - whiten : bool - If True, a color value less than 1 increases the image intensity. + The colorized image has values from ranging between black at the lowest + intensity to `color` at the highest. If `whiten=True`, then the color + ranges from `color` to white. """ color = np.asarray(color)[np.newaxis, np.newaxis, :] - img = img[:, :, np.newaxis] + image = image[:, :, np.newaxis] if whiten: # truncate and stretch intensity range to enhance contrast - img = np.clip(img, 80, 255) - img = rescale_intensity(img) - return np.uint8(color * (255 - img) + img) + image = rescale_intensity(image, in_range=(0.3, 1)) + return color * (1 - image) + image else: - return np.uint8(img * color) + return image * color def prepare_axes(ax): @@ -65,16 +53,6 @@ def prepare_axes(ax): spine.set_visible(False) -_rgb_stack = np.ones((1, 1, 3), dtype=bool) -def gray2rgb(arr): - """Return RGB image from a grayscale image. - - Expand h x w image to h x w x 3 image where color channels are simply copies - of the grayscale image. - """ - return arr[:, :, np.newaxis] * _rgb_stack - - # Logo generating classes # ======================= @@ -82,21 +60,17 @@ class LogoBase(object): def __init__(self): self.logo = scipy_logo.ScipyLogo(radius=self.radius) - self.mask_1 = self.logo.get_mask(self.img.shape, 'upper left') - self.mask_2 = self.logo.get_mask(self.img.shape, 'lower right') - self.edges = get_edges(self.img) + self.mask_1 = self.logo.get_mask(self.image.shape, 'upper left') + self.mask_2 = self.logo.get_mask(self.image.shape, 'lower right') + + edges = np.array([sobel(img) for img in self.image.T]).T # truncate and stretch intensity range to enhance contrast - self.edges = np.clip(self.edges, 0, 100) - self.edges = rescale_intensity(self.edges) + self.edges = rescale_intensity(edges, in_range=(0, 0.4)) - - def _crop_image(self, img): + def _crop_image(self, image): w = 2 * self.radius x, y = self.origin - return img[y:y+w, x:x+w] - - def get_canvas(self): - return 255 * np.ones(self.img.shape, dtype=np.uint8) + return image[y:y + w, x:x + w] def plot_curve(self, **kwargs): self.logo.plot_snake_curve(**kwargs) @@ -104,15 +78,13 @@ class LogoBase(object): class SnakeLogo(LogoBase): - def __init__(self): - self.radius = 250 - self.origin = (420, 0) - img = sio.imread('data/snake_pixabay.jpg') - img = self._crop_image(img) + radius = 250 + origin = (420, 0) - img = img.astype(float) * 1.1 - img[img > 255] = 255 - self.img = img.astype(np.uint8) + def __init__(self): + image = sio.imread('data/snake_pixabay.jpg') + image = self._crop_image(image) + self.image = img_as_float(image) LogoBase.__init__(self) @@ -120,107 +92,75 @@ class SnakeLogo(LogoBase): snake_color = SnakeLogo() snake = SnakeLogo() # turn RGB image into gray image -snake.img = np.mean(snake.img, axis=2) -snake.edges = np.mean(snake.edges, axis=2) +snake.image = rgb2gray(snake.image) +snake.edges = rgb2gray(snake.edges) # Demo plotting functions # ======================= -def plot_colorized_logo(logo, color, edges='light', switch=False, whiten=False): - """Convenience function to plot artificially colored logo. +def plot_colorized_logo(logo, color, edges='light', whiten=False): + """Convenience function to plot artificially-colored logo. + + The upper-left half of the logo is an edge filtered image, while the + lower-right half is unfiltered. Parameters ---------- - logo : subclass of LogoBase - color : length-3 sequence of floats + logo : LogoBase instance + color : length-3 sequence of floats or 2 length-3 sequences RGB color spec. Float values should be between 0 and 1. edges : {'light'|'dark'} Specifies whether Sobel edges are drawn light or dark - switch : bool - If False, the image is drawn on the southeast half of the Scipy curve - and the edge image is drawn on northwest half. - whiten : bool + whiten : bool or 2 bools If True, a color value less than 1 increases the image intensity. """ if not hasattr(color[0], '__iter__'): - color = [color] * 2 + color = [color] * 2 # use same color for upper-left & lower-right if not hasattr(whiten, '__iter__'): - whiten = [whiten] * 2 - img = gray2rgb(logo.get_canvas()) + whiten = [whiten] * 2 # use same setting for upper-left & lower-right + + image = gray2rgb(np.ones_like(logo.image)) mask_img = gray2rgb(logo.mask_2) mask_edge = gray2rgb(logo.mask_1) - if switch: - mask_img, mask_edge = mask_edge, mask_img + + # Compose image with colorized image and edge-image. if edges == 'dark': - lg_edge = colorize(255 - logo.edges, color[0], whiten=whiten[0]) + logo_edge = colorize(1 - logo.edges, color[0], whiten=whiten[0]) else: - lg_edge = colorize(logo.edges, color[0], whiten=whiten[0]) - lg_img = colorize(logo.img, color[1], whiten=whiten[1]) - img[mask_img] = lg_img[mask_img] - img[mask_edge] = lg_edge[mask_edge] - logo.plot_curve(lw=5, color='w') - plt.imshow(img) + logo_edge = colorize(logo.edges, color[0], whiten=whiten[0]) + logo_img = colorize(logo.image, color[1], whiten=whiten[1]) + image[mask_img] = logo_img[mask_img] + image[mask_edge] = logo_edge[mask_edge] - -def red_light_edges(logo, **kwargs): - plot_colorized_logo(logo, (1, 0, 0), edges='light', **kwargs) - - -def red_dark_edges(logo, **kwargs): - plot_colorized_logo(logo, (1, 0, 0), edges='dark', **kwargs) - -def blue_light_edges(logo, **kwargs): - plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='light', **kwargs) - - -def blue_dark_edges(logo, **kwargs): - plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='dark', **kwargs) - - -def green_orange_light_edges(logo, **kwargs): - colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) - plot_colorized_logo(logo, colors, edges='light', **kwargs) - -def green_orange_dark_edges(logo, **kwargs): - colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) - plot_colorized_logo(logo, colors, edges='dark', **kwargs) + logo.plot_curve(lw=5, color='w') # plot snake curve on current axes + plt.imshow(image) if __name__ == '__main__': - - import sys - plot = False - if len(sys.argv) < 2 or sys.argv[1] != '--no-plot': - plot = True - - print "Run with '--no-plot' flag to generate logo silently." + # Colors to use for the logo: + red = (1, 0, 0) + blue = (0.35, 0.55, 0.85) + green_orange = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) def plot_all(): - plotters = (red_light_edges, red_dark_edges, - blue_light_edges, blue_dark_edges, - green_orange_light_edges, green_orange_dark_edges) - - f, axes_array = plt.subplots(nrows=2, ncols=len(plotters)) - for plot, ax_col in zip(plotters, axes_array.T): - prepare_axes(ax_col[0]) - plot(snake) - prepare_axes(ax_col[1]) - plot(snake, whiten=True) + color_list = [red, blue, green_orange] + edge_list = ['light', 'dark'] + f, axes = plt.subplots(nrows=len(edge_list), ncols=len(color_list)) + for axes_row, edges in zip(axes, edge_list): + for ax, color in zip(axes_row, color_list): + prepare_axes(ax) + plot_colorized_logo(snake, color, edges=edges) plt.tight_layout() - def plot_snake(): - + def plot_official_logo(): f, ax = plt.subplots() prepare_axes(ax) - green_orange_dark_edges(snake, whiten=(False, True)) + plot_colorized_logo(snake, green_orange, edges='dark', + whiten=(False, True)) plt.savefig('green_orange_snake.png', bbox_inches='tight') - if plot: - plot_all() - - plot_snake() - - if plot: - plt.show() + plot_all() + plot_official_logo() + plt.show() diff --git a/doc/logo/scipy_logo.py b/doc/logo/scipy_logo.py index c9de053a..c57cd95b 100644 --- a/doc/logo/scipy_logo.py +++ b/doc/logo/scipy_logo.py @@ -3,10 +3,11 @@ Code used to trace Scipy logo. """ import numpy as np import matplotlib.pyplot as plt -import skimage.io as imgio -from scipy.misc import lena import matplotlib.nxutils as nx +from skimage import io +from skimage import data + class SymmetricAnchorPoint(object): """Anchor point in a parametric curve with symmetric handles @@ -185,7 +186,7 @@ class ScipyLogo(object): def plot_image(self, **kwargs): ax = kwargs.pop('ax', plt.gca()) - img = imgio.imread('data/scipy.png') + img = io.imread('data/scipy.png') ax.imshow(img, **kwargs) def get_mask(self, shape, region): @@ -236,9 +237,7 @@ def plot_snake_overlay(): logo = ScipyLogo((670, 250), 250) logo.plot_snake_curve() logo.plot_circle() - img = imgio.imread('data/snake_pixabay.jpg') - #mask = logo.get_mask(img.shape, 'upper left') - #img[mask] = 255 + img = io.imread('data/snake_pixabay.jpg') plt.imshow(img) @@ -247,9 +246,7 @@ def plot_lena_overlay(): logo = ScipyLogo((300, 300), 180) logo.plot_snake_curve() logo.plot_circle() - img = lena() - #mask = logo.get_mask(img.shape, 'upper left') - #img[mask] = 255 + img = data.lena() plt.imshow(img) @@ -259,4 +256,3 @@ if __name__ == '__main__': plot_lena_overlay() plt.show() - diff --git a/doc/make.bat b/doc/make.bat index e16c8d40..5f117648 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -27,6 +27,14 @@ if "%1" == "help" ( goto end ) +for %%x in (html htmlhelp latex qthelp) do ( + if "%1" == "%%x" ( + md source\api 2>NUL + python tools/build_modref_templates.py + ) +) + + if "%1" == "clean" ( for /d %%i in (build\*) do rmdir /q /s %%i del /q /s build\* @@ -34,6 +42,7 @@ if "%1" == "clean" ( ) if "%1" == "html" ( + cd source && python random_gallery.py && python coverage_generator.py && cd .. %SPHINXBUILD% -b html %ALLSPHINXOPTS% build/html echo. echo.Build finished. The HTML pages are in build/html. diff --git a/doc/release/release_0.8.txt b/doc/release/release_0.8.txt new file mode 100644 index 00000000..d146659c --- /dev/null +++ b/doc/release/release_0.8.txt @@ -0,0 +1,71 @@ +Announcement: scikits-image 0.8.0 +================================= + +We're happy to announce the 8th version of scikit-image! + +scikit-image is an image processing toolbox for SciPy that includes algorithms +for segmentation, geometric transformations, color space manipulation, +analysis, filtering, morphology, feature detection, and more. + +For more information, examples, and documentation, please visit our website: + + http://scikit-image.org + + +New Features +------------ + +- New rank filter package with many new functions and a very fast underlying + local histogram algorithm, especially for large structuring elements + `skimage.filter.rank.*` +- New function for small object removal + `skimage.morphology.remove_small_objects` +- New circular hough transformation `skimage.transform.hough_circle` +- New function to draw circle perimeter `skimage.draw.circle_perimeter` and + ellipse perimeter `skimage.draw.ellipse_perimeter` +- New dense DAISY feature descriptor `skimage.feature.daisy` +- New bilateral filter `skimage.filter.denoise_bilateral` +- New faster TV denoising filter based on split-Bregman algorithm + `skimage.filter.denoise_tv_bregman` +- New linear hough peak detection `skimage.transform.hough_peaks` +- New Scharr edge detection `skimage.filter.scharr` +- New geometric image scaling as convenience function + `skimage.transform.rescale` +- New theme for documentation and website +- Faster median filter through vectorization `skimage.filter.median_filter` +- Grayscale images supported for SLIC segmentation +- Unified peak detection with more options `skimage.feature.peak_local_max` +- `imread` can read images via URL and knows more formats `skimage.io.imread` + +Additionally, this release adds lots of bug fixes, new examples, and +performance enhancements. + + +Contributors to this release +---------------------------- + +This release was only possible due to the efforts of many contributors, both +new and old. + +- Adam Ginsburg +- Anders Boesen Lindbo Larsen +- Andreas Mueller +- Christoph Gohlke +- Christos Psaltis +- Colin Lea +- François Boulogne +- Jan Margeta +- Johannes Schönberger +- Josh Warner (Mac) +- Juan Nunez-Iglesias +- Luis Pedro Coelho +- Marianne Corvellec +- Matt McCormick +- Nicolas Pinto +- Olivier Debeir +- Paul Ivanov +- Sergey Karayev +- Stefan van der Walt +- Steven Silvester +- Thouis (Ray) Jones +- Tony S Yu diff --git a/doc/source/_static/docversions.js b/doc/source/_static/docversions.js index 0b414325..fde9437b 100644 --- a/doc/source/_static/docversions.js +++ b/doc/source/_static/docversions.js @@ -1,5 +1,5 @@ function insert_version_links() { - var labels = ['dev', '0.7.0', '0.6', '0.5', '0.4', '0.3']; + var labels = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3']; for (i = 0; i < labels.length; i++){ open_list = '
  • ' diff --git a/doc/source/conf.py b/doc/source/conf.py index 5071fab1..c2179b41 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -26,9 +26,26 @@ sys.path.append(os.path.join(curpath, '..', 'ext')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.pngmath', 'numpydoc', - 'sphinx.ext.autosummary', 'plot_directive', 'plot2rst', + 'sphinx.ext.autosummary', 'plot2rst', 'sphinx.ext.intersphinx'] +# Determine if the matplotlib has a recent enough version of the +# plot_directive, otherwise use the local fork. +try: + from matplotlib.sphinxext import plot_directive +except ImportError: + use_matplotlib_plot_directive = False +else: + try: + use_matplotlib_plot_directive = (plot_directive.__version__ >= 2) + except AttributeError: + use_matplotlib_plot_directive = False + +if use_matplotlib_plot_directive: + extensions.append('matplotlib.sphinxext.plot_directive') +else: + extensions.append('plot_directive') + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -42,8 +59,8 @@ source_suffix = '.txt' master_doc = 'index' # General information about the project. -project = u'skimage' -copyright = u'2011, the scikit-image team' +project = 'skimage' +copyright = '2013, the scikit-image team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -185,13 +202,13 @@ htmlhelp_basename = 'scikitimagedoc' #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('contents', 'scikitimage.tex', u'The Image Scikit Documentation', - u'SciPy Developers', 'manual'), + ('contents', 'scikit-image.tex', u'The scikit-image Documentation', + u'scikit-image development team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -203,13 +220,32 @@ latex_documents = [ #latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +latex_preamble = r''' +\usepackage{enumitem} +\setlistdepth{100} + +\usepackage{amsmath} +\DeclareUnicodeCharacter{00A0}{\nobreakspace} + +% In the parameters section, place a newline after the Parameters header +\usepackage{expdlist} +\let\latexdescription=\description +\def\description{\latexdescription{}{} \breaklabel} + +% Make Examples/etc section headers smaller and more compact +\makeatletter +\titleformat{\paragraph}{\normalsize\py@HeaderFamily}% + {\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} +\titlespacing*{\paragraph}{0pt}{1ex}{0pt} +\makeatother + +''' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +latex_use_modindex = False # ----------------------------------------------------------------------------- # Numpy extensions @@ -243,7 +279,7 @@ matplotlib.rcParams.update({ """ plot_include_source = True -plot_formats = [('png', 100)] +plot_formats = [('png', 100), ('pdf', 100)] plot2rst_index_name = 'README' plot2rst_rcparams = {'image.cmap' : 'gray', diff --git a/setup.py b/setup.py index 0040534e..d62a0d86 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za' URL = 'http://scikit-image.org' LICENSE = 'Modified BSD' DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image' -VERSION = '0.8dev' +VERSION = '0.9dev' PYTHON_VERSION = (2, 5) DEPENDENCIES = { 'numpy': (1, 6), diff --git a/skimage/_shared/geometry.pxd b/skimage/_shared/geometry.pxd index afdc6b5b..3379318c 100644 --- a/skimage/_shared/geometry.pxd +++ b/skimage/_shared/geometry.pxd @@ -1,6 +1,6 @@ -cdef unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, +cdef unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, double x, double y) -cdef void points_in_polygon(int nr_verts, double *xp, double *yp, - int nr_points, double *x, double *y, +cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, + Py_ssize_t nr_points, double *x, double *y, unsigned char *result) diff --git a/skimage/_shared/geometry.pyx b/skimage/_shared/geometry.pyx index 3f4850b0..beb07e14 100644 --- a/skimage/_shared/geometry.pyx +++ b/skimage/_shared/geometry.pyx @@ -4,8 +4,8 @@ #cython: wraparound=False -cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, - double x, double y): +cdef inline unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp, + double *yp, double x, double y): """Test whether point lies inside a polygon. Parameters @@ -17,9 +17,9 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, x, y : double Coordinates of point. """ - cdef int i + cdef Py_ssize_t i cdef unsigned char c = 0 - cdef int j = nr_verts - 1 + cdef Py_ssize_t j = nr_verts - 1 for i in range(nr_verts): if ( (((yp[i] <= y) and (y < yp[j])) or @@ -31,8 +31,8 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, return c -cdef void points_in_polygon(int nr_verts, double *xp, double *yp, - int nr_points, double *x, double *y, +cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, + Py_ssize_t nr_points, double *x, double *y, unsigned char *result): """Test whether points lie inside a polygon. @@ -49,6 +49,6 @@ cdef void points_in_polygon(int nr_verts, double *xp, double *yp, result : unsigned char array Test results for each point. """ - cdef int n + cdef Py_ssize_t n for n in range(nr_points): result[n] = point_in_polygon(nr_verts, xp, yp, x[n], y[n]) diff --git a/skimage/_shared/interpolation.pxd b/skimage/_shared/interpolation.pxd index 4e00dfb0..c5f32b6a 100644 --- a/skimage/_shared/interpolation.pxd +++ b/skimage/_shared/interpolation.pxd @@ -1,27 +1,27 @@ -cdef double nearest_neighbour_interpolation(double* image, int rows, - int cols, double r, +cdef double nearest_neighbour_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval) -cdef double bilinear_interpolation(double* image, int rows, int cols, +cdef double bilinear_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) cdef double quadratic_interpolation(double x, double[3] f) -cdef double biquadratic_interpolation(double* image, int rows, int cols, +cdef double biquadratic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) cdef double cubic_interpolation(double x, double[4] f) -cdef double bicubic_interpolation(double* image, int rows, int cols, +cdef double bicubic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) -cdef double get_pixel2d(double* image, int rows, int cols, int r, int c, - char mode, double cval) +cdef double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t r, + Py_ssize_t c, char mode, double cval) -cdef double get_pixel3d(double* image, int rows, int cols, int dims, int r, - int c, int d, char mode, double cval) +cdef double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims, + Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, char mode, double cval) -cdef int coord_map(int dim, int coord, char mode) +cdef Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode) diff --git a/skimage/_shared/interpolation.pyx b/skimage/_shared/interpolation.pyx index b34ba507..a8b96014 100644 --- a/skimage/_shared/interpolation.pyx +++ b/skimage/_shared/interpolation.pyx @@ -5,12 +5,12 @@ from libc.math cimport ceil, floor -cdef inline int round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline Py_ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -cdef inline double nearest_neighbour_interpolation(double* image, int rows, - int cols, double r, +cdef inline double nearest_neighbour_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval): """Nearest neighbour interpolation at a given position in the image. @@ -35,13 +35,12 @@ cdef inline double nearest_neighbour_interpolation(double* image, int rows, """ - return get_pixel2d(image, rows, cols, round(r), round(c), - mode, cval) + return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval) -cdef inline double bilinear_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double bilinear_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, + char mode, double cval): """Bilinear interpolation at a given position in the image. Parameters @@ -64,12 +63,12 @@ cdef inline double bilinear_interpolation(double* image, int rows, int cols, """ cdef double dr, dc - cdef int minr, minc, maxr, maxc + cdef Py_ssize_t minr, minc, maxr, maxc - minr = floor(r) - minc = floor(c) - maxr = ceil(r) - maxc = ceil(c) + minr = floor(r) + minc = floor(c) + maxr = ceil(r) + maxc = ceil(c) dr = r - minr dc = c - minc top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \ @@ -98,9 +97,9 @@ cdef inline double quadratic_interpolation(double x, double[3] f): return f[1] - 0.25 * (f[0] - f[2]) * x -cdef inline double biquadratic_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double biquadratic_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, + char mode, double cval): """Biquadratic interpolation at a given position in the image. Parameters @@ -123,8 +122,8 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols, """ - cdef int r0 = round(r) - cdef int c0 = round(c) + cdef Py_ssize_t r0 = round(r) + cdef Py_ssize_t c0 = round(c) if r < 0: r0 -= 1 if c < 0: @@ -139,7 +138,7 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols, cdef double fc[3], fr[3] - cdef int pr, pc + cdef Py_ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 3): @@ -174,9 +173,9 @@ cdef inline double cubic_interpolation(double x, double[4] f): (3.0 * (f[1] - f[2]) + f[3] - f[0]))) -cdef inline double bicubic_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double bicubic_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, + char mode, double cval): """Bicubic interpolation at a given position in the image. Parameters @@ -199,8 +198,8 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, """ - cdef int r0 = r - 1 - cdef int c0 = c - 1 + cdef Py_ssize_t r0 = r - 1 + cdef Py_ssize_t c0 = c - 1 if r < 0: r0 -= 1 if c < 0: @@ -211,7 +210,7 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, cdef double fc[4], fr[4] - cdef int pr, pc + cdef Py_ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 4): @@ -223,8 +222,8 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, return cubic_interpolation(xr, fr) -cdef inline double get_pixel2d(double* image, int rows, int cols, int r, int c, - char mode, double cval): +cdef inline double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -255,8 +254,9 @@ cdef inline double get_pixel2d(double* image, int rows, int cols, int r, int c, return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)] -cdef inline double get_pixel3d(double* image, int rows, int cols, int dims, int r, - int c, int d, char mode, double cval): +cdef inline double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t dims, Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, + char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -289,7 +289,7 @@ cdef inline double get_pixel3d(double* image, int rows, int cols, int dims, int + d] -cdef inline int coord_map(int dim, int coord, char mode): +cdef inline Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode): """ Wrap a coordinate, according to a given mode. @@ -308,20 +308,20 @@ cdef inline int coord_map(int dim, int coord, char mode): if mode == 'R': # reflect if coord < 0: # How many times times does the coordinate wrap? - if (-coord / dim) % 2 != 0: - return dim - (-coord % dim) + if (-coord / dim) % 2 != 0: + return dim - (-coord % dim) else: - return (-coord % dim) + return (-coord % dim) elif coord > dim: - if (coord / dim) % 2 != 0: - return (dim - (coord % dim)) + if (coord / dim) % 2 != 0: + return (dim - (coord % dim)) else: - return (coord % dim) + return (coord % dim) elif mode == 'W': # wrap if coord < 0: - return (dim - (-coord % dim)) + return (dim - (-coord % dim)) elif coord > dim: - return (coord % dim) + return (coord % dim) elif mode == 'N': # nearest if coord < 0: return 0 diff --git a/skimage/_shared/transform.pxd b/skimage/_shared/transform.pxd index 0edc22a4..ccb16ff3 100644 --- a/skimage/_shared/transform.pxd +++ b/skimage/_shared/transform.pxd @@ -2,4 +2,4 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - int r0, int c0, int r1, int c1) + Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1) diff --git a/skimage/_shared/transform.pyx b/skimage/_shared/transform.pyx index ba0efc71..8ce2ab67 100644 --- a/skimage/_shared/transform.pyx +++ b/skimage/_shared/transform.pyx @@ -6,7 +6,7 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - int r0, int c0, int r1, int c1): + Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1): """ Using a summed area table / integral image, calculate the sum over a given window. diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 4075ddb4..acdf9c16 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -25,9 +25,12 @@ class deprecated(object): def __call__(self, func): - msg = "Call to deprecated function `%s`." % func.__name__ + alt_msg = '' if self.alt_func is not None: - msg = msg + " Use `%s` instead." % self.alt_func + alt_msg = ' Use `%s` instead.' % self.alt_func + + msg = 'Call to deprecated function `%s`.' % func.__name__ + msg += alt_msg @functools.wraps(func) def wrapped(*args, **kwargs): @@ -40,4 +43,11 @@ class deprecated(object): raise DeprecationWarning(msg) return func(*args, **kwargs) + # modify doc string to display deprecation warning + doc = '**Deprecated function**.' + alt_msg + if wrapped.__doc__ is None: + wrapped.__doc__ = doc + else: + wrapped.__doc__ = doc + '\n\n' + wrapped.__doc__ + return wrapped diff --git a/skimage/_shared/vectorized_ops.h b/skimage/_shared/vectorized_ops.h new file mode 100644 index 00000000..ab9647ea --- /dev/null +++ b/skimage/_shared/vectorized_ops.h @@ -0,0 +1,110 @@ +/* Intrinsic declarations */ +#if defined(__SSE2__) +#include +#elif defined(__MMX__) +#include +#elif defined(__ALTIVEC__) +#include +#endif + +/* Compiler peculiarities */ +#if defined(__GNUC__) +#include +#elif defined(_MSC_VER) +#define inline __inline +typedef unsigned __int16 uint16_t; +#endif + +/** + * Add 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if + * available. + */ +#if defined(__SSE2__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + __m128i *d, *s; + d = (__m128i *) dest; + s = (__m128i *) src; + *d = _mm_add_epi16(*d, *s); + d++; s++; + *d = _mm_add_epi16(*d, *s); +} +#elif defined(__MMX__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + __m64 *d, *s; + d = (__m64 *) dest; + s = (__m64 *) src; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); +} +#elif defined(__ALTIVEC__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + vector unsigned short *d, *s; + d = (vector unsigned short *) dest; + s = (vector unsigned short *) src; + *d = vec_add(*d, *s); + d++; s++; + *d = vec_add(*d, *s); +} +#else +static inline void add16(uint16_t *dest, uint16_t *src) +{ + int i; + + for (i = 0; i < 16; i++) dest[i] += src[i]; +} +#endif + +/** + * Subtract 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if + * available. + */ +#if defined(__SSE2__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + __m128i *d, *s; + d = (__m128i *) dest; + s = (__m128i *) src; + *d = _mm_sub_epi16(*d, *s); + d++; s++; + *d = _mm_sub_epi16(*d, *s); +} +#elif defined(__MMX__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + __m64 *d, *s; + d = (__m64 *) dest; + s = (__m64 *) src; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); +} +#elif defined(__ALTIVEC__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + vector unsigned short *d, *s; + d = (vector unsigned short *) dest; + s = (vector unsigned short *) src; + *d = vec_sub(*d, *s); + d++; s++; + *d = vec_sub(*d, *s); +} +#else +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + int i; + + for (i = 0; i < 16; i++) dest[i] -= src[i]; +} +#endif diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 4186682d..fc9ab342 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -45,7 +45,14 @@ from __future__ import division __all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb', 'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb', - 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'is_rgb', 'is_gray' + 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'rgb2hed', 'hed2rgb', + 'separate_stains', 'combine_stains', 'rgb_from_hed', 'hed_from_rgb', + 'rgb_from_hdx', 'hdx_from_rgb', 'rgb_from_fgx', 'fgx_from_rgb', + 'rgb_from_bex', 'bex_from_rgb', 'rgb_from_rbd', 'rbd_from_rgb', + 'rgb_from_gdx', 'gdx_from_rgb', 'rgb_from_hax', 'hax_from_rgb', + 'rgb_from_bro', 'bro_from_rgb', 'rgb_from_bpx', 'bpx_from_rgb', + 'rgb_from_ahx', 'ahx_from_rgb', 'rgb_from_hpx', 'hpx_from_rgb', + 'is_rgb', 'is_gray' ] __docformat__ = "restructuredtext en" @@ -312,6 +319,90 @@ gray_from_rgb = np.array([[0.2125, 0.7154, 0.0721], # CIE LAB constants for Observer= 2A, Illuminant= D65 lab_ref_white = np.array([0.95047, 1., 1.08883]) + +# Haematoxylin-Eosin-DAB colorspace +# From original Ruifrok's paper: A. C. Ruifrok and D. A. Johnston, +# "Quantification of histochemical staining by color deconvolution.," +# Analytical and quantitative cytology and histology / the International +# Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, +# pp. 291-9, Aug. 2001. +rgb_from_hed = np.array([[0.65, 0.70, 0.29], + [0.07, 0.99, 0.11], + [0.27, 0.57, 0.78]]) +hed_from_rgb = linalg.inv(rgb_from_hed) + +# Following matrices are adapted form the Java code written by G.Landini. +# The original code is available at: +# http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + +# Hematoxylin + DAB +rgb_from_hdx = np.array([[0.650, 0.704, 0.286], + [0.268, 0.570, 0.776], + [0.0, 0.0, 0.0]]) +rgb_from_hdx[2, :] = np.cross(rgb_from_hdx[0, :], rgb_from_hdx[1, :]) +hdx_from_rgb = linalg.inv(rgb_from_hdx) + +# Feulgen + Light Green +rgb_from_fgx = np.array([[0.46420921, 0.83008335, 0.30827187], + [0.94705542, 0.25373821, 0.19650764], + [0.0, 0.0, 0.0]]) +rgb_from_fgx[2, :] = np.cross(rgb_from_fgx[0, :], rgb_from_fgx[1, :]) +fgx_from_rgb = linalg.inv(rgb_from_fgx) + +# Giemsa: Methyl Blue + Eosin +rgb_from_bex = np.array([[0.834750233, 0.513556283, 0.196330403], + [0.092789, 0.954111, 0.283111], + [0.0, 0.0, 0.0]]) +rgb_from_bex[2, :] = np.cross(rgb_from_bex[0, :], rgb_from_bex[1, :]) +bex_from_rgb = linalg.inv(rgb_from_bex) + +# FastRed + FastBlue + DAB +rgb_from_rbd = np.array([[0.21393921, 0.85112669, 0.47794022], + [0.74890292, 0.60624161, 0.26731082], + [0.268, 0.570, 0.776]]) +rbd_from_rgb = linalg.inv(rgb_from_rbd) + +# Methyl Green + DAB +rgb_from_gdx = np.array([[0.98003, 0.144316, 0.133146], + [0.268, 0.570, 0.776], + [0.0, 0.0, 0.0]]) +rgb_from_gdx[2, :] = np.cross(rgb_from_gdx[0, :], rgb_from_gdx[1, :]) +gdx_from_rgb = linalg.inv(rgb_from_gdx) + +# Hematoxylin + AEC +rgb_from_hax = np.array([[0.650, 0.704, 0.286], + [0.2743, 0.6796, 0.6803], + [0.0, 0.0, 0.0]]) +rgb_from_hax[2, :] = np.cross(rgb_from_hax[0, :], rgb_from_hax[1, :]) +hax_from_rgb = linalg.inv(rgb_from_hax) + +# Blue matrix Anilline Blue + Red matrix Azocarmine + Orange matrix Orange-G +rgb_from_bro = np.array([[0.853033, 0.508733, 0.112656], + [0.09289875, 0.8662008, 0.49098468], + [0.10732849, 0.36765403, 0.9237484]]) +bro_from_rgb = linalg.inv(rgb_from_bro) + +# Methyl Blue + Ponceau Fuchsin +rgb_from_bpx = np.array([[0.7995107, 0.5913521, 0.10528667], + [0.09997159, 0.73738605, 0.6680326], + [0.0, 0.0, 0.0]]) +rgb_from_bpx[2, :] = np.cross(rgb_from_bpx[0, :], rgb_from_bpx[1, :]) +bpx_from_rgb = linalg.inv(rgb_from_bpx) + +# Alcian Blue + Hematoxylin +rgb_from_ahx = np.array([[0.874622, 0.457711, 0.158256], + [0.552556, 0.7544, 0.353744], + [0.0, 0.0, 0.0]]) +rgb_from_ahx[2, :] = np.cross(rgb_from_ahx[0, :], rgb_from_ahx[1, :]) +ahx_from_rgb = linalg.inv(rgb_from_ahx) + +# Hematoxylin + PAS +rgb_from_hpx = np.array([[0.644211, 0.716556, 0.266844], + [0.175411, 0.972178, 0.154589], + [0.0, 0.0, 0.0]]) +rgb_from_hpx[2, :] = np.cross(rgb_from_hpx[0, :], rgb_from_hpx[1, :]) +hpx_from_rgb = linalg.inv(rgb_from_hpx) + #------------------------------------------------------------- # The conversion functions that make use of the matrices above #------------------------------------------------------------- @@ -721,3 +812,189 @@ def lab2rgb(lab): This function uses lab2xyz and xyz2rgb. """ return xyz2rgb(lab2xyz(lab)) + + +def rgb2hed(rgb): + """RGB to Haematoxylin-Eosin-DAB (HED) color space conversion. + + Parameters + ---------- + rgb : array_like + The image in RGB format, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in HED format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + + References + ---------- + .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2hed + >>> ihc = data.immunohistochemistry() + >>> ihc_hed = rgb2hed(ihc) + """ + return separate_stains(rgb, hed_from_rgb) + + +def hed2rgb(hed): + """Haematoxylin-Eosin-DAB (HED) to RGB color space conversion. + + Parameters + ---------- + hed : array_like + The image in the HED color space, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in RGB, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `hed` is not a 3-D array of shape (.., .., 3). + + References + ---------- + .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2hed, hed2rgb + >>> ihc = data.immunohistochemistry() + >>> ihc_hed = rgb2hed(ihc) + >>> ihc_rgb = hed2rgb(ihc_hed) + """ + return combine_stains(hed, rgb_from_hed) + + +def separate_stains(rgb, conv_matrix): + """RGB to stain color space conversion. + + Parameters + ---------- + rgb : array_like + The image in RGB format, in a 3-D array of shape (.., .., 3). + conv_matrix: ndarray + The stain separation matrix as described by G. Landini [1]_. + + Returns + ------- + out : ndarray + The image in stain color space, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + Notes + ----- + Stain separation matrices available in the ``color`` module and their + respective colorspace: + + * ``hed_from_rgb``: Hematoxylin + Eosin + DAB + * ``hdx_from_rgb``: Hematoxylin + DAB + * ``fgx_from_rgb``: Feulgen + Light Green + * ``bex_from_rgb``: Giemsa stain : Methyl Blue + Eosin + * ``rbd_from_rgb``: FastRed + FastBlue + DAB + * ``gdx_from_rgb``: Methyl Green + DAB + * ``hax_from_rgb``: Hematoxylin + AEC + * ``bro_from_rgb``: Blue matrix Anilline Blue + Red matrix Azocarmine\ + + Orange matrix Orange-G + * ``bpx_from_rgb``: Methyl Blue + Ponceau Fuchsin + * ``ahx_from_rgb``: Alcian Blue + Hematoxylin + * ``hpx_from_rgb``: Hematoxylin + PAS + + References + ---------- + .. [1] http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import separate_stains, hdx_from_rgb + >>> ihc = data.immunohistochemistry() + >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) + """ + rgb = dtype.img_as_float(rgb) + 2 + stains = np.dot(np.reshape(-np.log(rgb), (-1, 3)), conv_matrix) + return np.reshape(stains, rgb.shape) + + +def combine_stains(stains, conv_matrix): + """Stain to RGB color space conversion. + + Parameters + ---------- + stains : array_like + The image in stain color space, in a 3-D array of shape (.., .., 3). + conv_matrix: ndarray + The stain separation matrix as described by G. Landini [1]_. + + Returns + ------- + out : ndarray + The image in RGB format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `stains` is not a 3-D array of shape (.., .., 3). + + Notes + ----- + Stain combination matrices available in the ``color`` module and their + respective colorspace: + + * ``rgb_from_hed``: Hematoxylin + Eosin + DAB + * ``rgb_from_hdx``: Hematoxylin + DAB + * ``rgb_from_fgx``: Feulgen + Light Green + * ``rgb_from_bex``: Giemsa stain : Methyl Blue + Eosin + * ``rgb_from_rbd``: FastRed + FastBlue + DAB + * ``rgb_from_gdx``: Methyl Green + DAB + * ``rgb_from_hax``: Hematoxylin + AEC + * ``rgb_from_bro``: Blue matrix Anilline Blue + Red matrix Azocarmine\ + + Orange matrix Orange-G + * ``rgb_from_bpx``: Methyl Blue + Ponceau Fuchsin + * ``rgb_from_ahx``: Alcian Blue + Hematoxylin + * ``rgb_from_hpx``: Hematoxylin + PAS + + References + ---------- + .. [1] http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import (separate_stains, combine_stains, + ... hdx_from_rgb, rgb_from_hdx) + >>> ihc = data.immunohistochemistry() + >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) + >>> ihc_rgb = combine_stains(ihc_hdx, rgb_from_hdx) + """ + from ..exposure import rescale_intensity + + stains = dtype.img_as_float(stains) + logrgb2 = np.dot(-np.reshape(stains, (-1, 3)), conv_matrix) + rgb2 = np.exp(logrgb2) + return rescale_intensity(np.reshape(rgb2 - 2, stains.shape), in_range=(-1, 1)) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 962fa9fc..3365bb1c 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -16,11 +16,14 @@ import os.path import numpy as np from numpy.testing import * -from skimage import img_as_float +from skimage import img_as_float, img_as_ubyte from skimage.io import imread from skimage.color import ( rgb2hsv, hsv2rgb, rgb2xyz, xyz2rgb, + rgb2hed, hed2rgb, + separate_stains, + combine_stains, rgb2rgbcie, rgbcie2rgb, convert_colorspace, rgb2grey, gray2rgb, @@ -121,6 +124,32 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(xyz2rgb(rgb2xyz(img_rgb)), img_rgb) + # RGB<->HED roundtrip with ubyte image + def test_hed_rgb_roundtrip(self): + img_rgb = self.img_rgb + assert_equal(img_as_ubyte(hed2rgb(rgb2hed(img_rgb))), img_rgb) + + # RGB<->HED roundtrip with float image + def test_hed_rgb_float_roundtrip(self): + img_rgb = img_as_float(self.img_rgb) + assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb) + + # RGB<->HDX roundtrip with ubyte image + def test_hdx_rgb_roundtrip(self): + from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + img_rgb = self.img_rgb + conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb), + rgb_from_hdx) + assert_equal(img_as_ubyte(conv), img_rgb) + + # RGB<->HDX roundtrip with ubyte image + def test_hdx_rgb_roundtrip(self): + from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + img_rgb = img_as_float(self.img_rgb) + conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb), + rgb_from_hdx) + assert_array_almost_equal(conv, img_rgb) + # RGB to RGB CIE def test_rgb2rgbcie_conversion(self): gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574], diff --git a/skimage/data/__init__.py b/skimage/data/__init__.py index d2fba7db..a544179b 100644 --- a/skimage/data/__init__.py +++ b/skimage/data/__init__.py @@ -127,3 +127,19 @@ def clock(): """ return load("clock_motion.png") + + +def immunohistochemistry(): + """Immunohistochemical (IHC) staining with hematoxylin counterstaining. + + This picture shows colonic glands where the IHC expression of FHL2 protein + is revealed with DAB. Hematoxylin counterstaining is applied to enhance the + negative parts of the tissue. + + This image was acquired at the Center for Microscopy And Molecular Imaging + (CMMI). + + No known copyright restrictions. + + """ + return load("ihc.jpg") diff --git a/skimage/data/ihc.jpg b/skimage/data/ihc.jpg new file mode 100644 index 00000000..e64d2a0d Binary files /dev/null and b/skimage/data/ihc.jpg differ diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index e0c9231e..4316cadc 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -2,15 +2,15 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False -import numpy as np import math +import numpy as np + +cimport numpy as cnp from libc.math cimport sqrt -cimport numpy as np -cimport cython from skimage._shared.geometry cimport point_in_polygon -def line(int y, int x, int y2, int x2): +def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): """Generate line pixel coordinates. Parameters @@ -29,12 +29,12 @@ def line(int y, int x, int y2, int x2): """ - cdef np.ndarray[np.int32_t, ndim=1, mode="c"] rr, cc + cdef cnp.ndarray[cnp.intp_t, ndim=1, mode="c"] rr, cc - cdef int steep = 0 - cdef int dx = abs(x2 - x) - cdef int dy = abs(y2 - y) - cdef int sx, sy, d, i + cdef char steep = 0 + cdef Py_ssize_t dx = abs(x2 - x) + cdef Py_ssize_t dy = abs(y2 - y) + cdef Py_ssize_t sx, sy, d, i if (x2 - x) > 0: sx = 1 @@ -51,8 +51,8 @@ def line(int y, int x, int y2, int x2): sx, sy = sy, sx d = (2 * dy) - dx - rr = np.zeros(int(dx) + 1, dtype=np.int32) - cc = np.zeros(int(dx) + 1, dtype=np.int32) + rr = np.zeros(int(dx) + 1, dtype=np.intp) + cc = np.zeros(int(dx) + 1, dtype=np.intp) for i in range(dx): if steep: @@ -96,27 +96,27 @@ def polygon(y, x, shape=None): """ - cdef int nr_verts = x.shape[0] - cdef int minr = max(0, y.min()) - cdef int maxr = math.ceil(y.max()) - cdef int minc = max(0, x.min()) - cdef int maxc = math.ceil(x.max()) + cdef Py_ssize_t nr_verts = x.shape[0] + cdef Py_ssize_t minr = int(max(0, y.min())) + cdef Py_ssize_t maxr = int(math.ceil(y.max())) + cdef Py_ssize_t minc = int(max(0, x.min())) + cdef Py_ssize_t maxc = int(math.ceil(x.max())) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef int r, c + cdef Py_ssize_t r, c - #: make contigous arrays for r, c coordinates - cdef np.ndarray contiguous_rdata, contiguous_cdata + # make contigous arrays for r, c coordinates + cdef cnp.ndarray contiguous_rdata, contiguous_cdata contiguous_rdata = np.ascontiguousarray(y, 'double') contiguous_cdata = np.ascontiguousarray(x, 'double') - cdef np.double_t* rptr = contiguous_rdata.data - cdef np.double_t* cptr = contiguous_cdata.data + cdef cnp.double_t* rptr = contiguous_rdata.data + cdef cnp.double_t* cptr = contiguous_cdata.data - #: output coordinate arrays + # output coordinate arrays cdef list rr = list() cdef list cc = list() @@ -126,7 +126,7 @@ def polygon(y, x, shape=None): rr.append(r) cc.append(c) - return np.array(rr), np.array(cc) + return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) def ellipse(double cy, double cx, double yradius, double xradius, shape=None): @@ -138,6 +138,10 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): Centre coordinate of ellipse. yradius, xradius : double Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``. + shape : tuple, optional + image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for ellipses which exceed the image size. + By default the full extents of the ellipse are used. Returns ------- @@ -148,19 +152,19 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): """ - cdef int minr = max(0, cy - yradius) - cdef int maxr = math.ceil(cy + yradius) - cdef int minc = max(0, cx - xradius) - cdef int maxc = math.ceil(cx + xradius) + cdef Py_ssize_t minr = int(max(0, cy - yradius)) + cdef Py_ssize_t maxr = int(math.ceil(cy + yradius)) + cdef Py_ssize_t minc = int(max(0, cx - xradius)) + cdef Py_ssize_t maxc = int(math.ceil(cx + xradius)) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef int r, c + cdef Py_ssize_t r, c - #: output coordinate arrays + # output coordinate arrays cdef list rr = list() cdef list cc = list() @@ -170,7 +174,7 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): rr.append(r) cc.append(c) - return np.array(rr), np.array(cc) + return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) def circle(double cy, double cx, double radius, shape=None): @@ -182,6 +186,10 @@ def circle(double cy, double cx, double radius, shape=None): Centre coordinate of circle. radius: double Radius of circle. + shape : tuple, optional + image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for circles which exceed the image size. + By default the full extents of the circle are used. Returns ------- @@ -189,13 +197,16 @@ def circle(double cy, double cx, double radius, shape=None): Pixel coordinates of circle. May be used to directly index into an array, e.g. ``img[rr, cc] = 1``. - + Notes + ----- + This function is a wrapper for skimage.draw.ellipse() """ return ellipse(cy, cx, radius, radius, shape) -def circle_perimeter(int cy, int cx, int radius, method='bresenham'): +def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, + method='bresenham'): """Generate circle perimeter coordinates. Parameters @@ -234,9 +245,9 @@ def circle_perimeter(int cy, int cx, int radius, method='bresenham'): cdef list rr = list() cdef list cc = list() - cdef int x = 0 - cdef int y = radius - cdef int d = 0 + cdef Py_ssize_t x = 0 + cdef Py_ssize_t y = radius + cdef Py_ssize_t d = 0 cdef char cmethod if method == 'bresenham': d = 3 - 2 * radius @@ -270,10 +281,11 @@ def circle_perimeter(int cy, int cx, int radius, method='bresenham'): y = y - 1 x = x + 1 - return np.array(rr) + cy, np.array(cc) + cx + return np.array(rr, dtype=np.intp) + cy, np.array(cc, dtype=np.intp) + cx -def ellipse_perimeter(int cy, int cx, int yradius, int xradius): +def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, + Py_ssize_t xradius): """Generate ellipse perimeter coordinates. Parameters @@ -302,8 +314,8 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): return np.array(cy), np.array(cx) # a and b are xradius an yradius compute 2a^2 and 2b^2 - cdef int twoasquared = 2 * xradius**2 - cdef int twobsquared = 2 * yradius**2 + cdef Py_ssize_t twoasquared = 2 * xradius**2 + cdef Py_ssize_t twobsquared = 2 * yradius**2 # Pixels cdef list px = list() @@ -311,14 +323,14 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): # First set of points: # start at the top - cdef int x = xradius - cdef int y = 0 + cdef Py_ssize_t x = xradius + cdef Py_ssize_t y = 0 - cdef int err = 0 - cdef int xstop = twobsquared * xradius - cdef int ystop = 0 - cdef int xchange = yradius * yradius * (1 - 2 * xradius) - cdef int ychange = xradius * xradius + cdef Py_ssize_t err = 0 + cdef Py_ssize_t xstop = twobsquared * xradius + cdef Py_ssize_t ystop = 0 + cdef Py_ssize_t xchange = yradius * yradius * (1 - 2 * xradius) + cdef Py_ssize_t ychange = xradius * xradius while xstop > ystop: px.extend([x, -x, -x, x]) @@ -356,7 +368,7 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): err += ychange ychange += twobsquared - return np.array(py) + cy, np.array(px) + cx + return np.array(py, dtype=np.intp) + cy, np.array(px, dtype=np.intp) + cx def set_color(img, coords, color): diff --git a/skimage/exposure/_adapthist.py b/skimage/exposure/_adapthist.py index 346d8424..8a825435 100644 --- a/skimage/exposure/_adapthist.py +++ b/skimage/exposure/_adapthist.py @@ -259,6 +259,8 @@ def map_histogram(hist, min_val, max_val, n_pixels): It does so by cumulating the input histogram. + Parameters + ---------- hist : ndarray Clipped histogram. min_val : int @@ -301,12 +303,11 @@ def interpolate(image, xslice, yslice, out : ndarray Original image with the subregion replaced. - Note - ---- - This function calculates the new greylevel assignments of pixels - within a submatrix of the image. - This is done by a bilinear interpolation between four different - mappings in order to eliminate boundary artifacts. + Notes + ----- + This function calculates the new greylevel assignments of pixels within + a submatrix of the image. This is done by a bilinear interpolation between + four different mappings in order to eliminate boundary artifacts. """ norm = xslice.size * yslice.size # Normalization factor # interpolation weight matrices diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 465b9182..c7f1e712 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -81,7 +81,7 @@ def cumulative_distribution(image, nbins=256): @deprecated('equalize_hist') def equalize(image, nbins=256): - equalize_hist(image, nbins) + return equalize_hist(image, nbins) def equalize_hist(image, nbins=256): diff --git a/skimage/feature/_daisy.py b/skimage/feature/_daisy.py index f83605b5..1a97de8f 100644 --- a/skimage/feature/_daisy.py +++ b/skimage/feature/_daisy.py @@ -53,30 +53,35 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, the spatial smoothing of the center histogram and the last sigma value defines the spatial smoothing of the outermost ring. Specifying sigmas overrides the following parameter. - ``rings = len(sigmas)-1`` + + ``rings = len(sigmas) - 1`` ring_radii : 1D array of int, optional Radius (in pixels) for each ring. Specifying ring_radii overrides the following two parameters. - | ``rings = len(ring_radii)`` - | ``radius = ring_radii[-1]`` + + ``rings = len(ring_radii)`` + ``radius = ring_radii[-1]`` If both sigmas and ring_radii are given, they must satisfy the following predicate since no radius is needed for the center histogram. - ``len(ring_radii) == len(sigmas)+1`` + + ``len(ring_radii) == len(sigmas) + 1`` + visualize : bool, optional Generate a visualization of the DAISY descriptors - Returns ------- descs : array Grid of DAISY descriptors for the given image as an array dimensionality (P, Q, R) where - | ``P = ceil((M-radius*2)/step)`` - | ``Q = ceil((N-radius*2)/step)`` - | ``R = (rings*histograms + 1)*orientations`` + + ``P = ceil((M - radius*2) / step)`` + ``Q = ceil((N - radius*2) / step)`` + ``R = (rings * histograms + 1) * orientations`` + descs_img : (M, N, 3) array (only if visualize==True) Visualization of the DAISY descriptors. @@ -180,7 +185,7 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, color = (1, 0, 0) desc_y = i * step + radius desc_x = j * step + radius - coords = draw.circle_perimeter(desc_y, desc_x, sigmas[0]) + coords = draw.circle_perimeter(desc_y, desc_x, int(sigmas[0])) draw.set_color(descs_img, coords, color) max_bin = np.max(descs[i, j, :]) for o_num, o in enumerate(orientation_angles): @@ -188,8 +193,8 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, bin_size = descs[i, j, o_num] / max_bin dy = sigmas[0] * bin_size * sin(o) dx = sigmas[0] * bin_size * cos(o) - coords = draw.line(desc_y, desc_x, desc_y + dy, - desc_x + dx) + coords = draw.line(desc_y, desc_x, int(desc_y + dy), + int(desc_x + dx)) draw.set_color(descs_img, coords, color) for r_num, r in enumerate(ring_radii): color_offset = float(1 + r_num) / rings @@ -199,7 +204,7 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, hist_y = desc_y + int(round(r * sin(t))) hist_x = desc_x + int(round(r * cos(t))) coords = draw.circle_perimeter(hist_y, hist_x, - sigmas[r_num + 1]) + int(sigmas[r_num + 1])) draw.set_color(descs_img, coords, color) for o_num, o in enumerate(orientation_angles): # Draw histogram bins @@ -209,8 +214,9 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, bin_size /= max_bin dy = sigmas[r_num + 1] * bin_size * sin(o) dx = sigmas[r_num + 1] * bin_size * cos(o) - coords = draw.line(hist_y, hist_x, hist_y + dy, - hist_x + dx) + coords = draw.line(hist_y, hist_x, + int(hist_y + dy), + int(hist_x + dx)) draw.set_color(descs_img, coords, color) return descs, descs_img else: diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py index 8e8ea7f2..9fa018b7 100644 --- a/skimage/feature/_hog.py +++ b/skimage/feature/_hog.py @@ -142,8 +142,10 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), centre = tuple([y * cy + cy // 2, x * cx + cx // 2]) dx = radius * cos(float(o) / orientations * np.pi) dy = radius * sin(float(o) / orientations * np.pi) - rr, cc = draw.bresenham(centre[0] - dx, centre[1] - dy, - centre[0] + dx, centre[1] + dy) + rr, cc = draw.bresenham(int(centre[0] - dx), + int(centre[1] - dy), + int(centre[0] + dx), + int(centre[1] + dy)) hog_image[rr, cc] += orientation_histogram[y, x, o] """ diff --git a/skimage/feature/_template.pyx b/skimage/feature/_template.pyx index 58d48524..03695959 100644 --- a/skimage/feature/_template.pyx +++ b/skimage/feature/_template.pyx @@ -1,3 +1,8 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + """ Template matching using normalized cross-correlation. @@ -30,24 +35,31 @@ the image window *before* squaring.) .. [2] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and Magic. """ -import cython -cimport numpy as np + import numpy as np from scipy.signal import fftconvolve -from skimage.transform import integral + +cimport numpy as cnp from libc.math cimport sqrt, fabs from skimage._shared.transform cimport integrate -@cython.boundscheck(False) -def match_template(np.ndarray[float, ndim=2, mode="c"] image, - np.ndarray[float, ndim=2, mode="c"] template): - cdef np.ndarray[float, ndim=2, mode="c"] corr - cdef np.ndarray[float, ndim=2, mode="c"] image_sat - cdef np.ndarray[float, ndim=2, mode="c"] image_sqr_sat +from skimage.transform import integral + + +def match_template(cnp.ndarray[float, ndim=2, mode="c"] image, + cnp.ndarray[float, ndim=2, mode="c"] template): + + cdef cnp.ndarray[float, ndim=2, mode="c"] corr + cdef cnp.ndarray[float, ndim=2, mode="c"] image_sat + cdef cnp.ndarray[float, ndim=2, mode="c"] image_sqr_sat cdef float template_mean = np.mean(template) cdef float template_ssd cdef float inv_area + cdef Py_ssize_t r, c, r_end, c_end + cdef Py_ssize_t template_rows = template.shape[0] + cdef Py_ssize_t template_cols = template.shape[1] + cdef float den, window_sqr_sum, window_mean_sqr, window_sum image_sat = integral.integral_image(image) image_sqr_sat = integral.integral_image(image**2) @@ -63,24 +75,23 @@ def match_template(np.ndarray[float, ndim=2, mode="c"] image, mode="valid"), dtype=np.float32) - cdef int i, j - cdef float den, window_sqr_sum, window_mean_sqr, window_sum, - # move window through convolution results, normalizing in the process - for i in range(corr.shape[0]): - for j in range(corr.shape[1]): - # subtract 1 because `i_end` and `j_end` are used for indexing into - # summed-area table, instead of slicing windows of the image. - i_end = i + template.shape[0] - 1 - j_end = j + template.shape[1] - 1 - window_sum = integrate(image_sat, i, j, i_end, j_end) + # move window through convolution results, normalizing in the process + for r in range(corr.shape[0]): + for c in range(corr.shape[1]): + # subtract 1 because `i_end` and `c_end` are used for indexing into + # summed-area table, instead of slicing windows of the image. + r_end = r + template_rows - 1 + c_end = c + template_cols - 1 + + window_sum = integrate(image_sat, r, c, r_end, c_end) window_mean_sqr = window_sum * window_sum * inv_area - window_sqr_sum = integrate(image_sqr_sat, i, j, i_end, j_end) + window_sqr_sum = integrate(image_sqr_sat, r, c, r_end, c_end) if window_sqr_sum <= window_mean_sqr: - corr[i, j] = 0 + corr[r, c] = 0 continue den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd) - corr[i, j] /= den - return corr + corr[r, c] /= den + return corr diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 70a446bb..f98ed4ca 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -3,21 +3,20 @@ #cython: nonecheck=False #cython: wraparound=False import numpy as np -cimport numpy as np +cimport numpy as cnp 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, +def _glcm_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] image, + cnp.ndarray[dtype=cnp.float64_t, ndim=1, + negative_indices=False, mode='c'] distances, + cnp.ndarray[dtype=cnp.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 - ): + cnp.ndarray[dtype=cnp.uint32_t, ndim=4, + negative_indices=False, mode='c'] out): """Perform co-occurrence matrix accumulation. Parameters @@ -37,23 +36,26 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, 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 + Py_ssize_t a_idx, d_idx, r, c, rows, cols, row, col + cnp.uint8_t i, j + cnp.float64_t angle, distance rows = image.shape[0] cols = image.shape[1] - for a_idx, angle in enumerate(angles): - for d_idx, distance in enumerate(distances): + for a_idx in range(len(angles)): + angle = angles[a_idx] + for d_idx in range(len(distances)): + distance = distances[d_idx] 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); + col = c + (cos(angle) * distance + 0.5) # make sure the offset is within bounds if row >= 0 and row < rows and \ @@ -79,7 +81,7 @@ cdef inline int _bit_rotate_right(int value, int length): return (value >> 1) | ((value & 1) << (length - 1)) -def _local_binary_pattern(np.ndarray[double, ndim=2] image, +def _local_binary_pattern(cnp.ndarray[double, ndim=2] image, int P, float R, char method='D'): """Gray scale and rotation invariant LBP (Local Binary Patterns). @@ -109,25 +111,25 @@ def _local_binary_pattern(np.ndarray[double, ndim=2] image, """ # texture weights - cdef np.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32) + cdef cnp.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) + cdef cnp.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) + cdef cnp.ndarray[double, ndim=1] texture = np.zeros(P, np.double) + cdef cnp.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8) + cdef cnp.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 cnp.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] cdef double lbp - cdef int r, c, changes, i + cdef Py_ssize_t r, c, changes, i for r in range(image.shape[0]): for c in range(image.shape[1]): for i in range(P): diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 867acb5d..15aac545 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -136,11 +136,11 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1): References ---------- - ..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_harris, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 @@ -206,11 +206,11 @@ def corner_shi_tomasi(image, sigma=1): References ---------- - ..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_shi_tomasi, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 @@ -272,12 +272,12 @@ def corner_foerstner(image, sigma=1): References ---------- - ..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ - foerstner87.fast.pdf - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ + foerstner87.fast.pdf + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_foerstner, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 @@ -338,9 +338,9 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): References ---------- - ..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ - foerstner87.fast.pdf - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ + foerstner87.fast.pdf + .. [2] http://en.wikipedia.org/wiki/Corner_detection """ @@ -489,7 +489,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, threshold_abs=threshold_abs, threshold_rel=threshold_rel, exclude_border=exclude_border, - indices=False, num_peaks=np.inf, + indices=False, num_peaks=num_peaks, footprint=footprint, labels=labels) if min_distance > 0: coords = np.transpose(peaks.nonzero()) diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 2f001ea4..71c748f8 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -10,7 +10,7 @@ from skimage.color import rgb2grey from skimage.util import img_as_float -def corner_moravec(image, int window_size=1): +def corner_moravec(image, Py_ssize_t window_size=1): """Compute Moravec corner measure response image. This is one of the simplest corner detectors and is comparatively fast but @@ -34,7 +34,7 @@ def corner_moravec(image, int window_size=1): ..[2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import moravec, peak_local_max >>> square = np.zeros([7, 7]) >>> square[3, 3] = 1 @@ -56,8 +56,8 @@ def corner_moravec(image, int window_size=1): [ 0., 0., 0., 0., 0., 0., 0.]]) """ - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode='c'] cimage, out @@ -71,7 +71,7 @@ def corner_moravec(image, int window_size=1): cdef double* out_data = out.data cdef double msum, min_msum - cdef int r, c, br, bc, mr, mc, a, b + cdef Py_ssize_t r, c, br, bc, mr, mc, a, b for r in range(2 * window_size, rows - 2 * window_size): for c in range(2 * window_size, cols - 2 * window_size): min_msum = DBL_MAX diff --git a/skimage/feature/peak.py b/skimage/feature/peak.py index 4b0f6b4f..9c7a934f 100644 --- a/skimage/feature/peak.py +++ b/skimage/feature/peak.py @@ -50,9 +50,10 @@ def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, Returns ------- output : (N, 2) array or ndarray of bools - If `indices = True` : (row, column) coordinates of peaks. - If `indices = False` : Boolean array shaped like `image`, - with peaks represented by True values. + + * If `indices = True` : (row, column) coordinates of peaks. + * If `indices = False` : Boolean array shaped like `image`, with peaks + represented by True values. Notes ----- diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index a7ee01ff..fa8ddd17 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -100,6 +100,19 @@ def test_subpix(): assert_array_equal(subpix[0], (24.5, 24.5)) +def test_num_peaks(): + """For a bunch of different values of num_peaks, check that + peak_local_max returns exactly the right amount of peaks. Test + is run on Lena in order to produce a sufficient number of corners""" + + lena_corners = corner_harris(data.lena()) + + for i in range(20): + n = np.random.random_integers(20) + results = peak_local_max(lena_corners, num_peaks=n) + assert (results.shape[0] == n) + + def test_corner_peaks(): response = np.zeros((5, 5)) response[2:4, 2:4] = 1 diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py index 6f2d4cdf..cfef2da0 100644 --- a/skimage/feature/tests/test_hog.py +++ b/skimage/feature/tests/test_hog.py @@ -102,7 +102,7 @@ def test_hog_orientations_circle(): width = height = 100 image = np.zeros((height, width)) - rr, cc = draw.circle(height/2, width/2, width/3) + rr, cc = draw.circle(int(height / 2), int(width / 2), int(width / 3)) image[rr, cc] = 100 image = ndimage.gaussian_filter(image, 2) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index c133d8d6..e3e845e4 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -10,14 +10,20 @@ Copyright (c) 2009-2011 Broad Institute All rights reserved. Original author: Lee Kamentsky ''' + import numpy as np -cimport numpy as np + +cimport numpy as cnp cimport cython from libc.stdlib cimport malloc, free from libc.string cimport memset -np.import_array() + +cdef extern from "../_shared/vectorized_ops.h": + void add16(cnp.uint16_t *dest, cnp.uint16_t *src) + void sub16(cnp.uint16_t *dest, cnp.uint16_t *src) + ############################################################################## # @@ -39,7 +45,7 @@ np.import_array() DTYPE_UINT32 = np.uint32 DTYPE_BOOL = np.bool -ctypedef np.uint16_t pixel_count_t +ctypedef cnp.uint16_t pixel_count_t ########### # @@ -54,15 +60,15 @@ ctypedef np.uint16_t pixel_count_t ########### cdef struct HistogramPiece: - np.uint16_t coarse[16] - np.uint16_t fine[256] + cnp.uint16_t coarse[16] + cnp.uint16_t fine[256] cdef struct Histogram: - HistogramPiece top_left # top-left corner - HistogramPiece top_right # top-right corner - HistogramPiece edge # leading/trailing edge - HistogramPiece bottom_left # bottom-left corner - HistogramPiece bottom_right # bottom-right corner + HistogramPiece top_left # top-left corner + HistogramPiece top_right # top-right corner + HistogramPiece edge # leading/trailing edge + HistogramPiece bottom_left # bottom-left corner + HistogramPiece bottom_right # bottom-right corner # The pixel count has the number of pixels histogrammed in # each of the five compartments for this position. This changes @@ -80,27 +86,28 @@ cdef struct PixelCount: # relative offsets from the octagon center # cdef struct SCoord: - np.int32_t stride # add the stride to the memory location - np.int32_t x - np.int32_t y + Py_ssize_t stride # add the stride to the memory location + Py_ssize_t x + Py_ssize_t y cdef struct Histograms: - void *memory # pointer to the allocated memory - Histogram *histogram # pointer to the histogram memory + HistogramPiece accumulator # running histogram (32-byte aligned) + void *memory # pointer to the unaligned allocated memory + Histogram *histogram # pointer to the histogram memory (aligned) PixelCount *pixel_count # pointer to the pixel count memory - np.uint8_t *data # pointer to the image data - np.uint8_t *mask # pointer to the image mask - np.uint8_t *output # pointer to the output array - np.int32_t column_count # number of columns represented by this + cnp.uint8_t *data # pointer to the image data + cnp.uint8_t *mask # pointer to the image mask + cnp.uint8_t *output # pointer to the output array + Py_ssize_t column_count # number of columns represented by this # structure - np.int32_t stripe_length # number of columns including "radius" before + Py_ssize_t stripe_length # number of columns including "radius" before # and after - np.int32_t row_count # number of rows available in image - np.int32_t current_column # the column being processed - np.int32_t current_row # the row being processed - np.int32_t current_stride # offset in data and mask to current location - np.int32_t radius # the "radius" of the octagon - np.int32_t a_2 # 1/2 of the length of a side of the octagon + Py_ssize_t row_count # number of rows available in image + Py_ssize_t current_column # the column being processed + Py_ssize_t current_row # the row being processed + Py_ssize_t current_stride # offset in data and mask to current location + Py_ssize_t radius # the "radius" of the octagon + Py_ssize_t a_2 # 1/2 of the length of a side of the octagon # # # The strides are the offsets in the array to the points that need to @@ -123,83 +130,80 @@ cdef struct Histograms: # # x --> # - SCoord last_top_left # (-) left side of octagon's top - 1 row - SCoord top_left # (+) -1 row from trailing edge top - SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row - SCoord top_right # (+) -1 col -1 row from leading edge top - SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row - SCoord leading_edge # (+) leading edge bottom stride - SCoord last_bottom_right # (-) leading edge bottom - 1 col - SCoord bottom_right # (+) right side of octagon's bottom - 1 col - SCoord last_bottom_left # (-) trailing edge bottom - 1 col - SCoord bottom_left # (+) left side of octagon's bottom - 1 col + SCoord last_top_left # (-) left side of octagon's top - 1 row + SCoord top_left # (+) -1 row from trailing edge top + SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row + SCoord top_right # (+) -1 col -1 row from leading edge top + SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row + SCoord leading_edge # (+) leading edge bottom stride + SCoord last_bottom_right # (-) leading edge bottom - 1 col + SCoord bottom_right # (+) right side of octagon's bottom - 1 col + SCoord last_bottom_left # (-) trailing edge bottom - 1 col + SCoord bottom_left # (+) left side of octagon's bottom - 1 col - np.int32_t row_stride # stride between one row and the next - np.int32_t col_stride # stride between one column and the next - # The accumulator holds the running histogram - # - HistogramPiece accumulator + Py_ssize_t row_stride # stride between one row and the next + Py_ssize_t col_stride # stride between one column and the next # # The running count of pixels in the accumulator # - np.uint32_t accumulator_count + Py_ssize_t accumulator_count # # The percent of pixels within the octagon whose value is # less than or equal to the median-filtered value (e.g. for # median, this is 50, for lower quartile it's 25) # - np.int32_t percent + Py_ssize_t percent # # last_update_column keeps track of the column # of the last update # to the fine histogram accumulator. Short-term, the median # stays in one coarse block so only one fine histogram might # need to be updated # - np.int32_t last_update_column[16] + Py_ssize_t last_update_column[16] ############################################################################ # # allocate_histograms - allocates the Histograms structure for the run # ############################################################################ -cdef Histograms *allocate_histograms(np.int32_t rows, - np.int32_t columns, - np.int32_t row_stride, - np.int32_t col_stride, - np.int32_t radius, - np.int32_t percent, - np.uint8_t *data, - np.uint8_t *mask, - np.uint8_t *output): +cdef Histograms *allocate_histograms(Py_ssize_t rows, + Py_ssize_t columns, + Py_ssize_t row_stride, + Py_ssize_t col_stride, + Py_ssize_t radius, + Py_ssize_t percent, + cnp.uint8_t *data, + cnp.uint8_t *mask, + cnp.uint8_t *output): cdef: - unsigned int adjusted_stripe_length = columns + 2*radius + 1 - unsigned int memory_size + Py_ssize_t adjusted_stripe_length = columns + 2*radius + 1 + Py_ssize_t memory_size void *ptr Histograms *ph - size_t roundoff - int a + Py_ssize_t roundoff + Py_ssize_t a SCoord *psc memory_size = (adjusted_stripe_length * - (sizeof(Histogram) + sizeof(PixelCount))+ - sizeof(Histograms)+32) + (sizeof(Histogram) + sizeof(PixelCount)) + + sizeof(Histograms) + 64) ptr = malloc(memory_size) memset(ptr, 0, memory_size) - ph = ptr + # align ph.accumulator to 32-byte boundary + roundoff = ( ptr + 31) % 32 + ph = ( ptr + 31 - roundoff) if not ptr: return ph ph.memory = ptr - ptr = (ph+1) - ph.pixel_count = ptr - ptr = (ph.pixel_count + adjusted_stripe_length) + ptr = (ph + 1) + ph.pixel_count = ptr + ptr = (ph.pixel_count + adjusted_stripe_length) # # Align histogram memory to a 32-byte boundary # - roundoff = ptr - roundoff += 31 - roundoff -= roundoff % 32 - ptr = roundoff - ph.histogram = ptr + roundoff = ( ptr + 31) % 32 + ptr = ( ptr + 31 - roundoff) + ph.histogram = ptr # # Fill in the statistical things we keep around # @@ -228,7 +232,7 @@ cdef Histograms *allocate_histograms(np.int32_t rows, # a_2 is the offset from the center to each of the octagon # corners # - a = (radius * 2.0 / 2.414213) + a = (radius * 2.0 / 2.414213) a_2 = a / 2 if a_2 == 0: a_2 = 1 @@ -322,34 +326,18 @@ cdef void set_stride(Histograms *ph, SCoord *psc): # a column that is "radius" to the left. # ############################################################################ -cdef inline np.int32_t tl_br_colidx(Histograms *ph, np.int32_t colidx): +cdef inline Py_ssize_t tl_br_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length -cdef inline np.int32_t tr_bl_colidx(Histograms *ph, np.int32_t colidx): +cdef inline Py_ssize_t tr_bl_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ ph.stripe_length -cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx): +cdef inline Py_ssize_t leading_edge_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 5*ph.radius) % ph.stripe_length -cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): +cdef inline Py_ssize_t trailing_edge_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius - 1) % ph.stripe_length -# -# add16 - add 16 consecutive integers -# -# Add an array of 16 16-bit integers to an accumulator of 16 16-bit integers -# -# TO_DO - optimize using SIMD instructions -# -cdef inline void add16(np.uint16_t *dest, np.uint16_t *src): - cdef int i - for i in range(16): - dest[i] += src[i] - -cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src): - cdef int i - for i in range(16): - dest[i] -= src[i] ############################################################################ # @@ -360,9 +348,8 @@ cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src): # colidx - the index of the column to add # ############################################################################ -cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): - cdef: - int offset +cdef inline void accumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx): + cdef Py_ssize_t offset offset = tr_bl_colidx(ph, colidx) if ph.pixel_count[offset].top_right > 0: @@ -383,9 +370,8 @@ cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): # for a given column # ############################################################################ -cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): - cdef: - int offset +cdef inline void deaccumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx): + cdef Py_ssize_t offset # # The trailing diagonals don't appear until here # @@ -414,11 +400,11 @@ cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx # ############################################################################ cdef inline void accumulate_fine_histogram(Histograms *ph, - np.int32_t colidx, - np.uint32_t fineidx): + Py_ssize_t colidx, + Py_ssize_t fineidx): cdef: - int fineoffset = fineidx * 16 - int offset + Py_ssize_t fineoffset = fineidx * 16 + Py_ssize_t offset offset = tr_bl_colidx(ph, colidx) add16(ph.accumulator.fine + fineoffset, @@ -438,11 +424,11 @@ cdef inline void accumulate_fine_histogram(Histograms *ph, # ############################################################################ cdef inline void deaccumulate_fine_histogram(Histograms *ph, - np.int32_t colidx, - np.uint32_t fineidx): + Py_ssize_t colidx, + Py_ssize_t fineidx): cdef: - int fineoffset = fineidx * 16 - int offset + Py_ssize_t fineoffset = fineidx * 16 + Py_ssize_t offset # # The trailing diagonals don't appear until here @@ -470,10 +456,7 @@ cdef inline void deaccumulate_fine_histogram(Histograms *ph, ############################################################################ cdef inline void accumulate(Histograms *ph): - cdef: - int i - int j - np.int32_t accumulator + cdef cnp.int32_t accumulator accumulate_coarse_histogram(ph, ph.current_column) deaccumulate_coarse_histogram(ph, ph.current_column) @@ -497,11 +480,11 @@ cdef inline void accumulate(Histograms *ph): # to choose remains to be done. ############################################################################ -cdef inline void update_fine(Histograms *ph, int fineidx): +cdef inline void update_fine(Histograms *ph, Py_ssize_t fineidx): cdef: - int first_update_column = ph.last_update_column[fineidx]+1 - int update_limit = ph.current_column+1 - int i + Py_ssize_t first_update_column = ph.last_update_column[fineidx]+1 + Py_ssize_t update_limit = ph.current_column+1 + Py_ssize_t i for i in range(first_update_column, update_limit): accumulate_fine_histogram(ph, i, fineidx) @@ -526,23 +509,23 @@ cdef inline void update_histogram(Histograms *ph, SCoord *last_coord, SCoord *coord): cdef: - np.int32_t current_column = ph.current_column - np.int32_t current_row = ph.current_row - np.int32_t current_stride = ph.current_stride - np.int32_t column_count = ph.column_count - np.int32_t row_count = ph.row_count - np.uint8_t value - np.int32_t stride - np.int32_t x - np.int32_t y + Py_ssize_t current_column = ph.current_column + Py_ssize_t current_row = ph.current_row + Py_ssize_t current_stride = ph.current_stride + Py_ssize_t column_count = ph.column_count + Py_ssize_t row_count = ph.row_count + cnp.uint8_t value + Py_ssize_t stride + Py_ssize_t x + Py_ssize_t y x = last_coord.x + current_column y = last_coord.y + current_row stride = current_stride+last_coord.stride - if (x >= 0 and x < column_count and - y >= 0 and y < row_count and - ph.mask[stride]): + if (x >= 0 and x < column_count and \ + y >= 0 and y < row_count and \ + ph.mask[stride]): value = ph.data[stride] pixel_count[0] -= 1 hist_piece.fine[value] -= 1 @@ -552,9 +535,9 @@ cdef inline void update_histogram(Histograms *ph, y = coord.y + current_row stride = current_stride + coord.stride - if (x >= 0 and x < column_count and - y >= 0 and y < row_count and - ph.mask[stride]): + if (x >= 0 and x < column_count and \ + y >= 0 and y < row_count and \ + ph.mask[stride]): value = ph.data[stride] pixel_count[0] += 1 hist_piece.fine[value] += 1 @@ -567,21 +550,21 @@ cdef inline void update_histogram(Histograms *ph, ############################################################################ cdef inline void update_current_location(Histograms *ph): cdef: - np.int32_t current_column = ph.current_column - np.int32_t radius = ph.radius - np.int32_t top_left_off = tl_br_colidx(ph, current_column) - np.int32_t top_right_off = tr_bl_colidx(ph, current_column) - np.int32_t bottom_left_off = tr_bl_colidx(ph, current_column) - np.int32_t bottom_right_off = tl_br_colidx(ph, current_column) - np.int32_t leading_edge_off = leading_edge_colidx(ph, current_column) - np.int32_t *coarse_histogram - np.int32_t *fine_histogram - np.int32_t last_xoff - np.int32_t last_yoff - np.int32_t last_stride - np.int32_t xoff - np.int32_t yoff - np.int32_t stride + Py_ssize_t current_column = ph.current_column + Py_ssize_t radius = ph.radius + Py_ssize_t top_left_off = tl_br_colidx(ph, current_column) + Py_ssize_t top_right_off = tr_bl_colidx(ph, current_column) + Py_ssize_t bottom_left_off = tr_bl_colidx(ph, current_column) + Py_ssize_t bottom_right_off = tl_br_colidx(ph, current_column) + Py_ssize_t leading_edge_off = leading_edge_colidx(ph, current_column) + cnp.int32_t *coarse_histogram + cnp.int32_t *fine_histogram + Py_ssize_t last_xoff + Py_ssize_t last_yoff + Py_ssize_t last_stride + Py_ssize_t xoff + Py_ssize_t yoff + Py_ssize_t stride update_histogram(ph, &ph.histogram[top_left_off].top_left, &ph.pixel_count[top_left_off].top_left, @@ -614,18 +597,20 @@ cdef inline void update_current_location(Histograms *ph): # ############################################################################ -cdef inline np.uint8_t find_median(Histograms *ph): +cdef inline cnp.uint8_t find_median(Histograms *ph): cdef: - np.uint32_t pixels_below # of pixels below the median - int i - int j - int k - np.uint32_t accumulator + Py_ssize_t pixels_below # of pixels below the median + Py_ssize_t i + Py_ssize_t j + Py_ssize_t k + cnp.uint32_t accumulator if ph.accumulator_count == 0: return 0 - pixels_below = ((ph.accumulator_count * ph.percent + 50) - / 100) # +50 for roundoff + + # +50 for roundoff + pixels_below = (ph.accumulator_count * ph.percent + 50) / 100 + if pixels_below > 0: pixels_below -= 1 @@ -637,10 +622,10 @@ cdef inline np.uint8_t find_median(Histograms *ph): accumulator -= ph.accumulator.coarse[i] update_fine(ph, i) - for j in range(i*16,(i+1)*16): + for j in range(i*16, (i + 1)*16): accumulator += ph.accumulator.fine[j] if accumulator > pixels_below: - return j + return j return 0 @@ -659,30 +644,30 @@ cdef inline np.uint8_t find_median(Histograms *ph): # output - array to be filled with filtered pixels # ############################################################################ -cdef int c_median_filter(np.int32_t rows, - np.int32_t columns, - np.int32_t row_stride, - np.int32_t col_stride, - np.int32_t radius, - np.int32_t percent, - np.uint8_t *data, - np.uint8_t *mask, - np.uint8_t *output): +cdef int c_median_filter(Py_ssize_t rows, + Py_ssize_t columns, + Py_ssize_t row_stride, + Py_ssize_t col_stride, + Py_ssize_t radius, + Py_ssize_t percent, + cnp.uint8_t *data, + cnp.uint8_t *mask, + cnp.uint8_t *output): cdef: Histograms *ph Histogram *phistogram - int row - int col - int i - np.int32_t top_left_off - np.int32_t top_right_off - np.int32_t bottom_left_off - np.int32_t bottom_right_off + Py_ssize_t row + Py_ssize_t col + Py_ssize_t i + Py_ssize_t top_left_off + Py_ssize_t top_right_off + Py_ssize_t bottom_left_off + Py_ssize_t bottom_right_off ph = allocate_histograms(rows, columns, row_stride, col_stride, radius, percent, data, mask, output) if not ph: - return 1 + return 1 for row in range(-radius, rows): # @@ -709,7 +694,7 @@ cdef int c_median_filter(np.int32_t rows, # # Initialize the accumulator (octagon histogram) to zero # - memset(&ph.accumulator, 0, sizeof(ph.accumulator)) + memset(&(ph.accumulator), 0, sizeof(ph.accumulator)) ph.accumulator_count = 0 for i in range(16): ph.last_update_column[i] = -radius-1 @@ -721,7 +706,7 @@ cdef int c_median_filter(np.int32_t rows, # Update locations and coarse accumulator for the octagon # for points before 0 # - for col in range(-radius, 0 if row >=0 else columns+radius): + for col in range(-radius, 0 if row >= 0 else columns+radius): ph.current_column = col ph.current_stride = row * row_stride + col * col_stride update_current_location(ph) @@ -742,16 +727,18 @@ cdef int c_median_filter(np.int32_t rows, ph.current_stride = row * row_stride + col * col_stride update_current_location(ph) - free_histograms(ph) return 0 -def median_filter( - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] data, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] mask, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] output, - int radius, - np.int32_t percent): + +def median_filter(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] data, + cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] mask, + cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] output, + int radius, + cnp.int32_t percent): """Median filter with octagon shape and masking. Parameters @@ -773,10 +760,10 @@ def median_filter( """ if percent < 0: - raise ValueError('Median filter percent = %d is less than zero' % \ + raise ValueError('Median filter percent = %d is less than zero' % percent) if percent > 100: - raise ValueError('Median filter percent = %d is greater than 100' % \ + raise ValueError('Median filter percent = %d is greater than 100' % percent) if data.shape[0] != mask.shape[0] or data.shape[1] != mask.shape[1]: raise ValueError('Data shape (%d, %d) is not mask shape (%d, %d)' % @@ -786,10 +773,12 @@ def median_filter( raise ValueError('Data shape (%d, %d) is not output shape (%d, %d)' % (data.shape[0], data.shape[1], output.shape[0], output.shape[1])) - if c_median_filter(data.shape[0], data.shape[1], - data.strides[0], data.strides[1], + if c_median_filter(data.shape[0], + data.shape[1], + data.strides[0], + data.strides[1], radius, percent, - data.data, - mask.data, - output.data): + data.data, + mask.data, + output.data): raise MemoryError('Failed to allocate scratchpad memory') diff --git a/skimage/filter/_denoise.py b/skimage/filter/_denoise.py index e26f8387..87850759 100644 --- a/skimage/filter/_denoise.py +++ b/skimage/filter/_denoise.py @@ -32,7 +32,7 @@ def _denoise_tv_chambolle_3d(im, weight=100, eps=2.e-4, n_iter_max=200): Rudin, Osher and Fatemi algorithm. Examples - --------- + -------- >>> x, y, z = np.ogrid[0:40, 0:40, 0:40] >>> mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2 >>> mask = mask.astype(np.float) @@ -123,7 +123,7 @@ def _denoise_tv_chambolle_2d(im, weight=50, eps=2.e-4, n_iter_max=200): Springer, 2004, 20, 89-97. Examples - --------- + -------- >>> from skimage import color, data >>> lena = color.rgb2gray(data.lena()) >>> lena += 0.5 * lena.std() * np.random.randn(*lena.shape) @@ -221,7 +221,7 @@ def denoise_tv_chambolle(im, weight=50, eps=2.e-4, n_iter_max=200, Springer, 2004, 20, 89-97. Examples - --------- + -------- 2D example on Lena image: >>> from skimage import color, data diff --git a/skimage/filter/_denoise_cy.pyx b/skimage/filter/_denoise_cy.pyx index 8ebf5c73..5a85e84a 100644 --- a/skimage/filter/_denoise_cy.pyx +++ b/skimage/filter/_denoise_cy.pyx @@ -17,7 +17,7 @@ cdef inline double _gaussian_weight(double sigma, double value): return exp(-0.5 * (value / sigma)**2) -cdef double* _compute_color_lut(int bins, double sigma, double max_value): +cdef double* _compute_color_lut(Py_ssize_t bins, double sigma, double max_value): cdef: double* color_lut = malloc(bins * sizeof(double)) @@ -29,7 +29,7 @@ cdef double* _compute_color_lut(int bins, double sigma, double max_value): return color_lut -cdef double* _compute_range_lut(int win_size, double sigma): +cdef double* _compute_range_lut(Py_ssize_t win_size, double sigma): cdef: double* range_lut = malloc(win_size**2 * sizeof(double)) @@ -45,9 +45,9 @@ cdef double* _compute_range_lut(int win_size, double sigma): return range_lut -def denoise_bilateral(image, int win_size=5, sigma_range=None, - double sigma_spatial=1, int bins=10000, mode='constant', - double cval=0): +def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None, + double sigma_spatial=1, Py_ssize_t bins=10000, + mode='constant', double cval=0): """Denoise image using bilateral filter. This is an edge-preserving and noise reducing denoising filter. It averages diff --git a/skimage/filter/rank/_core16.pxd b/skimage/filter/rank/_core16.pxd index 5f7de9df..5586aea1 100644 --- a/skimage/filter/rank/_core16.pxd +++ b/skimage/filter/rank/_core16.pxd @@ -1,4 +1,7 @@ -cimport numpy as np +cimport numpy as cnp + + +ctypedef cnp.uint16_t dtype_t cdef int int_max(int a, int b) @@ -6,12 +9,12 @@ cdef int int_min(int a, int b) # 16-bit core kernel receives extra information about data bitdepth -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint16_t, ndim=2] out, +cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, Py_ssize_t bitdepth, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core16.pyx b/skimage/filter/rank/_core16.pyx index aa959fd0..36bc500d 100644 --- a/skimage/filter/rank/_core16.pyx +++ b/skimage/filter/rank/_core16.pyx @@ -4,7 +4,8 @@ #cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.stdlib cimport malloc, free from _core8 cimport is_in_mask @@ -18,24 +19,24 @@ cdef inline int int_min(int a, int b): cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, - np.uint16_t value): + dtype_t value): histo[value] += 1 pop[0] += 1 cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, - np.uint16_t value): + dtype_t value): histo[value] -= 1 pop[0] -= 1 -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint16_t, ndim=2] out, +cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, Py_ssize_t bitdepth, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and @@ -67,9 +68,9 @@ cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, assert (image < maxbin).all() # define pointers to the data - cdef np.uint16_t * out_data = out.data - cdef np.uint16_t * image_data = image.data - cdef np.uint8_t * mask_data = mask.data + cdef dtype_t * out_data = out.data + cdef dtype_t * image_data = image.data + cdef cnp.uint8_t * mask_data = mask.data # define local variable types cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row diff --git a/skimage/filter/rank/_core8.pxd b/skimage/filter/rank/_core8.pxd index 38236b87..d3b6d8c2 100644 --- a/skimage/filter/rank/_core8.pxd +++ b/skimage/filter/rank/_core8.pxd @@ -1,22 +1,25 @@ -cimport numpy as np +cimport numpy as cnp -cdef np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b) -cdef np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b) +ctypedef cnp.uint8_t dtype_t -cdef np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, - np.uint8_t * mask) +cdef dtype_t uint8_max(dtype_t a, dtype_t b) +cdef dtype_t uint8_min(dtype_t a, dtype_t b) + + +cdef dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, + dtype_t * mask) # 8-bit core kernel receives extra information about data inferior and superior # percentiles -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint8_t, ndim=2] out, +cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core8.pyx b/skimage/filter/rank/_core8.pyx index 9f4bc693..eca47891 100644 --- a/skimage/filter/rank/_core8.pyx +++ b/skimage/filter/rank/_core8.pyx @@ -4,33 +4,34 @@ #cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.stdlib cimport malloc, free -cdef inline np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b): +cdef inline dtype_t uint8_max(dtype_t a, dtype_t b): return a if a >= b else b -cdef inline np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b): +cdef inline dtype_t uint8_min(dtype_t a, dtype_t b): return a if a <= b else b cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, - np.uint8_t value): + dtype_t value): histo[value] += 1 pop[0] += 1 cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, - np.uint8_t value): + dtype_t value): histo[value] -= 1 pop[0] -= 1 -cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, - np.uint8_t * mask): +cdef inline dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, + dtype_t * mask): """Check whether given coordinate is within image and mask is true.""" if r < 0 or r > rows - 1 or c < 0 or c > cols - 1: return 0 @@ -41,12 +42,12 @@ cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, return 0 -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint8_t, ndim=2] out, +cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and @@ -69,9 +70,9 @@ cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, # define pointers to the data - cdef np.uint8_t * out_data = out.data - cdef np.uint8_t * image_data = image.data - cdef np.uint8_t * mask_data = mask.data + cdef dtype_t * out_data = out.data + cdef dtype_t * image_data = image.data + cdef dtype_t * mask_data = mask.data # define local variable types cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index e81bf81f..232d6812 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -3,9 +3,8 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np -from libc.math cimport log2 +cimport numpy as cnp +from libc.math cimport log from skimage.filter.rank._core16 cimport _core16 @@ -14,11 +13,14 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax, delta if pop: @@ -32,16 +34,16 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint16_t > (1. * (maxbin - 1) * (g - imin) / delta) + return (1. * (maxbin - 1) * (g - imin) / delta) else: - return < np.uint16_t > (imax - imin) + return (imax - imin) -cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: @@ -49,15 +51,15 @@ cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint16_t > (g - i) + return (g - i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = 0. @@ -67,16 +69,16 @@ cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, if i >= g: break - return < np.uint16_t > (((maxbin - 1) * sum) / pop) + return (((maxbin - 1) * sum) / pop) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax if pop: @@ -88,66 +90,66 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, if histo[i]: imin = i break - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_maximum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(maxbin - 1, -1, -1): if histo[i]: - return < np.uint16_t > (i) + return (i) - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > (mean / pop) + return (mean / pop) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_meansubstraction(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_meansubstraction(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > ((g - mean / pop) / 2. + (midbin - 1)) + return ((g - mean / pop) / 2. + (midbin - 1)) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = pop / 2.0 @@ -156,31 +158,31 @@ cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, if histo[i]: sum -= histo[i] if sum < 0: - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_minimum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(maxbin): if histo[i]: - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t hmax = 0, imax = 0 if pop: @@ -188,19 +190,19 @@ cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, if histo[i] > hmax: hmax = histo[i] imax = i - return < np.uint16_t > (imax) + return (imax) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax if pop: @@ -213,42 +215,42 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, imin = i break if imax - g < g - imin: - return < np.uint16_t > (imax) + return (imax) else: - return < np.uint16_t > (imin) + return (imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - return < np.uint16_t > (pop) +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): + return (pop) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > (g > (mean / pop)) + return (g > (mean / pop)) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: @@ -256,15 +258,15 @@ cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint16_t > (i - g) + return (i - g) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float e, p @@ -274,147 +276,147 @@ cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(maxbin): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.6931471805599453 - return < np.uint16_t > e * 1000 + return e * 1000 else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- # python wrappers # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def bottomhat(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def bottomhat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def equalize(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def equalize(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_equalize, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def gradient(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def maximum(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def maximum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_maximum, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def meansubstraction(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def meansubstraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def median(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def median(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_median, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def minimum(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def minimum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_minimum, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def modal(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def modal(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_modal, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def threshold(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def tophat(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def tophat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_tophat, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def entropy(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def entropy(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_entropy, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank16_bilateral.pyx b/skimage/filter/rank/_crank16_bilateral.pyx index c71ccc5c..e431e42b 100644 --- a/skimage/filter/rank/_crank16_bilateral.pyx +++ b/skimage/filter/rank/_crank16_bilateral.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core16 cimport _core16 @@ -13,11 +12,14 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 cdef float mean = 0. @@ -28,18 +30,18 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, bilat_pop += histo[i] mean += histo[i] * i if bilat_pop: - return < np.uint16_t > (mean / bilat_pop) + return (mean / bilat_pop) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 @@ -47,9 +49,9 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, for i in range(maxbin): if (g > (i - s0)) and (g < (i + s1)): bilat_pop += histo[i] - return < np.uint16_t > (bilat_pop) + return (bilat_pop) else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- @@ -57,10 +59,10 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1): """average greylevel (clipped on uint8) """ @@ -68,10 +70,10 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, 0., 0., s0, s1) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1): """returns the number of actual pixels of the structuring element inside the mask diff --git a/skimage/filter/rank/_crank16_percentiles.pyx b/skimage/filter/rank/_crank16_percentiles.pyx index 220d2386..0ab71353 100644 --- a/skimage/filter/rank/_crank16_percentiles.pyx +++ b/skimage/filter/rank/_crank16_percentiles.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core16 cimport _core16, int_min, int_max @@ -13,11 +12,14 @@ from skimage.filter.rank._core16 cimport _core16, int_min, int_max # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -38,19 +40,20 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, delta = imax - imin if delta > 0: - return < np.uint16_t > (1.0 * (maxbin - 1) - * (int_min(int_max(imin, g), imax) - imin) / delta) + return (1.0 * (maxbin - 1) + * (int_min(int_max(imin, g), imax) + - imin) / delta) else: - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -69,16 +72,16 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, imax = i break - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -93,21 +96,21 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, mean += histo[i] * i if n > 0: - return < np.uint16_t > (1.0 * mean / n) + return (1.0 * mean / n) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean_substraction(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -121,21 +124,21 @@ cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint16_t > ((g - (mean / n)) * .5 + midbin) + return ((g - (mean / n)) * .5 + midbin) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -154,22 +157,22 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, imax = i break if g > imax: - return < np.uint16_t > imax + return imax if g < imin: - return < np.uint16_t > imin + return imin if imax - g < g - imin: - return < np.uint16_t > imax + return imax else: - return < np.uint16_t > imin + return imin else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -180,16 +183,16 @@ cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n @@ -200,16 +203,16 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, sum += histo[i] if (sum >= p0 * pop) and (sum <= p1 * pop): n += histo[i] - return < np.uint16_t > (n) + return (n) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -220,9 +223,9 @@ cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint16_t > ((maxbin - 1) * (g >= i)) + return ((maxbin - 1) * (g >= i)) else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- @@ -230,10 +233,10 @@ cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """bottom hat @@ -242,10 +245,10 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def gradient(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return p0,p1 percentile gradient @@ -254,10 +257,10 @@ def gradient(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return mean between [p0 and p1] percentiles @@ -266,10 +269,10 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean_substraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return original - mean between [p0 and p1] percentiles *.5 +127 @@ -279,10 +282,10 @@ def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """reforce contrast using percentiles @@ -291,10 +294,10 @@ def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def percentile(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def percentile(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return p0 percentile @@ -303,10 +306,10 @@ def percentile(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return nb of pixels between [p0 and p1] @@ -315,10 +318,10 @@ def pop(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def threshold(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return (maxbin-1) if g > percentile p0 diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index 8bff9703..cb7febac 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -3,9 +3,8 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np -from libc.math cimport log2 +cimport numpy as cnp +from libc.math cimport log from skimage.filter.rank._core8 cimport _core8 @@ -14,9 +13,12 @@ from skimage.filter.rank._core8 cimport _core8 # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint8_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax, delta @@ -31,16 +33,16 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint8_t > (255. * (g - imin) / delta) + return (255. * (g - imin) / delta) else: - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i @@ -49,14 +51,14 @@ cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint8_t > (g - i) + return (g - i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = 0. @@ -67,14 +69,14 @@ cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, if i >= g: break - return < np.uint8_t > ((255 * sum) / pop) + return ((255 * sum) / pop) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax @@ -87,28 +89,28 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, if histo[i]: imin = i break - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_maximum(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(255, -1, -1): if histo[i]: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -116,14 +118,14 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > (mean / pop) + return (mean / pop) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_meansubstraction(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -131,14 +133,14 @@ cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > ((g - mean / pop) / 2. + 127) + return ((g - mean / pop) / 2. + 127) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = pop / 2.0 @@ -148,28 +150,28 @@ cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, if histo[i]: sum -= histo[i] if sum < 0: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_minimum(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(256): if histo[i]: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t hmax = 0, imax = 0 @@ -178,14 +180,14 @@ cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, if histo[i] > hmax: hmax = histo[i] imax = i - return < np.uint8_t > (imax) + return (imax) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax @@ -199,23 +201,23 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, imin = i break if imax - g < g - imin: - return < np.uint8_t > (imax) + return (imax) else: - return < np.uint8_t > (imin) + return (imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): - return < np.uint8_t > (pop) + return (pop) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -223,14 +225,14 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > (g > (mean / pop)) + return (g > (mean / pop)) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i @@ -239,20 +241,20 @@ cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint8_t > (i - g) + return (i - g) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_noise_filter(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef Py_ssize_t min_i # early stop if at least one pixel of the neighborhood has the same g if histo[g] > 0: - return < np.uint8_t > 0 + return 0 for i in range(g, -1, -1): if histo[i]: @@ -262,14 +264,14 @@ cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, if histo[i]: break if i - g < min_i: - return < np.uint8_t > (i - g) + return (i - g) else: - return < np.uint8_t > min_i + return min_i -cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float e, p @@ -279,15 +281,15 @@ cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(256): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.6931471805599453 - return < np.uint8_t > e * 10 + return e * 10 else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, - float p0, float p1, Py_ssize_t s0, - Py_ssize_t s1): +cdef inline dtype_t kernel_otsu(Py_ssize_t * histo, float pop, dtype_t g, + float p0, float p1, Py_ssize_t s0, + Py_ssize_t s1): cdef Py_ssize_t i cdef Py_ssize_t max_i cdef float P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b @@ -299,7 +301,7 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, mu += histo[i] * i mu = (mu / pop) else: - return < np.uint8_t > (0) + return (0) # maximizing the between class variance max_i = 0 @@ -319,7 +321,7 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, max_i = i q1 = new_q1 - return < np.uint8_t > max_i + return max_i # ----------------------------------------------------------------- @@ -328,154 +330,154 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def bottomhat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def equalize(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def equalize(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_equalize, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def gradient(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def maximum(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def maximum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_maximum, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def mean(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def meansubstraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def median(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def median(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_median, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def minimum(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def minimum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def modal(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def modal(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_modal, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def pop(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def threshold(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def tophat(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def tophat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def noise_filter(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def entropy(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def entropy(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def otsu(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def otsu(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank8_percentiles.pyx b/skimage/filter/rank/_crank8_percentiles.pyx index 31b00742..d085957e 100644 --- a/skimage/filter/rank/_crank8_percentiles.pyx +++ b/skimage/filter/rank/_crank8_percentiles.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min @@ -13,9 +12,12 @@ from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint8_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -37,17 +39,17 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint8_t > (255 - * (uint8_min(uint8_max(imin, g), imax) - imin) / delta) + return (255 * (uint8_min(uint8_max(imin, g), imax) + - imin) / delta) else: - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (128) + return (128) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -65,14 +67,14 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, imax = i break - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -85,18 +87,18 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint8_t > (1.0 * mean / n) + return (1.0 * mean / n) else: - return < np.uint8_t > (0) + return (0) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, - float pop, - np.uint8_t g, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean_substraction(Py_ssize_t * histo, + float pop, + dtype_t g, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -109,17 +111,17 @@ cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint8_t > ((g - (mean / n)) * .5 + 127) + return ((g - (mean / n)) * .5 + 127) else: - return < np.uint8_t > (0) + return (0) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -137,20 +139,20 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, imax = i break if g > imax: - return < np.uint8_t > imax + return imax if g < imin: - return < np.uint8_t > imin + return imin if imax - g < g - imin: - return < np.uint8_t > imax + return imax else: - return < np.uint8_t > imin + return imin else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -160,14 +162,14 @@ cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n if pop: @@ -177,14 +179,14 @@ cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, sum += histo[i] if (sum >= p0 * pop) and (sum <= p1 * pop): n += histo[i] - return < np.uint8_t > (n) + return (n) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -194,9 +196,9 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint8_t > (255 * (g >= i)) + return (255 * (g >= i)) else: - return < np.uint8_t > (0) + return (0) # ----------------------------------------------------------------- @@ -204,10 +206,10 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """autolevel """ @@ -215,10 +217,10 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def gradient(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return p0,p1 percentile gradient """ @@ -226,10 +228,10 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def mean(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return mean between [p0 and p1] percentiles """ @@ -237,10 +239,10 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean_substraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return original - mean between [p0 and p1] percentiles *.5 +127 """ @@ -248,10 +250,10 @@ def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """reforce contrast using percentiles """ @@ -259,10 +261,10 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def percentile(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def percentile(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return p0 percentile """ @@ -270,10 +272,10 @@ def percentile(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def pop(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return nb of pixels between [p0 and p1] """ @@ -281,10 +283,10 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def threshold(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return 255 if g > percentile p0 """ diff --git a/skimage/filter/rank/bilateral_rank.pyx b/skimage/filter/rank/bilateral_rank.pyx index dec07c28..04f6cb35 100644 --- a/skimage/filter/rank/bilateral_rank.pyx +++ b/skimage/filter/rank/bilateral_rank.pyx @@ -3,8 +3,8 @@ The local histogram is computed using a sliding window similar to the method described in [1]_. -Input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit), 8-bit -images are casted in 16-bit the number of histogram bins is determined from the +Input image must be 16-bit with a value < 4096 (i.e. 12 bit), +the number of histogram bins is determined from the maximum value present in the image. The pixel neighborhood is defined by: @@ -89,9 +89,8 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, as the - algorithm uses max. 12bit histogram, an exception will be raised if - image has a value > 4095 + Image array (uint16). As the algorithm uses max. 12bit histogram, + an exception will be raised if image has a value > 4095 selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -108,7 +107,7 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Returns ------- - out : uint16 array (uint8 image are casted to uint16) + out : uint16 array The result of the local bilateral mean. See also @@ -118,17 +117,15 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Notes ----- - * input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit) - - * 8-bit images are casted in 16-bit + * input image are 16-bit only Examples -------- >>> from skimage import data >>> from skimage.morphology import disk >>> from skimage.filter.rank import bilateral_mean - >>> # Load test image - >>> ima = data.camera() + >>> # Load test image / cast to uint16 + >>> ima = data.camera().astype(np.uint16) >>> # bilateral filtering of cameraman image using a flat kernel >>> bilat_ima = bilateral_mean(ima, disk(20), s0=10,s1=10) """ @@ -146,9 +143,8 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False, Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, as the - algorithm uses max. 12bit histogram, an exception will be raised if - image has a value > 4095 + Image array (uint16). As the algorithm uses max. 12bit histogram, + an exception will be raised if image has a value > 4095 selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -165,20 +161,25 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False, Returns ------- - out : uint16 array (uint8 image are casted to uint16) + out : uint16 array the local number of pixels inside the bilateral neighborhood + Notes + ----- + + * input image are 16-bit only + Examples -------- >>> # Local mean >>> from skimage.morphology import square >>> import skimage.filter.rank as rank - >>> ima8 = 255 * np.array([[0, 0, 0, 0, 0], + >>> ima16 = 255 * np.array([[0, 0, 0, 0, 0], ... [0, 1, 1, 1, 0], ... [0, 1, 1, 1, 0], ... [0, 1, 1, 1, 0], - ... [0, 0, 0, 0, 0]], dtype=np.uint8) - >>> rank.bilateral_pop(ima8, square(3), s0=10,s1=10) + ... [0, 0, 0, 0, 0]], dtype=np.uint16) + >>> rank.bilateral_pop(ima16, square(3), s0=10,s1=10) array([[3, 4, 3, 4, 3], [4, 4, 6, 4, 4], [3, 6, 9, 6, 3], diff --git a/skimage/filter/rank/rank.pyx b/skimage/filter/rank/rank.pyx index 43f03133..5380363a 100644 --- a/skimage/filter/rank/rank.pyx +++ b/skimage/filter/rank/rank.pyx @@ -648,12 +648,12 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False, References ---------- .. [Hashimoto12] N. Hashimoto et al. Referenceless image quality evaluation - for whole slide imaging. J Pathol Inform 2012;3:9. + for whole slide imaging. J Pathol Inform 2012;3:9. Returns ------- out : uint8 array or uint16 array (same as input image) - The image noise . + The image noise. """ @@ -669,7 +669,7 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False, def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False): - """Returns the entropy [wiki_entropy]_ computed locally. Entropy is computed + """Returns the entropy [1]_ computed locally. Entropy is computed using base 2 logarithm i.e. the filter returns the minimum number of bits needed to encode local greylevel distribution. @@ -698,7 +698,7 @@ def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False): References ---------- - .. [wiki_entropy] http://en.wikipedia.org/wiki/Entropy_(information_theory) + .. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory) Examples -------- @@ -726,9 +726,7 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False): Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, the algorithm - uses max. 12bit histogram, an exception will be raised if image has a - value > 4095. + Image array (uint8 array). selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -743,20 +741,24 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False): Returns ------- - out : uint8 array or uint16 array (same as input image) + out : uint8 array Otsu's threshold values References ---------- .. [otsu] http://en.wikipedia.org/wiki/Otsu's_method + Notes + ----- + * input image are 8-bit only + Examples -------- >>> # Local entropy >>> from skimage import data >>> from skimage.filter.rank import otsu >>> from skimage.morphology import disk - >>> # defining a 8- and a 16-bit test images + >>> # defining a 8-bit test images >>> a8 = data.camera() >>> loc_otsu = otsu(a8, disk(5)) >>> thresh_image = a8 >= loc_otsu diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 650a26a6..b1d070fc 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -26,34 +26,34 @@ def configuration(parent_package='', top_path=None): cython(['rank/bilateral_rank.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], - include_dirs=[get_numpy_include_dirs()]) + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) - config.add_extension('rank/_core8', sources=['rank/_core8.c'], + config.add_extension('rank._core8', sources=['rank/_core8.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_core16', sources=['rank/_core16.c'], + config.add_extension('rank._core16', sources=['rank/_core16.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank8', sources=['rank/_crank8.c'], + config.add_extension('rank._crank8', sources=['rank/_crank8.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank8_percentiles', sources=['rank/_crank8_percentiles.c'], + 'rank._crank8_percentiles', sources=['rank/_crank8_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank16', sources=['rank/_crank16.c'], + config.add_extension('rank._crank16', sources=['rank/_crank16.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_percentiles', sources=['rank/_crank16_percentiles.c'], + 'rank._crank16_percentiles', sources=['rank/_crank16_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_bilateral', sources=['rank/_crank16_bilateral.c'], + 'rank._crank16_bilateral', sources=['rank/_crank16_bilateral.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/rank', sources=['rank/rank.c'], + 'rank.rank', sources=['rank/rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/percentile_rank', sources=['rank/percentile_rank.c'], + 'rank.percentile_rank', sources=['rank/percentile_rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/bilateral_rank', sources=['rank/bilateral_rank.c'], + 'rank.bilateral_rank', sources=['rank/bilateral_rank.c'], include_dirs=[get_numpy_include_dirs()]) return config diff --git a/skimage/graph/_mcp.pxd b/skimage/graph/_mcp.pxd index 4222d877..b2e2a548 100644 --- a/skimage/graph/_mcp.pxd +++ b/skimage/graph/_mcp.pxd @@ -4,11 +4,11 @@ other cython modules can "cimport mcp" and subclass it. """ cimport heap -cimport numpy as np +cimport numpy as cnp ctypedef heap.BOOL_T BOOL_T -ctypedef unsigned char DIM_T -ctypedef np.float64_t FLOAT_T +ctypedef unsigned char DIM_T +ctypedef cnp.float64_t FLOAT_T cdef class MCP: cdef heap.FastUpdateBinaryHeap costs_heap @@ -23,7 +23,7 @@ cdef class MCP: cdef object flat_offsets cdef object offset_lengths cdef BOOL_T dirty - cdef BOOL_T use_start_cost + cdef BOOL_T use_start_cost # if use_start_cost is true, the cost of the starting element is added to # the cost of the path. Set to true by default in the base class... diff --git a/skimage/graph/_mcp.pyx b/skimage/graph/_mcp.pyx index 320493c2..4e5f6d52 100644 --- a/skimage/graph/_mcp.pyx +++ b/skimage/graph/_mcp.pyx @@ -1,5 +1,7 @@ -# -*- python -*- - +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False """Cython implementation of Dijkstra's minimum cost path algorithm, for use with data on a n-dimensional lattice. @@ -32,19 +34,19 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -import cython -cimport numpy as np import numpy as np -cimport heap import heap -ctypedef np.int8_t OFFSET_T +cimport numpy as cnp +cimport heap + +ctypedef cnp.int8_t OFFSET_T OFFSET_D = np.int8 -ctypedef np.int16_t OFFSETS_INDEX_T +ctypedef cnp.int16_t OFFSETS_INDEX_T OFFSETS_INDEX_D = np.int16 -ctypedef np.int8_t EDGE_T +ctypedef cnp.int8_t EDGE_T EDGE_D = np.int8 -ctypedef np.intp_t INDEX_T +ctypedef cnp.intp_t INDEX_T INDEX_D = np.intp FLOAT_D = np.float64 @@ -317,7 +319,6 @@ cdef class MCP: FLOAT_T new_cost, FLOAT_T offset_length): return new_cost - @cython.boundscheck(False) def find_costs(self, starts, ends=None, find_all_ends=True): """ Find the minimum-cost path from the given starting points. @@ -366,7 +367,7 @@ cdef class MCP: cdef BOOL_T use_ends = 0 cdef INDEX_T num_ends cdef BOOL_T all_ends = find_all_ends - cdef np.ndarray[INDEX_T, ndim=1] flat_ends + cdef cnp.ndarray[INDEX_T, ndim=1] flat_ends starts = _normalize_indices(starts, self.costs_shape) if starts is None: raise ValueError('start points must all be within the costs array') @@ -385,18 +386,18 @@ cdef class MCP: # lookup and array-ify object attributes for fast use cdef heap.FastUpdateBinaryHeap costs_heap = self.costs_heap - cdef np.ndarray[FLOAT_T, ndim=1] flat_costs = self.flat_costs - cdef np.ndarray[FLOAT_T, ndim=1] flat_cumulative_costs = \ + cdef cnp.ndarray[FLOAT_T, ndim=1] flat_costs = self.flat_costs + cdef cnp.ndarray[FLOAT_T, ndim=1] flat_cumulative_costs = \ self.flat_cumulative_costs - cdef np.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ + cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ self.traceback_offsets - cdef np.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \ + cdef cnp.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \ self.flat_pos_edge_map - cdef np.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \ + cdef cnp.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \ self.flat_neg_edge_map - cdef np.ndarray[OFFSET_T, ndim=2] offsets = self.offsets - cdef np.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets - cdef np.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths + cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets + cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets + cdef cnp.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths cdef DIM_T dim = self.dim cdef int num_offsets = len(flat_offsets) @@ -514,7 +515,6 @@ cdef class MCP: self.dirty = 1 return cumulative_costs, traceback - @cython.boundscheck(False) def traceback(self, end): """traceback(end) @@ -555,12 +555,12 @@ cdef class MCP: raise ValueError('no minimum-cost path was found ' 'to the specified end point') - cdef np.ndarray[INDEX_T, ndim=1] position = \ + cdef cnp.ndarray[INDEX_T, ndim=1] position = \ np.array(ends[0], dtype=INDEX_D) - cdef np.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ + cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ self.traceback_offsets - cdef np.ndarray[OFFSET_T, ndim=2] offsets = self.offsets - cdef np.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets + cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets + cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets cdef OFFSETS_INDEX_T offset cdef DIM_T d diff --git a/skimage/graph/heap.pxd b/skimage/graph/heap.pxd index 51b4f79c..e31aa150 100644 --- a/skimage/graph/heap.pxd +++ b/skimage/graph/heap.pxd @@ -1,7 +1,7 @@ """ This is the definition file for heap.pyx. It contains the definitions of the heap classes, such that other cython modules can "cimport heap" and thus use the -C versions of pop(), push(), and value_of(): pop_fast(), push_fast() and +C versions of pop(), push(), and value_of(): pop_fast(), push_fast() and value_of_fast() """ @@ -14,16 +14,16 @@ ctypedef unsigned char LEVELS_T cdef class BinaryHeap: cdef readonly INDEX_T count - cdef readonly LEVELS_T levels, min_levels + cdef readonly LEVELS_T levels, min_levels cdef VALUE_T *_values cdef REFERENCE_T *_references cdef REFERENCE_T _popped_ref - + cdef void _add_or_remove_level(self, LEVELS_T add_or_remove) cdef void _update(self) cdef void _update_one(self, INDEX_T i) cdef void _remove(self, INDEX_T i) - + cdef INDEX_T push_fast(self, VALUE_T value, REFERENCE_T reference) cdef VALUE_T pop_fast(self) @@ -32,8 +32,7 @@ cdef class FastUpdateBinaryHeap(BinaryHeap): cdef INDEX_T *_crossref cdef BOOL_T _invalid_ref cdef BOOL_T _pushed - + cdef VALUE_T value_of_fast(self, REFERENCE_T reference) - cdef INDEX_T push_if_lower_fast(self, VALUE_T value, + cdef INDEX_T push_if_lower_fast(self, VALUE_T value, REFERENCE_T reference) - \ No newline at end of file diff --git a/skimage/io/_plugins/_colormixer.pyx b/skimage/io/_plugins/_colormixer.pyx index ace45c94..64ac62ff 100644 --- a/skimage/io/_plugins/_colormixer.pyx +++ b/skimage/io/_plugins/_colormixer.pyx @@ -1,4 +1,7 @@ -# -*- python -*- +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False """Colour Mixer @@ -9,15 +12,14 @@ one. """ import cython -import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.math cimport exp, pow -@cython.boundscheck(False) -def add(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - int channel, int amount): +def add(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + Py_ssize_t channel, Py_ssize_t amount): """Add a given amount to a colour channel of `stateimg`, and store the result in `img`. Overflow is clipped. @@ -33,38 +35,37 @@ def add(np.ndarray[np.uint8_t, ndim=3] img, Value to add. """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] - cdef int k = channel - cdef int n = amount + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] + cdef Py_ssize_t k = channel + cdef Py_ssize_t n = amount - cdef np.int16_t op_result + cdef cnp.int16_t op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, l + cdef Py_ssize_t i, j, l with nogil: for l from 0 <= l < 256: - op_result = (l + n) + op_result = (l + n) if op_result > 255: op_result = 255 elif op_result < 0: op_result = 0 else: pass - lut[l] = op_result + lut[l] = op_result for i from 0 <= i < height: for j from 0 <= j < width: img[i, j, k] = lut[stateimg[i,j,k]] -@cython.boundscheck(False) -def multiply(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - int channel, float amount): +def multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + Py_ssize_t channel, float amount): """Multiply a colour channel of `stateimg` by a certain amount, and store the result in `img`. Overflow is clipped. @@ -80,16 +81,16 @@ def multiply(np.ndarray[np.uint8_t, ndim=3] img, Multiplication factor. """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] - cdef int k = channel + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] + cdef Py_ssize_t k = channel cdef float n = amount cdef float op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, l + cdef Py_ssize_t i, j, l with nogil: @@ -101,17 +102,16 @@ def multiply(np.ndarray[np.uint8_t, ndim=3] img, op_result = 0 else: pass - lut[l] = op_result + lut[l] = op_result for i from 0 <= i < height: for j from 0 <= j < width: img[i,j,k] = lut[stateimg[i,j,k]] -@cython.boundscheck(False) -def brightness(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - float factor, int offset): +def brightness(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + float factor, Py_ssize_t offset): """Modify the brightness of an image. 'factor' is multiplied to all channels, which are then added by 'amount'. Overflow is clipped. @@ -129,13 +129,13 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, k + cdef Py_ssize_t i, j, k with nogil: for k from 0 <= k < 256: @@ -146,7 +146,7 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, op_result = 0 else: pass - lut[k] = op_result + lut[k] = op_result for i from 0 <= i < height: for j from 0 <= j < width: @@ -155,27 +155,25 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.boundscheck(False) -@cython.cdivision(True) -def sigmoid_gamma(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def sigmoid_gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float alpha, float beta): - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] - cdef int i, j, k + cdef Py_ssize_t i, j, k cdef float c1 = 1 / (1 + exp(beta)) cdef float c2 = 1 / (1 + exp(beta - alpha)) - c1 - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] with nogil: # compute the lut for k from 0 <= k < 256: - lut[k] = (((1 / (1 + exp(beta - (k / 255.) * alpha))) + lut[k] = (((1 / (1 + exp(beta - (k / 255.) * alpha))) - c1) * 255 / c2) for i from 0 <= i < height: for j from 0 <= j < width: @@ -184,17 +182,16 @@ def sigmoid_gamma(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.boundscheck(False) -def gamma(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float gamma): - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, k + cdef Py_ssize_t i, j, k if gamma == 0: gamma = 0.00000000000000000001 @@ -204,7 +201,7 @@ def gamma(np.ndarray[np.uint8_t, ndim=3] img, # compute the lut for k from 0 <= k < 256: - lut[k] = ((pow((k / 255.), gamma) * 255)) + lut[k] = ((pow((k / 255.), gamma) * 255)) for i from 0 <= i < height: for j from 0 <= j < width: @@ -213,7 +210,6 @@ def gamma(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.cdivision(True) cdef void rgb_2_hsv(float* RGB, float* HSV) nogil: cdef float R, G, B, H, S, V, MAX, MIN R = RGB[0] @@ -277,11 +273,10 @@ cdef void rgb_2_hsv(float* RGB, float* HSV) nogil: HSV[2] = V -@cython.cdivision(True) cdef void hsv_2_rgb(float* HSV, float* RGB) nogil: cdef float H, S, V cdef float f, p, q, t, r, g, b - cdef int hi + cdef Py_ssize_t hi H = HSV[0] S = HSV[1] @@ -422,9 +417,8 @@ def py_rgb_2_hsv(R, G, B): return (H, S, V) -@cython.boundscheck(False) -def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def hsv_add(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float h_amt, float s_amt, float v_amt): """Modify the image color by specifying additive HSV Values. @@ -455,13 +449,13 @@ def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float HSV[3] cdef float RGB[3] - cdef int i, j + cdef Py_ssize_t i, j with nogil: for i from 0 <= i < height: @@ -483,14 +477,13 @@ def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, RGB[1] *= 255 RGB[2] *= 255 - img[i, j, 0] = RGB[0] - img[i, j, 1] = RGB[1] - img[i, j, 2] = RGB[2] + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] -@cython.boundscheck(False) -def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def hsv_multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float h_amt, float s_amt, float v_amt): """Modify the image color by specifying multiplicative HSV Values. @@ -525,13 +518,13 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float HSV[3] cdef float RGB[3] - cdef int i, j + cdef Py_ssize_t i, j with nogil: for i from 0 <= i < height: @@ -553,6 +546,6 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, RGB[1] *= 255 RGB[2] *= 255 - img[i, j, 0] = RGB[0] - img[i, j, 1] = RGB[1] - img[i, j, 2] = RGB[2] + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] diff --git a/skimage/io/_plugins/_histograms.pyx b/skimage/io/_plugins/_histograms.pyx index 41ec79a1..1f4344d1 100644 --- a/skimage/io/_plugins/_histograms.pyx +++ b/skimage/io/_plugins/_histograms.pyx @@ -1,7 +1,10 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -import cython +cimport numpy as cnp cdef inline float tri_max(float a, float b, float c): @@ -18,8 +21,7 @@ cdef inline float tri_max(float a, float b, float c): return c -@cython.boundscheck(False) -def histograms(np.ndarray[np.uint8_t, ndim=3] img, int nbins): +def histograms(cnp.ndarray[cnp.uint8_t, ndim=3] img, int nbins): '''Calculate the channel histograms of the current image. Parameters @@ -39,10 +41,7 @@ def histograms(np.ndarray[np.uint8_t, ndim=3] img, int nbins): ''' cdef int width = img.shape[1] cdef int height = img.shape[0] - cdef np.ndarray[np.int32_t, ndim=1] r - cdef np.ndarray[np.int32_t, ndim=1] g - cdef np.ndarray[np.int32_t, ndim=1] b - cdef np.ndarray[np.int32_t, ndim=1] v + cdef cnp.ndarray[cnp.int32_t, ndim=1] r, g, b, v r = np.zeros((nbins,), dtype=np.int32) g = np.zeros((nbins,), dtype=np.int32) diff --git a/skimage/io/_plugins/simpleitk_plugin.ini b/skimage/io/_plugins/simpleitk_plugin.ini new file mode 100644 index 00000000..75a6d995 --- /dev/null +++ b/skimage/io/_plugins/simpleitk_plugin.ini @@ -0,0 +1,3 @@ +[simpleitk] +description = Image reading and writing via SimpleITK +provides = imread, imsave diff --git a/skimage/io/_plugins/simpleitk_plugin.py b/skimage/io/_plugins/simpleitk_plugin.py new file mode 100644 index 00000000..90f7cbc6 --- /dev/null +++ b/skimage/io/_plugins/simpleitk_plugin.py @@ -0,0 +1,21 @@ +__all__ = ['imread', 'imsave'] + +try: + import SimpleITK as sitk +except ImportError: + raise ImportError("SimpleITK could not be found. " + "Please try " + " easy_install SimpleITK " + "or refer to " + " http://simpleitk.org/ " + "for further instructions.") + + +def imread(fname): + sitk_img = sitk.ReadImage(fname) + return sitk.GetArrayFromImage(sitk_img) + + +def imsave(fname, arr): + sitk_img = sitk.GetImageFromArray(arr, isVector=True) + sitk.WriteImage(sitk_img, fname) diff --git a/skimage/io/collection.py b/skimage/io/collection.py index 5a5dfff6..6aa9cfc6 100644 --- a/skimage/io/collection.py +++ b/skimage/io/collection.py @@ -105,6 +105,7 @@ class MultiImage(object): The two frames in this image can be shown with matplotlib: .. plot:: show_collection.py + """ def __init__(self, filename, conserve_memory=True, dtype=None): """Load a multi-img.""" diff --git a/skimage/io/tests/test_simpleitk.py b/skimage/io/tests/test_simpleitk.py new file mode 100644 index 00000000..4bb2cc23 --- /dev/null +++ b/skimage/io/tests/test_simpleitk.py @@ -0,0 +1,93 @@ +import os.path +import numpy as np +from numpy.testing import * +from numpy.testing.decorators import skipif + +from tempfile import NamedTemporaryFile + +from skimage import data_dir +from skimage.io import imread, imsave, use_plugin, reset_plugins + +try: + import SimpleITK as sitk + use_plugin('simpleitk') +except ImportError: + sitk_available = False +else: + sitk_available = True + + +def teardown(): + reset_plugins() + + +def setup_module(self): + """The effect of the `plugin.use` call may be overridden by later imports. + Call `use_plugin` directly before the tests to ensure that sitk is used. + + """ + try: + use_plugin('simpleitk') + except ImportError: + pass + + +@skipif(not sitk_available) +def test_imread_flatten(): + # a color image is flattened + img = imread(os.path.join(data_dir, 'color.png'), flatten=True) + assert img.ndim == 2 + assert img.dtype == np.float64 + img = imread(os.path.join(data_dir, 'camera.png'), flatten=True) + # check that flattening does not occur for an image that is grey already. + assert np.sctype2char(img.dtype) in np.typecodes['AllInteger'] + + +@skipif(not sitk_available) +def test_bilevel(): + expected = np.zeros((10, 10)) + expected[::2] = 255 + + img = imread(os.path.join(data_dir, 'checker_bilevel.png')) + assert_array_equal(img, expected) + + +@skipif(not sitk_available) +def test_imread_uint16(): + expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy')) + img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16.tif')) + assert np.issubdtype(img.dtype, np.uint16) + assert_array_almost_equal(img, expected) + + +@skipif(not sitk_available) +def test_imread_uint16_big_endian(): + expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy')) + img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16B.tif')) + assert_array_almost_equal(img, expected) + + +class TestSave: + def roundtrip(self, dtype, x): + f = NamedTemporaryFile(suffix='.mha') + fname = f.name + f.close() + imsave(fname, x) + y = imread(fname) + + assert_array_almost_equal(x, y) + + @skipif(not sitk_available) + def test_imsave_roundtrip(self): + for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]: + for dtype in (np.uint8, np.uint16, np.float32, np.float64): + x = np.ones(shape, dtype=dtype) * np.random.random(shape) + + if np.issubdtype(dtype, float): + yield self.roundtrip, dtype, x + else: + x = (x * 255).astype(dtype) + yield self.roundtrip, dtype, x + +if __name__ == "__main__": + run_module_suite() diff --git a/skimage/measure/_find_contours.pyx b/skimage/measure/_find_contours.pyx index 4f1b3cee..d05d9aa7 100644 --- a/skimage/measure/_find_contours.pyx +++ b/skimage/measure/_find_contours.pyx @@ -1,10 +1,11 @@ -# -*- python -*- -# cython: cdivision=True - +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -np.import_array() +cimport numpy as cnp + cdef inline double _get_fraction(double from_value, double to_value, double level): @@ -13,8 +14,8 @@ cdef inline double _get_fraction(double from_value, double to_value, return ((level - from_value) / (to_value - from_value)) -def iterate_and_store(np.ndarray[double, ndim=2] array, - double level, int vertex_connect_high): +def iterate_and_store(cnp.ndarray[double, ndim=2] array, + double level, Py_ssize_t vertex_connect_high): """Iterate across the given array in a marching-squares fashion, looking for segments that cross 'level'. If such a segment is found, its coordinates are added to a growing list of segments, @@ -27,7 +28,7 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, raise ValueError("Input array must be at least 2x2.") cdef list arc_list = [] - cdef int n + cdef Py_ssize_t n # The plan is to iterate a 2x2 square across the input array. This means # that the upper-left corner of the square needs to iterate across a @@ -39,17 +40,18 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, # index varies the fastest). # Current coords start at 0,0. - cdef int[2] coords + cdef Py_ssize_t[2] coords coords[0] = 0 coords[1] = 0 # Calculate the number of iterations we'll need - cdef int num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) + cdef Py_ssize_t num_square_steps = (array.shape[0] - 1) \ + * (array.shape[1] - 1) cdef unsigned char square_case = 0 cdef tuple top, bottom, left, right cdef double ul, ur, ll, lr - cdef int r0, r1, c0, c1 + cdef Py_ssize_t r0, r1, c0, c1 for n in range(num_square_steps): # There are sixteen different possible square types, diagramed below. diff --git a/skimage/measure/_moments.pyx b/skimage/measure/_moments.pyx index f84e14dd..145f6052 100644 --- a/skimage/measure/_moments.pyx +++ b/skimage/measure/_moments.pyx @@ -1,14 +1,16 @@ -#cython: boundscheck=False -#cython: wraparound=False #cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp -def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, - int order): - cdef int p, q, r, c - cdef np.ndarray[np.double_t, ndim=2] mu +def central_moments(cnp.ndarray[cnp.double_t, ndim=2] array, double cr, + double cc, int order): + cdef Py_ssize_t p, q, r, c + cdef cnp.ndarray[cnp.double_t, ndim=2] mu mu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): for q in range(order + 1): @@ -17,9 +19,10 @@ def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, mu[p,q] += array[r,c] * (r - cr) ** q * (c - cc) ** p return mu -def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): - cdef int p, q - cdef np.ndarray[np.double_t, ndim=2] nu + +def normalized_moments(cnp.ndarray[cnp.double_t, ndim=2] mu, int order): + cdef Py_ssize_t p, q + cdef cnp.ndarray[cnp.double_t, ndim=2] nu nu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): for q in range(order + 1): @@ -29,8 +32,9 @@ def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): nu[p,q] = np.nan return nu -def hu_moments(np.ndarray[np.double_t, ndim=2] nu): - cdef np.ndarray[np.double_t, ndim=1] hu = np.zeros((7,), 'double') + +def hu_moments(cnp.ndarray[cnp.double_t, ndim=2] nu): + cdef cnp.ndarray[cnp.double_t, ndim=1] hu = np.zeros((7,), 'double') cdef double t0 = nu[3,0] + nu[1,2] cdef double t1 = nu[2,1] + nu[0,3] cdef double q0 = t0 * t0 diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 8185bf67..c1396670 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -49,7 +49,7 @@ def test_bbox(): def test_central_moments(): mu = regionprops(SAMPLE, ['CentralMoments'])[0]['CentralMoments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(mu[0,2], 436.00000000000045) # different from OpenCV results, bug in OpenCV assert_almost_equal(mu[0,3], -737.333333333333) @@ -198,7 +198,7 @@ def test_minor_axis_length(): def test_moments(): m = regionprops(SAMPLE, ['Moments'])[0]['Moments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(m[0,0], 72.0) assert_almost_equal(m[0,1], 408.0) assert_almost_equal(m[0,2], 2748.0) @@ -213,7 +213,7 @@ def test_moments(): def test_normalized_moments(): nu = regionprops(SAMPLE, ['NormalizedMoments'])[0]['NormalizedMoments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(nu[0,2], 0.08410493827160502) assert_almost_equal(nu[1,1], -0.016846707818929982) assert_almost_equal(nu[1,2], -0.002899800614433943) diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index d4c775eb..044fcf89 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -7,3 +7,4 @@ from .watershed import watershed, is_local_maximum from ._skeletonize import skeletonize, medial_axis from .convex_hull import convex_hull_image from .greyreconstruct import reconstruction +from .misc import remove_small_objects diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index dc426900..7298e1ed 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -1,9 +1,13 @@ -# -*- python -*- - -cimport numpy as np +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): +cimport numpy as cnp + + +def possible_hull(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, mode="c"] img): """Return positions of pixels that possibly belong to the convex hull. Parameters @@ -13,47 +17,44 @@ def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): Returns ------- - coords : ndarray (N, 2) + coords : ndarray (cols, 2) The ``(row, column)`` coordinates of all pixels that possibly belong to the convex hull. """ - cdef int i, j, k - cdef unsigned int M, N - - M = img.shape[0] - N = img.shape[1] + cdef Py_ssize_t r, c + cdef Py_ssize_t rows = img.shape[0] + cdef Py_ssize_t cols = img.shape[1] - # Output: M storage slots for left boundary pixels - # N storage slots for top boundary pixels - # M storage slots for right boundary pixels - # N storage slots for bottom boundary pixels - cdef np.ndarray[dtype=np.int_t, ndim=2] nonzero = \ - np.ones((2 * (M + N), 2), dtype=np.int) - nonzero *= -1 + # Output: rows storage slots for left boundary pixels + # cols storage slots for top boundary pixels + # rows storage slots for right boundary pixels + # cols storage slots for bottom boundary pixels + cdef cnp.ndarray[dtype=cnp.intp_t, ndim=2] nonzero = \ + np.ones((2 * (rows + cols), 2), dtype=np.intp) + nonzero *= -1 - k = 0 - for i in range(M): - for j in range(N): - if img[i, j] != 0: + for r in range(rows): + for c in range(cols): + if img[r, c] != 0: # Left check - if nonzero[i, 1] == -1: - nonzero[i, 0] = i - nonzero[i, 1] = j + if nonzero[r, 1] == -1: + nonzero[r, 0] = r + nonzero[r, 1] = c # Right check - elif nonzero[M + N + i, 1] < j: - nonzero[M + N + i, 0] = i - nonzero[M + N + i, 1] = j + elif nonzero[rows + cols + r, 1] < c: + nonzero[rows + cols + r, 0] = r + nonzero[rows + cols + r, 1] = c # Top check - if nonzero[M + j, 1] == -1: - nonzero[M + j, 0] = i - nonzero[M + j, 1] = j + if nonzero[rows + c, 1] == -1: + nonzero[rows + c, 0] = r + nonzero[rows + c, 1] = c # Bottom check - elif nonzero[2 * M + N + j, 0] < i: - nonzero[2 * M + N + j, 0] = i - nonzero[2 * M + N + j, 1] = j - + elif nonzero[2 * rows + cols + c, 0] < r: + nonzero[2 * rows + cols + c, 0] = r + nonzero[2 * rows + cols + c, 1] = c + return nonzero[nonzero[:, 0] != -1] diff --git a/skimage/morphology/_pnpoly.pyx b/skimage/morphology/_pnpoly.pyx index 7deb6a50..f32778cc 100644 --- a/skimage/morphology/_pnpoly.pyx +++ b/skimage/morphology/_pnpoly.pyx @@ -1,7 +1,10 @@ -# -*- python -*- - -cimport numpy as np +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np + +cimport numpy as cnp from skimage._shared.geometry cimport point_in_polygon, points_in_polygon @@ -26,23 +29,24 @@ def grid_points_inside_poly(shape, verts): True where the grid falls inside the polygon. """ - cdef np.ndarray[np.double_t, ndim=1, mode="c"] vx, vy + cdef cnp.ndarray[cnp.double_t, ndim=1, mode="c"] vx, vy verts = np.asarray(verts) vx = verts[:, 0].astype(np.double) vy = verts[:, 1].astype(np.double) - cdef int V = vx.shape[0] + cdef Py_ssize_t V = vx.shape[0] - cdef int M = shape[0] - cdef int N = shape[1] - cdef int m, n + cdef Py_ssize_t M = shape[0] + cdef Py_ssize_t N = shape[1] + cdef Py_ssize_t m, n - cdef np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] out = \ + cdef cnp.ndarray[dtype=cnp.uint8_t, ndim=2, mode="c"] out = \ np.zeros((M, N), dtype=np.uint8) for m in range(M): for n in range(N): - out[m, n] = point_in_polygon(V, vx.data, vy.data, m, n) + out[m, n] = point_in_polygon(V, vx.data, vy.data, + m, n) return out.view(bool) @@ -64,7 +68,7 @@ def points_inside_poly(points, verts): True if corresponding point is inside the polygon. """ - cdef np.ndarray[np.double_t, ndim=1, mode="c"] x, y, vx, vy + cdef cnp.ndarray[cnp.double_t, ndim=1, mode="c"] x, y, vx, vy points = np.asarray(points) verts = np.asarray(verts) @@ -75,12 +79,12 @@ def points_inside_poly(points, verts): vx = verts[:, 0].astype(np.double) vy = verts[:, 1].astype(np.double) - cdef np.ndarray[np.uint8_t, ndim=1] out = \ - np.zeros(x.shape[0], dtype=np.uint8) + cdef cnp.ndarray[cnp.uint8_t, ndim=1] out = \ + np.zeros(x.shape[0], dtype=np.uint8) points_in_polygon(vx.shape[0], vx.data, vy.data, - x.shape[0], x.data, y.data, - out.data) + x.shape[0], x.data, y.data, + out.data) return out.astype(bool) diff --git a/skimage/morphology/_skeletonize.py b/skimage/morphology/_skeletonize.py index 04a65da6..b48beb86 100644 --- a/skimage/morphology/_skeletonize.py +++ b/skimage/morphology/_skeletonize.py @@ -277,8 +277,8 @@ def medial_axis(image, mask=None, return_distance=False): i, j = np.mgrid[0:image.shape[0], 0:image.shape[1]] result = masked_image.copy() distance = distance[result] - i = np.ascontiguousarray(i[result], np.int32) - j = np.ascontiguousarray(j[result], np.int32) + i = np.ascontiguousarray(i[result], np.intp) + j = np.ascontiguousarray(j[result], np.intp) result = np.ascontiguousarray(result, np.uint8) # Determine the order in which pixels are processed. diff --git a/skimage/morphology/_skeletonize_cy.pyx b/skimage/morphology/_skeletonize_cy.pyx index ff5fcdf2..13e303d4 100644 --- a/skimage/morphology/_skeletonize_cy.pyx +++ b/skimage/morphology/_skeletonize_cy.pyx @@ -1,3 +1,8 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + ''' Originally part of CellProfiler, code licensed under both GPL and BSD licenses. Website: http://www.cellprofiler.org @@ -10,21 +15,20 @@ Original author: Lee Kamentsky ''' import numpy as np -cimport numpy as np -cimport cython + +cimport numpy as cnp -@cython.boundscheck(False) -def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, - negative_indices=False, mode='c'] result, - np.ndarray[dtype=np.int32_t, ndim=1, - negative_indices=False, mode='c'] i, - np.ndarray[dtype=np.int32_t, ndim=1, - negative_indices=False, mode='c'] j, - np.ndarray[dtype=np.int32_t, ndim=1, - negative_indices=False, mode='c'] order, - np.ndarray[dtype=np.uint8_t, ndim=1, - negative_indices=False, mode='c'] table): +def _skeletonize_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] result, + cnp.ndarray[dtype=cnp.intp_t, ndim=1, + negative_indices=False, mode='c'] i, + cnp.ndarray[dtype=cnp.intp_t, ndim=1, + negative_indices=False, mode='c'] j, + cnp.ndarray[dtype=cnp.int32_t, ndim=1, + negative_indices=False, mode='c'] order, + cnp.ndarray[dtype=cnp.uint8_t, ndim=1, + negative_indices=False, mode='c'] table): """ Inner loop of skeletonize function @@ -37,13 +41,13 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, i, j : ndarrays The coordinates of each foreground pixel in the image - + order : ndarray The index of each pixel, in the order of processing (order[0] is the first pixel to process, etc.) - + table : ndarray - The 512-element lookup table of values after transformation + The 512-element lookup table of values after transformation (whether to keep or not each configuration in a binary 3x3 array) Notes @@ -55,15 +59,15 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, the quench-line of the brushfire will be evaluated later than a point closer to the edge. - Note that the neighbourhood of a pixel may evolve before the loop - arrives at this pixel. This is why it is possible to compute the + Note that the neighbourhood of a pixel may evolve before the loop + arrives at this pixel. This is why it is possible to compute the skeleton in only one pass, thanks to an adapted ordering of the pixels. """ cdef: - np.int32_t accumulator - np.int32_t index, order_index - np.int32_t ii, jj + cnp.int32_t accumulator + Py_ssize_t index, order_index + Py_ssize_t ii, jj for index in range(order.shape[0]): accumulator = 16 @@ -92,9 +96,10 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, # Assign the value of table corresponding to the configuration result[ii, jj] = table[accumulator] -@cython.boundscheck(False) -def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, - negative_indices=False, mode='c'] image): + + +def _table_lookup_index(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] image): """ Return an index into a table per pixel of a binary image @@ -110,27 +115,27 @@ def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, 256 128 64 32 16 8 4 2 1 - + but this runs about twice as fast because of inlining and the hardwired kernel. """ cdef: - np.ndarray[dtype=np.int32_t, ndim=2, - negative_indices=False, mode='c'] indexer - np.int32_t *p_indexer - np.uint8_t *p_image - np.int32_t i_stride - np.int32_t i_shape - np.int32_t j_shape - np.int32_t i - np.int32_t j - np.int32_t offset + cnp.ndarray[dtype=cnp.int32_t, ndim=2, + negative_indices=False, mode='c'] indexer + cnp.int32_t *p_indexer + cnp.uint8_t *p_image + Py_ssize_t i_stride + Py_ssize_t i_shape + Py_ssize_t j_shape + Py_ssize_t i + Py_ssize_t j + Py_ssize_t offset i_shape = image.shape[0] j_shape = image.shape[1] indexer = np.zeros((i_shape, j_shape), np.int32) - p_indexer = indexer.data - p_image = image.data + p_indexer = indexer.data + p_image = image.data i_stride = image.strides[0] assert i_shape >= 3 and j_shape >= 3, \ "Please use the slow method for arrays < 3x3" diff --git a/skimage/morphology/_watershed.pyx b/skimage/morphology/_watershed.pyx index c86d8744..122f0262 100644 --- a/skimage/morphology/_watershed.pyx +++ b/skimage/morphology/_watershed.pyx @@ -9,39 +9,33 @@ All rights reserved. Original author: Lee Kamentsky """ - -cdef extern from "numpy/arrayobject.h": - cdef void import_array() -import_array() - import numpy as np cimport numpy as np cimport cython -DTYPE_INT32 = np.int32 + ctypedef np.int32_t DTYPE_INT32_t DTYPE_BOOL = np.bool ctypedef np.int8_t DTYPE_BOOL_t + include "heap_watershed.pxi" + @cython.boundscheck(False) -def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] image, - np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, - mode='c'] pq, - DTYPE_INT32_t age, - np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, - mode='c'] structure, - DTYPE_INT32_t ndim, - np.ndarray[DTYPE_BOOL_t, ndim=1, negative_indices=False, - mode='c'] mask, - np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] image_shape, - np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] output): +def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, + mode='c'] image, + np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, + mode='c'] pq, + Py_ssize_t age, + np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, + mode='c'] structure, + np.ndarray[DTYPE_BOOL_t, ndim=1, negative_indices=False, + mode='c'] mask, + np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, + mode='c'] output): """Do heavy lifting of watershed algorithm - + Parameters ---------- @@ -58,20 +52,17 @@ def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, in a flattened array. The remaining elements are the offsets from the point to its neighbor in the various dimensions - ndim - # of dimensions in the image mask - numpy boolean (char) array indicating which pixels to consider and which to ignore. Also flattened. - image_shape - the dimensions of the image, for boundary checking, - a numpy array of np.int32 output - put the image labels in here """ cdef Heapitem elem cdef Heapitem new_elem - cdef DTYPE_INT32_t nneighbors = structure.shape[0] - cdef DTYPE_INT32_t i = 0 - cdef DTYPE_INT32_t index = 0 - cdef DTYPE_INT32_t old_index = 0 - cdef DTYPE_INT32_t max_index = image.shape[0] + cdef Py_ssize_t nneighbors = structure.shape[0] + cdef Py_ssize_t i = 0 + cdef Py_ssize_t index = 0 + cdef Py_ssize_t old_index = 0 + cdef Py_ssize_t max_index = image.shape[0] cdef Heap *hp = heap_from_numpy2() diff --git a/skimage/morphology/ccomp.pxd b/skimage/morphology/ccomp.pxd index 0b431832..569921fa 100644 --- a/skimage/morphology/ccomp.pxd +++ b/skimage/morphology/ccomp.pxd @@ -1,10 +1,10 @@ """Export fast union find in Cython""" -cimport numpy as np +cimport numpy as cnp -DTYPE = np.int -ctypedef np.int_t DTYPE_t +DTYPE = cnp.intp +ctypedef cnp.intp_t DTYPE_t -cdef DTYPE_t find_root(np.int_t *forest, np.int_t n) -cdef set_root(np.int_t *forest, np.int_t n, np.int_t root) -cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m) -cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node) +cdef DTYPE_t find_root(DTYPE_t *forest, DTYPE_t n) +cdef set_root(DTYPE_t *forest, DTYPE_t n, DTYPE_t root) +cdef join_trees(DTYPE_t *forest, DTYPE_t n, DTYPE_t m) +cdef link_bg(DTYPE_t *forest, DTYPE_t n, DTYPE_t *background_node) diff --git a/skimage/morphology/ccomp.pyx b/skimage/morphology/ccomp.pyx index 6a4fb1f2..1db78a6d 100644 --- a/skimage/morphology/ccomp.pyx +++ b/skimage/morphology/ccomp.pyx @@ -1,8 +1,11 @@ -# -*- python -*- #cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp """ See also: @@ -23,23 +26,25 @@ See also: # Tree operations implemented by an array as described in Wu et al. # The term "forest" is used to indicate an array that stores one or more trees -DTYPE = np.int +DTYPE = np.intp -cdef DTYPE_t find_root(np.int_t *forest, np.int_t n): + +cdef DTYPE_t find_root(DTYPE_t *forest, DTYPE_t n): """Find the root of node n. """ - cdef np.int_t root = n + cdef DTYPE_t root = n while (forest[root] < root): root = forest[root] return root -cdef set_root(np.int_t *forest, np.int_t n, np.int_t root): + +cdef set_root(DTYPE_t *forest, DTYPE_t n, DTYPE_t root): """ Set all nodes on a path to point to new_root. """ - cdef np.int_t j + cdef DTYPE_t j while (forest[n] < n): j = forest[n] forest[n] = root @@ -48,12 +53,12 @@ cdef set_root(np.int_t *forest, np.int_t n, np.int_t root): forest[n] = root -cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m): +cdef join_trees(DTYPE_t *forest, DTYPE_t n, DTYPE_t m): """Join two trees containing nodes n and m. """ - cdef np.int_t root = find_root(forest, n) - cdef np.int_t root_m + cdef DTYPE_t root = find_root(forest, n) + cdef DTYPE_t root_m if (n != m): root_m = find_root(forest, m) @@ -64,7 +69,8 @@ cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m): set_root(forest, n, root) set_root(forest, m, root) -cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node): + +cdef link_bg(DTYPE_t *forest, DTYPE_t n, DTYPE_t *background_node): """ Link a node to the background node. @@ -76,7 +82,7 @@ cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node): # Connected components search as described in Fiorio et al. -def label(input, np.int_t neighbors=8, np.int_t background=-1): +def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1): """Label connected regions of an integer array. Two pixels are connected when they are neighbors and have the same value. @@ -134,21 +140,21 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): [-1 -1 -1]] """ - cdef np.int_t rows = input.shape[0] - cdef np.int_t cols = input.shape[1] + cdef DTYPE_t rows = input.shape[0] + cdef DTYPE_t cols = input.shape[1] - cdef np.ndarray[DTYPE_t, ndim=2] data = np.array(input, copy=True, - dtype=DTYPE) - cdef np.ndarray[DTYPE_t, ndim=2] forest + cdef cnp.ndarray[DTYPE_t, ndim=2] data = np.array(input, copy=True, + dtype=DTYPE) + cdef cnp.ndarray[DTYPE_t, ndim=2] forest forest = np.arange(data.size, dtype=DTYPE).reshape((rows, cols)) - cdef np.int_t *forest_p = forest.data - cdef np.int_t *data_p = data.data + cdef DTYPE_t *forest_p = forest.data + cdef DTYPE_t *data_p = data.data - cdef np.int_t i, j + cdef DTYPE_t i, j - cdef np.int_t background_node = -999 + cdef DTYPE_t background_node = -999 if neighbors != 4 and neighbors != 8: raise ValueError('Neighbors must be either 4 or 8.') @@ -197,7 +203,7 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): # Label output - cdef np.int_t ctr = 0 + cdef DTYPE_t ctr = 0 for i in range(rows): for j in range(cols): if (i*cols + j) == background_node: @@ -208,4 +214,8 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): else: data[i, j] = data_p[forest[i, j]] - return data + # Work around a bug in ndimage's type checking on 32-bit platforms + if data.dtype == np.int32: + return data.view(np.int32) + else: + return data diff --git a/skimage/morphology/cmorph.pyx b/skimage/morphology/cmorph.pyx index 9b8b3a27..a09a39a3 100644 --- a/skimage/morphology/cmorph.pyx +++ b/skimage/morphology/cmorph.pyx @@ -13,13 +13,13 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] - cdef int srows = selem.shape[0] - cdef int scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef int centre_r = int(selem.shape[0] / 2) - shift_y - cdef int centre_c = int(selem.shape[1] / 2) - shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -30,11 +30,11 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, cdef np.uint8_t* out_data = out.data cdef np.uint8_t* image_data = image.data - cdef int r, c, rr, cc, s, value, local_max + cdef Py_ssize_t r, c, rr, cc, s, value, local_max - cdef int selem_num = np.sum(selem != 0) - cdef int* sr = malloc(selem_num * sizeof(int)) - cdef int* sc = malloc(selem_num * sizeof(int)) + cdef Py_ssize_t selem_num = np.sum(selem != 0) + cdef Py_ssize_t* sr = malloc(selem_num * sizeof(Py_ssize_t)) + cdef Py_ssize_t* sc = malloc(selem_num * sizeof(Py_ssize_t)) s = 0 for r in range(srows): @@ -68,13 +68,13 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] - cdef int srows = selem.shape[0] - cdef int scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef int centre_r = int(selem.shape[0] / 2) - shift_y - cdef int centre_c = int(selem.shape[1] / 2) - shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -87,9 +87,9 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, cdef int r, c, rr, cc, s, value, local_min - cdef int selem_num = np.sum(selem != 0) - cdef int* sr = malloc(selem_num * sizeof(int)) - cdef int* sc = malloc(selem_num * sizeof(int)) + cdef Py_ssize_t selem_num = np.sum(selem != 0) + cdef Py_ssize_t* sr = malloc(selem_num * sizeof(Py_ssize_t)) + cdef Py_ssize_t* sc = malloc(selem_num * sizeof(Py_ssize_t)) s = 0 for r in range(srows): diff --git a/skimage/morphology/heap_general.pxi b/skimage/morphology/heap_general.pxi index a113b98e..67b21fa6 100644 --- a/skimage/morphology/heap_general.pxi +++ b/skimage/morphology/heap_general.pxi @@ -10,21 +10,18 @@ All rights reserved. Original author: Lee Kamentsky """ -cdef extern from "stdlib.h": - ctypedef unsigned long size_t - void free(void *ptr) - void *malloc(size_t size) - void *realloc(void *ptr, size_t size) +from libc.stdlib cimport free, malloc, realloc + cdef struct Heap: - unsigned int items - unsigned int space + Py_ssize_t items + Py_ssize_t space Heapitem *data Heapitem **ptrs cdef inline Heap *heap_from_numpy2(): - cdef unsigned int k - cdef Heap *heap + cdef Py_ssize_t k + cdef Heap *heap heap = malloc(sizeof (Heap)) heap.items = 0 heap.space = 1000 @@ -39,7 +36,7 @@ cdef inline void heap_done(Heap *heap): free(heap.ptrs) free(heap) -cdef inline void swap(unsigned int a, unsigned int b, Heap *h): +cdef inline void swap(Py_ssize_t a, Py_ssize_t b, Heap *h): h.ptrs[a], h.ptrs[b] = h.ptrs[b], h.ptrs[a] @@ -47,13 +44,13 @@ cdef inline void swap(unsigned int a, unsigned int b, Heap *h): # heappop - inlined # # pop an element off the heap, maintaining heap invariant -# +# # Note: heap ordering is the same as python heapq, i.e., smallest first. ###################################################### -cdef inline void heappop(Heap *heap, - Heapitem *dest): - cdef unsigned int i, smallest, l, r # heap indices - +cdef inline void heappop(Heap *heap, Heapitem *dest): + + cdef Py_ssize_t i, smallest, l, r # heap indices + # # Start by copying the first element to the destination # @@ -76,10 +73,10 @@ cdef inline void heappop(Heap *heap, smallest = i while True: # loop invariant here: smallest == i - + # find smallest of (i, l, r), and swap it to i's position if necessary - l = i*2+1 #__left(i) - r = i*2+2 #__right(i) + l = i * 2 + 1 #__left(i) + r = i * 2 + 2 #__right(i) if l < heap.items: if smaller(heap.ptrs[l], heap.ptrs[i]): smallest = l @@ -88,13 +85,14 @@ cdef inline void heappop(Heap *heap, else: # this is unnecessary, but trims 0.04 out of 0.85 seconds... break - # the element at i is smaller than either of its children, heap invariant restored. + # the element at i is smaller than either of its children, heap + # invariant restored. if smallest == i: break # swap swap(i, smallest, heap) i = smallest - + ################################################## # heappush - inlined # @@ -102,34 +100,36 @@ cdef inline void heappop(Heap *heap, # # Note: heap ordering is the same as python heapq, i.e., smallest first. ################################################## -cdef inline void heappush(Heap *heap, - Heapitem *new_elem): - cdef unsigned int child = heap.items - cdef unsigned int parent - cdef unsigned int k - cdef Heapitem *new_data +cdef inline void heappush(Heap *heap, Heapitem *new_elem): - # grow if necessary - if heap.items == heap.space: + cdef Py_ssize_t child = heap.items + cdef Py_ssize_t parent + cdef Py_ssize_t k + cdef Heapitem *new_data + + # grow if necessary + if heap.items == heap.space: heap.space = heap.space * 2 - new_data = realloc( heap.data, (heap.space * sizeof(Heapitem))) - heap.ptrs = realloc( heap.ptrs, (heap.space * sizeof(Heapitem *))) + new_data = realloc(heap.data, + (heap.space * sizeof(Heapitem))) + heap.ptrs = realloc(heap.ptrs, + (heap.space * sizeof(Heapitem *))) for k in range(heap.items): heap.ptrs[k] = new_data + (heap.ptrs[k] - heap.data) for k in range(heap.items, heap.space): heap.ptrs[k] = new_data + k heap.data = new_data - # insert new data at child - heap.ptrs[child][0] = new_elem[0] - heap.items += 1 + # insert new data at child + heap.ptrs[child][0] = new_elem[0] + heap.items += 1 - # restore heap invariant, all parents <= children - while child>0: - parent = (child + 1) / 2 - 1 # __parent(i) - - if smaller(heap.ptrs[child], heap.ptrs[parent]): - swap(parent, child, heap) - child = parent - else: - break + # restore heap invariant, all parents <= children + while child > 0: + parent = (child + 1) / 2 - 1 # __parent(i) + + if smaller(heap.ptrs[child], heap.ptrs[parent]): + swap(parent, child, heap) + child = parent + else: + break diff --git a/skimage/morphology/heap_watershed.pxi b/skimage/morphology/heap_watershed.pxi index ea66da26..07b29f5c 100644 --- a/skimage/morphology/heap_watershed.pxi +++ b/skimage/morphology/heap_watershed.pxi @@ -9,18 +9,19 @@ All rights reserved. Original author: Lee Kamentsky """ -import numpy as np -cimport numpy as np -cimport cython +cimport numpy as cnp + cdef struct Heapitem: - np.int32_t value - np.int32_t age - np.int32_t index + cnp.int32_t value + cnp.int32_t age + Py_ssize_t index + cdef inline int smaller(Heapitem *a, Heapitem *b): if a.value <> b.value: - return a.value < b.value + return a.value < b.value return a.age < b.age + include "heap_general.pxi" diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py new file mode 100644 index 00000000..db0136cd --- /dev/null +++ b/skimage/morphology/misc.py @@ -0,0 +1,85 @@ +import numpy as np +import scipy.ndimage as nd + + +def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): + """Remove connected components smaller than the specified size. + + Parameters + ---------- + ar : ndarray (arbitrary shape, int or bool type) + The array containing the connected components of interest. If the array + type is int, it is assumed that it contains already-labeled objects. + The ints must be non-negative. + min_size : int, optional (default: 64) + The smallest allowable connected component size. + connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1) + The connectivity defining the neighborhood of a pixel. + in_place : bool, optional (default: False) + If `True`, remove the connected components in the input array itself. + Otherwise, make a copy. + + Raises + ------ + TypeError + If the input array is of an invalid type, such as float or string. + ValueError + If the input array contains negative values. + + Returns + ------- + out : ndarray, same shape and type as input `ar` + The input array with small connected components removed. + + Examples + -------- + >>> from skimage import morphology + >>> from scipy import ndimage as nd + >>> a = np.array([[0, 0, 0, 1, 0], + ... [1, 1, 1, 0, 0], + ... [1, 1, 1, 0, 1]], bool) + >>> b = morphology.remove_small_connected_components(a, 6) + >>> b + array([[False, False, False, False, False], + [ True, True, True, False, False], + [ True, True, True, False, False]], dtype=bool) + >>> c = morphology.remove_small_connected_components(a, 7, connectivity=2) + >>> c + array([[False, False, False, True, False], + [ True, True, True, False, False], + [ True, True, True, False, False]], dtype=bool) + >>> d = morphology.remove_small_connected_components(a, 6, in_place=True) + >>> d is a + True + """ + # Should use `issubdtype` for bool below, but there's a bug in numpy 1.7 + if not (ar.dtype == bool or np.issubdtype(ar.dtype, int)): + raise TypeError("Only bool or integer image types are supported. " + "Got %s." % ar.dtype) + + if in_place: + out = ar + else: + out = ar.copy() + + if min_size == 0: # shortcut for efficiency + return out + + if out.dtype == bool: + selem = nd.generate_binary_structure(ar.ndim, connectivity) + ccs = nd.label(ar, selem)[0] + else: + ccs = out + + try: + component_sizes = np.bincount(ccs.ravel()) + except ValueError: + raise ValueError("Negative value labels are not supported. Try " + "relabeling the input with `scipy.ndimage.label` or " + "`skimage.morphology.label`.") + + too_small = component_sizes < min_size + too_small_mask = too_small[ccs] + out[too_small_mask] = 0 + + return out diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py new file mode 100644 index 00000000..a5ab7ba9 --- /dev/null +++ b/skimage/morphology/tests/test_misc.py @@ -0,0 +1,56 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_equal, assert_raises +from skimage.morphology import remove_small_objects + +test_image = np.array([[0, 0, 0, 1, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 1]], bool) + + +def test_one_connectivity(): + expected = np.array([[0, 0, 0, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], bool) + observed = remove_small_objects(test_image, min_size=6) + assert_array_equal(observed, expected) + + +def test_two_connectivity(): + expected = np.array([[0, 0, 0, 1, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], bool) + observed = remove_small_objects(test_image, min_size=7, connectivity=2) + assert_array_equal(observed, expected) + + +def test_in_place(): + observed = remove_small_objects(test_image, min_size=6, in_place=True) + assert_equal(observed is test_image, True, + "remove_small_objects in_place argument failed.") + + +def test_labeled_image(): + labeled_image = np.array([[2, 2, 2, 0, 1], + [2, 2, 2, 0, 1], + [2, 0, 0, 0, 0], + [0, 0, 3, 3, 3]], dtype=int) + expected = np.array([[2, 2, 2, 0, 0], + [2, 2, 2, 0, 0], + [2, 0, 0, 0, 0], + [0, 0, 3, 3, 3]], dtype=int) + observed = remove_small_objects(labeled_image, min_size=3) + assert_array_equal(observed, expected) + + +def test_float_input(): + float_test = np.random.rand(5, 5) + assert_raises(TypeError, remove_small_objects, float_test) + + +def test_negative_input(): + negative_int = np.random.randint(-4, -1, size=(5, 5)) + assert_raises(ValueError, remove_small_objects, negative_int) + + +if __name__ == "__main__": + np.testing.run_module_suite() diff --git a/skimage/morphology/watershed.py b/skimage/morphology/watershed.py index b6f2a4fb..fbd63281 100644 --- a/skimage/morphology/watershed.py +++ b/skimage/morphology/watershed.py @@ -214,9 +214,7 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None): c_mask = c_mask.astype(np.int8).flatten() _watershed.watershed(c_image.flatten(), pq, age, c, - c_image.ndim, c_mask, - np.array(c_image.shape, np.int32), c_output) c_output = c_output.reshape(c_image.shape)[[slice(1, -1, None)] * image.ndim] diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index efae45a4..b525a0d9 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -1,16 +1,18 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np import scipy -cimport cython +cimport cython +cimport numpy as cnp from skimage.morphology.ccomp cimport find_root, join_trees from ..util import img_as_float -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): + +def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20): """Felzenszwalb's efficient graph based segmentation for a single channel. Produces an oversegmentation of a 2d image using a fast, minimum spanning @@ -49,31 +51,31 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): down_cost = np.abs((image[:, 1:] - image[:, :-1])) dright_cost = np.abs((image[1:, 1:] - image[:-1, :-1])) uright_cost = np.abs((image[1:, :-1] - image[:-1, 1:])) - cdef np.ndarray[np.float_t, ndim=1] costs = np.hstack([right_cost.ravel(), + cdef cnp.ndarray[cnp.float_t, ndim=1] costs = np.hstack([right_cost.ravel(), down_cost.ravel(), dright_cost.ravel(), uright_cost.ravel()]).astype(np.float) # compute edges between pixels: height, width = image.shape[:2] - cdef np.ndarray[np.int_t, ndim=2] segments \ - = np.arange(width * height, dtype=np.int).reshape(height, width) + cdef cnp.ndarray[cnp.intp_t, ndim=2] segments \ + = np.arange(width * height, dtype=np.intp).reshape(height, width) right_edges = np.c_[segments[1:, :].ravel(), segments[:-1, :].ravel()] down_edges = np.c_[segments[:, 1:].ravel(), segments[:, :-1].ravel()] dright_edges = np.c_[segments[1:, 1:].ravel(), segments[:-1, :-1].ravel()] uright_edges = np.c_[segments[:-1, 1:].ravel(), segments[1:, :-1].ravel()] - cdef np.ndarray[np.int_t, ndim=2] edges \ + cdef cnp.ndarray[cnp.intp_t, ndim=2] edges \ = np.vstack([right_edges, down_edges, dright_edges, uright_edges]) # initialize data structures for segment size # and inner cost, then start greedy iteration over edges. edge_queue = np.argsort(costs) edges = np.ascontiguousarray(edges[edge_queue]) costs = np.ascontiguousarray(costs[edge_queue]) - cdef np.int_t *segments_p = segments.data - cdef np.int_t *edges_p = edges.data - cdef np.float_t *costs_p = costs.data - cdef np.ndarray[np.int_t, ndim=1] segment_size \ - = np.ones(width * height, dtype=np.int) + cdef cnp.intp_t *segments_p = segments.data + cdef cnp.intp_t *edges_p = edges.data + cdef cnp.float_t *costs_p = costs.data + cdef cnp.ndarray[cnp.intp_t, ndim=1] segment_size \ + = np.ones(width * height, dtype=np.intp) # inner cost of segments - cdef np.ndarray[np.float_t, ndim=1] cint = np.zeros(width * height) + cdef cnp.ndarray[cnp.float_t, ndim=1] cint = np.zeros(width * height) cdef int seg0, seg1, seg_new, e cdef float cost, inner_cost0, inner_cost1 # set costs_p back one. we increase it before we use it @@ -96,7 +98,7 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): cint[seg_new] = costs_p[0] # postprocessing to remove small segments - edges_p = edges.data + edges_p = edges.data for e in range(costs.size): seg0 = find_root(segments_p, edges_p[0]) seg1 = find_root(segments_p, edges_p[1]) diff --git a/skimage/segmentation/_quickshift.pyx b/skimage/segmentation/_quickshift.pyx index b465eb08..cc649e8f 100644 --- a/skimage/segmentation/_quickshift.pyx +++ b/skimage/segmentation/_quickshift.pyx @@ -1,18 +1,18 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -cimport cython -from libc.math cimport exp, sqrt - -from itertools import product from scipy import ndimage +from itertools import product + +cimport numpy as cnp +from libc.math cimport exp, sqrt from ..util import img_as_float from ..color import rgb2lab -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, return_tree=False, sigma=0, convert2lab=True, random_seed=None): """Segments image using quickshift clustering in Color-(x,y) space. @@ -69,7 +69,7 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, image = rgb2lab(image) image = ndimage.gaussian_filter(img_as_float(image), [sigma, sigma, 0]) - cdef np.ndarray[dtype=np.float_t, ndim=3, mode="c"] image_c \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=3, mode="c"] image_c \ = np.ascontiguousarray(image) * ratio random_state = np.random.RandomState(random_seed) @@ -85,18 +85,19 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, raise ValueError("Sigma should be >= 1") cdef int w = int(3 * kernel_size) - cdef int height = image_c.shape[0] - cdef int width = image_c.shape[1] - cdef int channels = image_c.shape[2] + cdef Py_ssize_t height = image_c.shape[0] + cdef Py_ssize_t width = image_c.shape[1] + cdef Py_ssize_t channels = image_c.shape[2] cdef double current_density, closest, dist - cdef int r, c, r_, c_, channel + cdef Py_ssize_t r, c, r_, c_, channel, r_min, c_min - cdef np.float_t* image_p = image_c.data - cdef np.float_t* current_pixel_p = image_p + cdef cnp.float_t* image_p = image_c.data + cdef cnp.float_t* current_pixel_p = image_p - cdef np.ndarray[dtype=np.float_t, ndim=2] densities \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] densities \ = np.zeros((height, width)) + # compute densities for r in range(height): for c in range(width): @@ -116,10 +117,11 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, densities += random_state.normal(scale=0.00001, size=(height, width)) # default parent to self: - cdef np.ndarray[dtype=np.int_t, ndim=2] parent \ + cdef cnp.ndarray[dtype=cnp.int_t, ndim=2] parent \ = np.arange(width * height).reshape(height, width) - cdef np.ndarray[dtype=np.float_t, ndim=2] dist_parent \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] dist_parent \ = np.zeros((height, width)) + # find nearest node with higher density current_pixel_p = image_p for r in range(height): diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index dfb37a7c..7db072c0 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -1,13 +1,19 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np from time import time from scipy import ndimage + +cimport numpy as cnp + from ..util import img_as_float from ..color import rgb2lab, gray2rgb def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, - convert2lab=True): + convert2lab=True): """Segments image using k-means clustering in Color-(x,y) space. Parameters @@ -62,41 +68,41 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, image = rgb2lab(image) # initialize on grid: - cdef int height, width + cdef Py_ssize_t height, width height, width = image.shape[:2] # approximate grid size for desired n_segments - cdef int step = np.ceil(np.sqrt(height * width / n_segments)) + cdef Py_ssize_t step = int(np.ceil(np.sqrt(height * width / n_segments))) grid_y, grid_x = np.mgrid[:height, :width] means_y = grid_y[::step, ::step] means_x = grid_x[::step, ::step] means_color = np.zeros((means_y.shape[0], means_y.shape[1], 3)) - cdef np.ndarray[dtype=np.float_t, ndim=2] means \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] means \ = np.dstack([means_y, means_x, means_color]).reshape(-1, 5) - cdef np.float_t* current_mean - cdef np.float_t* mean_entry + cdef cnp.float_t* current_mean + cdef cnp.float_t* mean_entry n_means = means.shape[0] # we do the scaling of ratio in the same way as in the SLIC paper # so the values have the same meaning ratio = (ratio / float(step)) ** 2 - cdef np.ndarray[dtype=np.float_t, ndim=3] image_yx \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=3] image_yx \ = np.dstack([grid_y, grid_x, image / ratio]).copy("C") - cdef int i, k, x, y, x_min, x_max, y_min, y_max, changes + cdef Py_ssize_t i, k, x, y, x_min, x_max, y_min, y_max, changes cdef double dist_mean - cdef np.ndarray[dtype=np.int_t, ndim=2] nearest_mean \ - = np.zeros((height, width), dtype=np.int) - cdef np.ndarray[dtype=np.float_t, ndim=2] distance \ + cdef cnp.ndarray[dtype=cnp.intp_t, ndim=2] nearest_mean \ + = np.zeros((height, width), dtype=np.intp) + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] distance \ = np.empty((height, width)) - cdef np.float_t* image_p = image_yx.data - cdef np.float_t* distance_p = distance.data - cdef np.float_t* current_distance - cdef np.float_t* current_pixel + cdef cnp.float_t* image_p = image_yx.data + cdef cnp.float_t* distance_p = distance.data + cdef cnp.float_t* current_distance + cdef cnp.float_t* current_pixel cdef double tmp for i in range(max_iter): distance.fill(np.inf) changes = 0 - current_mean = means.data + current_mean = means.data # assign pixels to means for k in range(n_means): # compute windows: diff --git a/skimage/segmentation/random_walker_segmentation.py b/skimage/segmentation/random_walker_segmentation.py index 32baf59c..6df714d2 100644 --- a/skimage/segmentation/random_walker_segmentation.py +++ b/skimage/segmentation/random_walker_segmentation.py @@ -30,8 +30,7 @@ from ..filter import rank_order def _make_graph_edges_3d(n_x, n_y, n_z): - """ - Returns a list of edges for a 3D image. + """Returns a list of edges for a 3D image. Parameters ---------- @@ -45,9 +44,12 @@ def _make_graph_edges_3d(n_x, n_y, n_z): Returns ------- edges : (2, N) ndarray - with the total number of edges N = n_x * n_y * (nz - 1) + - n_x * (n_y - 1) * nz + - (n_x - 1) * n_y * nz + with the total number of edges:: + + N = n_x * n_y * (nz - 1) + + n_x * (n_y - 1) * nz + + (n_x - 1) * n_y * nz + Graph edges with each column describing a node-id pair. """ vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z)) @@ -200,6 +202,7 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, mode : {'bf', 'cg_mg', 'cg'} (default: 'bf') Mode for solving the linear system in the random walker algorithm. + - 'bf' (brute force, default): an LU factorization of the Laplacian is computed. This is fast for small images (<1024x1024), but very slow (due to the memory cost) and memory-consuming for big images (in 3-D @@ -214,6 +217,7 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, requires that the pyamg module (http://code.google.com/p/pyamg/) is installed. For images of size > 512x512, this is the recommended (fastest) mode. + tol : float tolerance to achieve when solving the linear system, in cg' and 'cg_mg' modes. @@ -237,12 +241,12 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, Returns ------- output : ndarray - If `return_full_prob` is False, array of ints of same shape as `data`, - in which each pixel has been labeled according to the marker that - reached the pixel first by anisotropic diffusion. - If `return_full_prob` is True, array of floats of shape - `(nlabels, data.shape)`. `output[label_nb, i, j]` is the probability - that label `label_nb` reaches the pixel `(i, j)` first. + * If `return_full_prob` is False, array of ints of same shape as + `data`, in which each pixel has been labeled according to the marker + that reached the pixel first by anisotropic diffusion. + * If `return_full_prob` is True, array of floats of shape + `(nlabels, data.shape)`. `output[label_nb, i, j]` is the probability + that label `label_nb` reaches the pixel `(i, j)` first. See also -------- diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 59088fcd..89dee59b 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -13,8 +13,8 @@ def test_color(): img[img > 1] = 1 img[img < 0] = 0 seg = slic(img, sigma=0, n_segments=4) - # we expect 4 segments: - print(seg) + + # we expect 4 segments assert_equal(len(np.unique(seg)), 4) assert_array_equal(seg[:10, :10], 0) assert_array_equal(seg[10:, :10], 2) @@ -31,7 +31,7 @@ def test_gray(): img[img > 1] = 1 img[img < 0] = 0 seg = slic(img, sigma=0, n_segments=4, ratio=50.0) - print(seg) + assert_equal(len(np.unique(seg)), 4) assert_array_equal(seg[:10, :10], 0) assert_array_equal(seg[10:, :10], 2) diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py index befcc23f..089487ee 100644 --- a/skimage/transform/__init__.py +++ b/skimage/transform/__init__.py @@ -6,6 +6,6 @@ from ._geometric import (warp, warp_coords, estimate_transform, SimilarityTransform, AffineTransform, ProjectiveTransform, PolynomialTransform, PiecewiseAffineTransform) -from ._warps import swirl, homography, resize, rotate, rescale +from ._warps import swirl, resize, rotate, rescale from .pyramids import (pyramid_reduce, pyramid_expand, pyramid_gaussian, pyramid_laplacian) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index ef1cf700..2b1acc7c 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -1,31 +1,101 @@ -cimport cython +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -from random import randint -from libc.math cimport abs, fabs, sqrt, ceil, floor + +cimport numpy as cnp +cimport cython + +from libc.math cimport abs, fabs, sqrt, ceil from libc.stdlib cimport rand - -np.import_array() - +from skimage.draw import circle_perimeter cdef double PI_2 = 1.5707963267948966 cdef double NEG_PI_2 = -PI_2 -cdef inline int round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline Py_ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -@cython.boundscheck(False) -def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): +def _hough_circle(cnp.ndarray img, + cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius, + char normalize=True): + """Perform a circular Hough transform. + + Parameters + ---------- + img : (M, N) ndarray + Input image with nonzero values representing edges. + radius : ndarray + Radii at which to compute the Hough transform. + normalize : boolean, optional + Normalize the accumulator with the number + of pixels used to draw the radius + + Returns + ------- + H : 3D ndarray (radius index, (M, N) ndarray) + Hough transform accumulator for each radius + + """ + if img.ndim != 2: + raise ValueError('The input image must be 2D.') + + # compute the nonzero indexes + cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] x, y + x, y = np.nonzero(img) + + cdef Py_ssize_t num_pixels = x.size + + # Offset the image + cdef Py_ssize_t max_radius = radius.max() + x = x + max_radius + y = y + max_radius + + cdef Py_ssize_t i, p, c, num_circle_pixels, tx, ty + cdef double incr + cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] circle_x, circle_y + + cdef cnp.ndarray[ndim=3, dtype=cnp.double_t] acc = \ + np.zeros((radius.size, + img.shape[0] + 2 * max_radius, + img.shape[1] + 2 * max_radius), dtype=np.double) + + for i, rad in enumerate(radius): + # Store in memory the circle of given radius + # centered at (0,0) + circle_x, circle_y = circle_perimeter(0, 0, rad) + + num_circle_pixels = circle_x.size + + if normalize: + incr = 1.0 / num_circle_pixels + else: + incr = 1 + + # For each non zero pixel + for p in range(num_pixels): + # Plug the circle at (px, py), + # its coordinates are (tx, ty) + for c in range(num_circle_pixels): + tx = circle_x[c] + x[p] + ty = circle_y[c] + y[p] + acc[i, tx, ty] += incr + + return acc + + +def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None): if img.ndim != 2: raise ValueError('The input image must be 2D.') # Compute the array of angles and their sine and cosine - cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta - cdef np.ndarray[ndim=1, dtype=np.double_t] stheta + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] ctheta + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] stheta if theta is None: theta = np.linspace(PI_2, NEG_PI_2, 180) @@ -34,23 +104,22 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): stheta = np.sin(theta) # compute the bins and allocate the accumulator array - cdef np.ndarray[ndim=2, dtype=np.uint64_t] accum - cdef np.ndarray[ndim=1, dtype=np.double_t] bins - cdef int max_distance, offset + cdef cnp.ndarray[ndim=2, dtype=cnp.uint64_t] accum + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] bins + cdef Py_ssize_t max_distance, offset - max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + - img.shape[1] * img.shape[1]))) + max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + + img.shape[1] * img.shape[1])) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.uint64) bins = np.linspace(-max_distance / 2.0, max_distance / 2.0, max_distance) offset = max_distance / 2 # compute the nonzero indexes - cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs - y_idxs, x_idxs = np.PyArray_Nonzero(img) - + cdef cnp.ndarray[ndim=1, dtype=cnp.npy_intp] x_idxs, y_idxs + y_idxs, x_idxs = np.nonzero(img) # finally, run the transform - cdef int nidxs, nthetas, i, j, x, y, accum_idx + cdef Py_ssize_t nidxs, nthetas, i, j, x, y, accum_idx nidxs = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] for i in range(nidxs): @@ -61,67 +130,75 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): accum[accum_idx, j] += 1 return accum, theta, bins -import math -@cython.cdivision(True) -@cython.boundscheck(False) -def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ - int line_gap, np.ndarray[ndim=1, dtype=np.double_t] theta=None): +def _probabilistic_hough(cnp.ndarray img, int value_threshold, + int line_length, int line_gap, + cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None): + if img.ndim != 2: raise ValueError('The input image must be 2D.') - # compute the array of angles and their sine and cosine - cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta - cdef np.ndarray[ndim=1, dtype=np.double_t] stheta - # calculate thetas if none specified + if theta is None: - theta = np.linspace(math.pi/2, -math.pi/2, 180) - theta = math.pi/2-np.arange(180)/180.0* math.pi - ctheta = np.cos(theta) - stheta = np.sin(theta) - cdef int height = img.shape[0] - cdef int width = img.shape[1] + theta = PI_2 - np.arange(180) / 180.0 * 2 * PI_2 + + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] + # compute the bins and allocate the accumulator array - cdef np.ndarray[ndim=2, dtype=np.int64_t] accum - cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = np.zeros((height, width), dtype=np.uint8) - cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = np.zeros((2, 2), dtype=np.int32) - cdef int max_distance, offset, num_indexes, index + cdef cnp.ndarray[ndim=2, dtype=cnp.int64_t] accum + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] ctheta, stheta + cdef cnp.ndarray[ndim=2, dtype=cnp.uint8_t] mask = \ + np.zeros((height, width), dtype=np.uint8) + cdef cnp.ndarray[ndim=2, dtype=cnp.int32_t] line_end = \ + np.zeros((2, 2), dtype=np.int32) + cdef Py_ssize_t max_distance, offset, num_indexes, index cdef double a, b - cdef int nidxs, nthetas, i, j, x, y, px, py, accum_idx, value, max_value, max_theta + cdef Py_ssize_t nidxs, i, j, x, y, px, py, accum_idx + cdef int value, max_value, max_theta cdef int shift = 16 # maximum line number cutoff - cdef int lines_max = 2 ** 15 - cdef int xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count + cdef Py_ssize_t lines_max = 2 ** 15 + cdef Py_ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, \ + good_line, count + cdef list lines = list() + max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + img.shape[1] * img.shape[1]))) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.int64) offset = max_distance / 2 - # find the nonzero indexes - cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs - y_idxs, x_idxs = np.nonzero(img) - num_indexes = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] - points = [] - for i in range(num_indexes): - points.append((x_idxs[i], y_idxs[i])) - lines = [] - # create mask of all non-zero indexes - for i in range(num_indexes): - mask[y_idxs[i], x_idxs[i]] = 1 + + # compute sine and cosine of angles + ctheta = np.cos(theta) + stheta = np.sin(theta) + + # find the nonzero indexes + y_idxs, x_idxs = np.nonzero(img) + points = list(zip(x_idxs, y_idxs)) + # mask all non-zero indexes + mask[y_idxs, x_idxs] = 1 + while 1: - # select random non-zero point + + # quit if no remaining points count = len(points) if count == 0: break - index = rand() % (count) + + # select random non-zero point + index = rand() % count x = points[index][0] y = points[index][1] del points[index] + # if previously eliminated, skip if not mask[y, x]: continue + value = 0 - max_value = value_threshold-1 + max_value = value_threshold - 1 max_theta = -1 + # apply hough transform on point for j in range(nthetas): accum_idx = round((ctheta[j] * x + stheta[j] * y)) + offset @@ -132,7 +209,9 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ max_theta = j if max_value < value_threshold: continue - # from the random point walk in opposite directions and find line beginning and end + + # from the random point walk in opposite directions and find line + # beginning and end a = -stheta[max_theta] b = ctheta[max_theta] x0 = x @@ -188,6 +267,7 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ # confirm line length is sufficient good_line = abs(line_end[1, 1] - line_end[0, 1]) >= line_length or \ abs(line_end[1, 0] - line_end[0, 0]) >= line_length + # pass 2: walk the line again and reset accumulator and mask for k in range(2): px = x0 @@ -207,7 +287,8 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ # if non-zero point found, continue the line if mask[y1, x1]: if good_line: - accum_idx = round((ctheta[j] * x1 + stheta[j] * y1)) + offset + accum_idx = round((ctheta[j] * x1 \ + + stheta[j] * y1)) + offset accum[accum_idx, max_theta] -= 1 mask[y1, x1] = 0 # exit when the point is the line end @@ -218,9 +299,9 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ # add line to the result if good_line: - lines.append(((line_end[0, 0], line_end[0, 1]), (line_end[1, 0], line_end[1, 1]))) + lines.append(((line_end[0, 0], line_end[0, 1]), + (line_end[1, 0], line_end[1, 1]))) if len(lines) > lines_max: return lines + return lines - - diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 0ace0937..be7eee12 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -129,7 +129,7 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.): Input image. angle : float Rotation angle in degrees in counter-clockwise direction. - resize: bool, optional + resize : bool, optional Determine whether the shape of the output image will be automatically calculated, so the complete rotated image exactly fits. Default is False. @@ -253,101 +253,3 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0, return warp(image, _swirl_mapping, map_args=warp_args, output_shape=output_shape, order=order, mode=mode, cval=cval) - - -def homography(image, H, output_shape=None, order=1, - mode='constant', cval=0.): - """ - .. note:: Deprecated in skimage 0.7 - `homography` will be removed in skimage 0.8, it is replaced by - `warp` because the latter provides the same functionality:: - - warp(image, ProjectiveTransform(H)) - - Perform a projective transformation (homography) on an image. - - For each pixel, given its homogeneous coordinate :math:`\mathbf{x} - = [x, y, 1]^T`, its target position is calculated by multiplying - with the given matrix, :math:`H`, to give :math:`H \mathbf{x}`. - E.g., to rotate by theta degrees clockwise, the matrix should be - - :: - - [[cos(theta) -sin(theta) 0] - [sin(theta) cos(theta) 0] - [0 0 1]] - - or, to translate x by 10 and y by 20, - - :: - - [[1 0 10] - [0 1 20] - [0 0 1 ]]. - - Parameters - ---------- - image : 2-D array - Input image. - H : array of shape ``(3, 3)`` - Transformation matrix H that defines the homography. - output_shape : tuple (rows, cols) - Shape of the output image generated. - order : int - Order of splines used in interpolation. - mode : string - How to handle values outside the image borders. Passed as-is - to ndimage. - cval : string - Used in conjunction with mode 'constant', the value outside - the image boundaries. - - Examples - -------- - >>> # rotate by 90 degrees around origin and shift down by 2 - >>> x = np.arange(9, dtype=np.uint8).reshape((3, 3)) + 1 - >>> x - array([[1, 2, 3], - [4, 5, 6], - [7, 8, 9]], dtype=uint8) - >>> theta = -np.pi/2 - >>> M = np.array([[np.cos(theta),-np.sin(theta),0], - ... [np.sin(theta), np.cos(theta),2], - ... [0, 0, 1]]) - >>> x90 = homography(x, M, order=1) - >>> x90 - array([[3, 6, 9], - [2, 5, 8], - [1, 4, 7]], dtype=uint8) - >>> # translate right by 2 and down by 1 - >>> y = np.zeros((5,5), dtype=np.uint8) - >>> y[1, 1] = 255 - >>> y - array([[ 0, 0, 0, 0, 0], - [ 0, 255, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]], dtype=uint8) - >>> M = np.array([[ 1., 0., 2.], - ... [ 0., 1., 1.], - ... [ 0., 0., 1.]]) - >>> y21 = homography(y, M, order=1) - >>> y21 - array([[ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 255, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]], dtype=uint8) - - """ - import warnings - warnings.warn('the homography function is deprecated; ' - 'use the `warp` and `ProjectiveTransform` class instead', - category=DeprecationWarning) - - tform = ProjectiveTransform(H) - return warp(image, inverse_map=tform.inverse, output_shape=output_shape, - order=order, mode=mode, cval=cval) - - return warp(image, inverse_map=tform.inverse, output_shape=output_shape, - order=order, mode=mode, cval=cval) diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index ce400ed6..968f643a 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -2,9 +2,9 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False - -cimport numpy as np import numpy as np + +cimport numpy as cnp from skimage._shared.interpolation cimport (nearest_neighbour_interpolation, bilinear_interpolation, biquadratic_interpolation, @@ -35,7 +35,7 @@ cdef inline void _matrix_transform(double x, double y, double* H, double *x_, y_[0] = yy / zz -def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, +def _warp_fast(cnp.ndarray image, cnp.ndarray H, output_shape=None, int order=1, mode='constant', double cval=0): """Projective transformation (homography). @@ -83,9 +83,9 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, """ - cdef np.ndarray[dtype=np.double_t, ndim=2, mode="c"] img = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode="c"] img = \ np.ascontiguousarray(image, dtype=np.double) - cdef np.ndarray[dtype=np.double_t, ndim=2, mode="c"] M = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode="c"] M = \ np.ascontiguousarray(H) if mode not in ('constant', 'wrap', 'reflect', 'nearest'): @@ -93,7 +93,7 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, "`constant`, `nearest`, `wrap` or `reflect`.") cdef char mode_c = ord(mode[0].upper()) - cdef int out_r, out_c + cdef Py_ssize_t out_r, out_c if output_shape is None: out_r = img.shape[0] out_c = img.shape[1] @@ -101,15 +101,15 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, out_r = output_shape[0] out_c = output_shape[1] - cdef np.ndarray[dtype=np.double_t, ndim=2] out = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2] out = \ np.zeros((out_r, out_c), dtype=np.double) - cdef int tfr, tfc + cdef Py_ssize_t tfr, tfc cdef double r, c - cdef int rows = img.shape[0] - cdef int cols = img.shape[1] + cdef Py_ssize_t rows = img.shape[0] + cdef Py_ssize_t cols = img.shape[1] - cdef double (*interp_func)(double*, int, int, double, double, + cdef double (*interp_func)(double*, Py_ssize_t, Py_ssize_t, double, double, char, double) if order == 0: interp_func = nearest_neighbour_interpolation diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 05d5cfbe..e83cecd5 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -1,4 +1,4 @@ -__all__ = ['hough', 'hough_peaks', 'probabilistic_hough'] +__all__ = ['hough', 'hough_line', 'hough_circle', 'hough_peaks', 'probabilistic_hough'] from itertools import izip as zip @@ -96,8 +96,15 @@ def probabilistic_hough(img, threshold=10, line_length=50, line_gap=10, """ return _probabilistic_hough(img, threshold, line_length, line_gap, theta) +from skimage._shared.utils import deprecated +@deprecated('hough_line') def hough(img, theta=None): + return hough_line(img, theta) + +from ._hough_transform import _hough_circle + +def hough_line(img, theta=None): """Perform a straight line Hough transform. Parameters @@ -138,6 +145,26 @@ def hough(img, theta=None): """ return _hough(img, theta) +def hough_circle(img, radius, normalize=True): + """Perform a circular Hough transform. + + Parameters + ---------- + img : (M, N) ndarray + Input image with nonzero values representing edges. + radius : ndarray + Radii at which to compute the Hough transform. + normalize : boolean, optional + Normalize the accumulator with the number + of pixels used to draw the radius + + Returns + ------- + H : 3D ndarray (radius index, (M, N) ndarray) + Hough transform accumulator for each radius + + """ + return _hough_circle(img, radius.astype(np.intp), normalize) def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, threshold=None, num_peaks=np.inf): diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index a38e6e76..00427332 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -4,6 +4,7 @@ from numpy.testing import * import skimage.transform as tf import skimage.transform.hough_transform as ht from skimage.transform import probabilistic_hough +from skimage.draw import circle_perimeter def append_desc(func, description): @@ -14,8 +15,6 @@ def append_desc(func, description): return func -from skimage.transform import * - def test_hough(): # Generate a test image @@ -23,7 +22,7 @@ def test_hough(): for i in range(25, 75): img[100 - i, i] = 1 - out, angles, d = tf.hough(img) + out, angles, d = tf.hough_line(img) y, x = np.where(out == out.max()) dist = d[y[0]] @@ -37,7 +36,7 @@ def test_hough_angles(): img = np.zeros((10, 10)) img[0, 0] = 1 - out, angles, d = tf.hough(img, np.linspace(0, 360, 10)) + out, angles, d = tf.hough_line(img, np.linspace(0, 360, 10)) assert_equal(len(angles), 10) @@ -76,7 +75,7 @@ def test_hough_peaks_dist(): img = np.zeros((100, 100), dtype=np.bool_) img[:, 30] = True img[:, 40] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_distance=5)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_distance=15)[0]) == 1 @@ -86,17 +85,17 @@ def test_hough_peaks_angle(): img[:, 0] = True img[0, :] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 theta = np.linspace(0, np.pi, 100) - hspace, angles, dists = tf.hough(img, theta) + hspace, angles, dists = tf.hough_line(img, theta) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 theta = np.linspace(np.pi / 3, 4. / 3 * np.pi, 100) - hspace, angles, dists = tf.hough(img, theta) + hspace, angles, dists = tf.hough_line(img, theta) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 @@ -105,10 +104,25 @@ def test_hough_peaks_num(): img = np.zeros((100, 100), dtype=np.bool_) img[:, 30] = True img[:, 40] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_distance=0, min_angle=0, num_peaks=1)[0]) == 1 +def test_houghcircle(): + # Prepare picture + img = np.zeros((120, 100), dtype=int) + radius = 20 + x_0, y_0 = (99, 50) + x, y = circle_perimeter(y_0, x_0, radius) + img[y, x] = 1 + + out = tf.hough_circle(img, np.array([radius])) + + x, y = np.where(out[0] == out[0].max()) + # Offset for x_0, y_0 + assert_equal(x[0], x_0 + radius) + assert_equal(y[0], y_0 + radius) + if __name__ == "__main__": run_module_suite() diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index 2cdda001..93f87320 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -5,7 +5,7 @@ from scipy.ndimage import map_coordinates from skimage.transform import (warp, warp_coords, rotate, resize, rescale, AffineTransform, ProjectiveTransform, - SimilarityTransform, homography) + SimilarityTransform) from skimage import transform as tf, data, img_as_float from skimage.color import rgb2gray @@ -39,10 +39,6 @@ def test_homography(): assert_array_almost_equal(x90, np.rot90(x)) -def test_homography_basic(): - homography(np.random.random((25, 25)), np.eye(3)) - - def test_fast_homography(): img = rgb2gray(data.lena()).astype(np.uint8) img = img[:, :100] @@ -87,10 +83,10 @@ def test_rotate(): def test_rotate_resize(): x = np.zeros((10, 10), dtype=np.double) - + x45 = rotate(x, 45, resize=False) assert x45.shape == (10, 10) - + x45 = rotate(x, 45, resize=True) # new dimension should be d = sqrt(2 * (10/2)^2) assert x45.shape == (14, 14) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 9f804406..03fa62b4 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -2,7 +2,7 @@ from __future__ import division import numpy as np __all__ = ['img_as_float', 'img_as_int', 'img_as_uint', 'img_as_ubyte', - 'img_as_bool'] + 'img_as_bool', 'dtype_limits'] from .. import get_log log = get_log('dtype_converter') @@ -28,6 +28,23 @@ if np.__version__ >= "1.6.0": _supported_types += (np.float16, ) +def dtype_limits(image, clip_negative=True): + """Return intensity limits, i.e. (min, max) tuple, of the image's dtype. + + Parameters + ---------- + image : ndarray + Input image. + clip_negative : bool + If True, clip the negative range (i.e. return 0 for min intensity) + even if the image dtype allows negative values. + """ + imin, imax = dtype_range[image.dtype.type] + if clip_negative: + imin = 0 + return imin, imax + + def convert(image, dtype, force_copy=False, uniform=False): """ Convert an image to the requested data-type. diff --git a/skimage/viewer/canvastools/__init__.py b/skimage/viewer/canvastools/__init__.py new file mode 100644 index 00000000..22a2d205 --- /dev/null +++ b/skimage/viewer/canvastools/__init__.py @@ -0,0 +1,3 @@ +from linetool import LineTool, ThickLineTool +from recttool import RectangleTool +from painttool import PaintTool diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py new file mode 100644 index 00000000..6fcda9c4 --- /dev/null +++ b/skimage/viewer/canvastools/base.py @@ -0,0 +1,176 @@ +import numpy as np + +try: + from matplotlib import lines +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") + + +__all__ = ['CanvasToolBase', 'ToolHandles'] + + +def _pass(*args): + pass + + +class CanvasToolBase(object): + """Base canvas tool for matplotlib axes. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + useblit : bool + If True, update canvas by blitting, which is much faster than normal + redrawing (turn off for debugging purposes). + """ + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + useblit=True): + self.ax = ax + self.canvas = ax.figure.canvas + self.img_background = None + self.cids = [] + self._artists = [] + self.active = True + + if useblit: + self.connect_event('draw_event', self._blit_on_draw_event) + self.useblit = useblit + + self.callback_on_move = _pass if on_move is None else on_move + self.callback_on_enter = _pass if on_enter is None else on_enter + self.callback_on_release = _pass if on_release is None else on_release + + self.connect_event('key_press_event', self._on_key_press) + + def connect_event(self, event, callback): + """Connect callback with an event. + + This should be used in lieu of `figure.canvas.mpl_connect` since this + function stores call back ids for later clean up. + """ + cid = self.canvas.mpl_connect(event, callback) + self.cids.append(cid) + + def disconnect_events(self): + """Disconnect all events created by this widget.""" + for c in self.cids: + self.canvas.mpl_disconnect(c) + + def ignore(self, event): + """Return True if event should be ignored. + + This method (or a version of it) should be called at the beginning + of any event callback. + """ + return not self.active + + def set_visible(self, val): + for artist in self._artists: + artist.set_visible(val) + + def _blit_on_draw_event(self, event=None): + self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + self._draw_artists() + + def _draw_artists(self): + for artist in self._artists: + self.ax.draw_artist(artist) + + def remove(self): + """Remove artists and events from axes. + + Note that the naming here mimics the interface of Matplotlib artists. + """ + #TODO: For some reason, RectangleTool doesn't get properly removed + self.disconnect_events() + for a in self._artists: + a.remove() + + def redraw(self): + """Redraw image and canvas artists. + + This method should be called by subclasses when artists are updated. + """ + if self.useblit and self.img_background is not None: + self.canvas.restore_region(self.img_background) + self._draw_artists() + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw_idle() + + def _on_key_press(self, event): + if event.key == 'enter': + self.callback_on_enter(self.geometry) + self.set_visible(False) + self.redraw() + + @property + def geometry(self): + """Geometry information that gets passed to callback functions.""" + raise NotImplementedError + + +class ToolHandles(object): + """Control handles for canvas tools. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool handles are displayed. + x, y : 1D arrays + Coordinates of control handles. + marker : str + Shape of marker used to display handle. See `matplotlib.pyplot.plot`. + marker_props : dict + Additional marker properties. See :class:`matplotlib.lines.Line2D`. + """ + def __init__(self, ax, x, y, marker='o', marker_props=None): + self.ax = ax + + props = dict(marker=marker, markersize=7, mfc='w', ls='none', + alpha=0.5, visible=False) + props.update(marker_props if marker_props is not None else {}) + self._markers = lines.Line2D(x, y, animated=True, **props) + self.ax.add_line(self._markers) + self.artist = self._markers + + @property + def x(self): + return self._markers.get_xdata() + + @property + def y(self): + return self._markers.get_ydata() + + def set_data(self, pts, y=None): + """Set x and y positions of handles""" + if y is not None: + x = pts + pts = np.array([x, y]) + self._markers.set_data(pts) + + def set_visible(self, val): + self._markers.set_visible(val) + + def set_animated(self, val): + self._markers.set_animated(val) + + def draw(self): + self.ax.draw_artist(self._markers) + + def closest(self, x, y): + """Return index and pixel distance to closest index.""" + pts = np.transpose((self.x, self.y)) + # Transform data coordinates to pixel coordinates. + pts = self.ax.transData.transform(pts) + diff = pts - ((x, y)) + dist = np.sqrt(np.sum(diff**2, axis=1)) + return np.argmin(dist), np.min(dist) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py new file mode 100644 index 00000000..7cdf91b5 --- /dev/null +++ b/skimage/viewer/canvastools/linetool.py @@ -0,0 +1,207 @@ +import numpy as np + +try: + from matplotlib import lines +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") + +from base import CanvasToolBase, ToolHandles + + +__all__ = ['LineTool', 'ThickLineTool'] + + +class LineTool(CanvasToolBase): + """Widget for line selection in a plot. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + maxdist : float + Maximum pixel distance allowed when selecting control handle. + line_props : dict + Properties for :class:`matplotlib.lines.Line2D`. + + Attributes + ---------- + end_points : 2D array + End points of line ((x1, y1), (x2, y2)). + """ + def __init__(self, ax, on_move=None, on_release=None, on_enter=None, + maxdist=10, line_props=None): + super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + on_release=on_release) + + props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') + props.update(line_props if line_props is not None else {}) + self.linewidth = props['linewidth'] + self.maxdist = maxdist + self._active_pt = None + + x = (0, 0) + y = (0, 0) + self._end_pts = np.transpose([x, y]) + + self._line = lines.Line2D(x, y, visible=False, animated=True, **props) + ax.add_line(self._line) + + self._handles = ToolHandles(ax, x, y) + self._handles.set_visible(False) + self._artists = [self._line, self._handles.artist] + + if on_enter is None: + def on_enter(pts): + x, y = np.transpose(pts) + print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2) + self.callback_on_enter = on_enter + + self.connect_event('button_press_event', self.on_mouse_press) + self.connect_event('button_release_event', self.on_mouse_release) + self.connect_event('motion_notify_event', self.on_move) + + @property + def end_points(self): + return self._end_pts + + @end_points.setter + def end_points(self, pts): + self._end_pts = np.asarray(pts) + + self._line.set_data(np.transpose(pts)) + self._handles.set_data(np.transpose(pts)) + self._line.set_linewidth(self.linewidth) + + self.set_visible(True) + self.redraw() + + def on_mouse_press(self, event): + if event.button != 1 or not self.ax.in_axes(event): + return + self.set_visible(True) + idx, px_dist = self._handles.closest(event.x, event.y) + if px_dist < self.maxdist: + self._active_pt = idx + else: + self._active_pt = 0 + x, y = event.xdata, event.ydata + self._end_pts = np.array([[x, y], [x, y]]) + + def on_mouse_release(self, event): + if event.button != 1: + return + self._active_pt = None + self.callback_on_release(self.geometry) + + def on_move(self, event): + if event.button != 1 or self._active_pt is None: + return + if not self.ax.in_axes(event): + return + self.update(event.xdata, event.ydata) + self.callback_on_move(self.geometry) + + def update(self, x=None, y=None): + if x is not None: + self._end_pts[self._active_pt, :] = x, y + self.end_points = self._end_pts + + @property + def geometry(self): + return self.end_points + + +class ThickLineTool(LineTool): + """Widget for line selection in a plot. + + The thickness of the line can be varied using the mouse scroll wheel, or + with the '+' and '-' keys. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + on_change : function + Function called whenever the line thickness is changed. + maxdist : float + Maximum pixel distance allowed when selecting control handle. + line_props : dict + Properties for :class:`matplotlib.lines.Line2D`. + + Attributes + ---------- + end_points : 2D array + End points of line ((x1, y1), (x2, y2)). + """ + + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + on_change=None, maxdist=10, line_props=None): + super(ThickLineTool, self).__init__(ax, + on_move=on_move, + on_enter=on_enter, + on_release=on_release, + maxdist=maxdist, + line_props=line_props) + + if on_change is None: + def on_change(*args): + pass + self.callback_on_change = on_change + + self.connect_event('scroll_event', self.on_scroll) + self.connect_event('key_press_event', self.on_key_press) + + def on_scroll(self, event): + if not event.inaxes: + return + if event.button == 'up': + self._thicken_scan_line() + elif event.button == 'down': + self._shrink_scan_line() + + def on_key_press(self, event): + if event.key == '+': + self._thicken_scan_line() + elif event.key == '-': + self._shrink_scan_line() + + def _thicken_scan_line(self): + self.linewidth += 1 + self.update() + self.callback_on_change(self.geometry) + + def _shrink_scan_line(self): + if self.linewidth > 1: + self.linewidth -= 1 + self.update() + self.callback_on_change(self.geometry) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + from skimage import data + + image = data.camera() + + f, ax = plt.subplots() + ax.imshow(image, interpolation='nearest') + h, w = image.shape + + # line_tool = LineTool(ax) + line_tool = ThickLineTool(ax) + line_tool.end_points = ([w/3, h/2], [2*w/3, h/2]) + plt.show() diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py new file mode 100644 index 00000000..3fbab153 --- /dev/null +++ b/skimage/viewer/canvastools/painttool.py @@ -0,0 +1,203 @@ +import numpy as np + +try: + import matplotlib.pyplot as plt + import matplotlib.colors as mcolors + LABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold', + 'greenyellow', 'blueviolet']) +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") + +from base import CanvasToolBase + + +__all__ = ['PaintTool'] + + +class PaintTool(CanvasToolBase): + """Widget for painting on top of a plot. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + overlay_shape : shape tuple + 2D shape tuple used to initialize overlay image. + alpha : float (between [0, 1]) + Opacity of overlay + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + rect_props : dict + Properties for :class:`matplotlib.patches.Rectangle`. This class + redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. + + Attributes + ---------- + overlay : array + Overlay of painted labels displayed on top of image. + label : int + Current paint color. + """ + def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None, + on_release=None, on_enter=None, rect_props=None): + super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + on_release=on_release) + + props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True) + props.update(rect_props if rect_props is not None else {}) + + self.alpha = alpha + self.cmap = LABELS_CMAP + self._overlay_plot = None + self._shape = overlay_shape + self.overlay = np.zeros(overlay_shape, dtype='uint8') + + self._cursor = plt.Rectangle((0, 0), 0, 0, **props) + self._cursor.set_visible(False) + self.ax.add_patch(self._cursor) + + # `label` and `radius` can only be set after initializing `_cursor` + self.label = 1 + self.radius = radius + + # Note that the order is important: Redraw cursor *after* overlay + self._artists = [self._overlay_plot, self._cursor] + + self.connect_event('button_press_event', self.on_mouse_press) + self.connect_event('button_release_event', self.on_mouse_release) + self.connect_event('motion_notify_event', self.on_move) + + @property + def label(self): + return self._label + + @label.setter + def label(self, value): + if value >= self.cmap.N: + raise ValueError('Maximum label value = %s' % len(self.cmap - 1)) + self._label = value + self._cursor.set_edgecolor(self.cmap(value)) + + @property + def radius(self): + return self._radius + + @radius.setter + def radius(self, r): + self._radius = r + self._width = 2 * r + 1 + self._cursor.set_width(self._width) + self._cursor.set_height(self._width) + self.window = CenteredWindow(r, self._shape) + + @property + def overlay(self): + return self._overlay + + @overlay.setter + def overlay(self, image): + self._overlay = image + if image is None: + self.ax.images.remove(self._overlay_plot) + self._overlay_plot = None + elif self._overlay_plot is None: + props = dict(cmap=self.cmap, alpha=self.alpha, + norm=mcolors.no_norm(), animated=True) + self._overlay_plot = self.ax.imshow(image, **props) + else: + self._overlay_plot.set_data(image) + self.redraw() + + def _on_key_press(self, event): + if event.key == 'enter': + self.callback_on_enter(self.geometry) + self.redraw() + + def on_mouse_press(self, event): + if event.button != 1 or not self.ax.in_axes(event): + return + self.update_cursor(event.xdata, event.ydata) + self.update_overlay(event.xdata, event.ydata) + + def on_mouse_release(self, event): + if event.button != 1: + return + self.callback_on_release(self.geometry) + + def on_move(self, event): + if not self.ax.in_axes(event): + self._cursor.set_visible(False) + self.redraw() # make sure cursor is not visible + return + self._cursor.set_visible(True) + + self.update_cursor(event.xdata, event.ydata) + if event.button != 1: + self.redraw() # update cursor position + return + self.update_overlay(event.xdata, event.ydata) + self.callback_on_move(self.geometry) + + def update_overlay(self, x, y): + overlay = self.overlay + overlay[self.window.at(y, x)] = self.label + # Note that overlay calls `redraw` + self.overlay = overlay + + def update_cursor(self, x, y): + x = x - self.radius - 1 + y = y - self.radius - 1 + self._cursor.set_xy((x, y)) + + @property + def geometry(self): + return self.overlay + + +class CenteredWindow(object): + """Window that create slices numpy arrays over 2D windows. + + Example + ------- + >>> a = np.arange(16).reshape(4, 4) + >>> w = CenteredWindow(1, a.shape) + >>> a[w.at(1, 1)] + array([[ 0, 1, 2], + [ 4, 5, 6], + [ 8, 9, 10]]) + >>> a[w.at(0, 0)] + array([[0, 1], + [4, 5]]) + >>> a[w.at(4, 3)] + array([[14, 15]]) + """ + def __init__(self, radius, array_shape): + self.radius = radius + self.array_shape = array_shape + + def at(self, row, col): + h, w = self.array_shape + r = self.radius + xmin = max(0, col - r) + xmax = min(w, col + r + 1) + ymin = max(0, row - r) + ymax = min(h, row + r + 1) + return [slice(ymin, ymax), slice(xmin, xmax)] + + +if __name__ == '__main__': + np.testing.rundocs() + import matplotlib.pyplot as plt + from skimage import data + + image = data.camera() + + f, ax = plt.subplots() + ax.imshow(image, interpolation='nearest') + paint_tool = PaintTool(ax, image.shape) + plt.show() diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py new file mode 100644 index 00000000..d4ae113e --- /dev/null +++ b/skimage/viewer/canvastools/recttool.py @@ -0,0 +1,214 @@ +try: + from matplotlib.widgets import RectangleSelector +except ImportError: + RectangleSelector = object + print("Could not import matplotlib -- skimage.viewer not available.") + +from skimage.viewer.canvastools.base import CanvasToolBase +from skimage.viewer.canvastools.base import ToolHandles + + +__all__ = ['RectangleTool'] + + +class RectangleTool(CanvasToolBase, RectangleSelector): + """Widget for selecting a rectangular region in a plot. + + After making the desired selection, press "Enter" to accept the selection + and call the `on_enter` callback function. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the rectangle extents as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + maxdist : float + Maximum pixel distance allowed when selecting control handle. + rect_props : dict + Properties for :class:`matplotlib.patches.Rectangle`. This class + redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. + + Attributes + ---------- + extents : tuple + Rectangle extents: (xmin, xmax, ymin, ymax). + """ + + def __init__(self, ax, on_move=None, on_release=None, on_enter=None, + maxdist=10, rect_props=None): + CanvasToolBase.__init__(self, ax, on_move=on_move, + on_enter=on_enter, on_release=on_release) + + props = dict(edgecolor=None, facecolor='r', alpha=0.15) + props.update(rect_props if rect_props is not None else {}) + if props['edgecolor'] is None: + props['edgecolor'] = props['facecolor'] + RectangleSelector.__init__(self, ax, lambda *args: None, + rectprops=props, + useblit=self.useblit) + # Alias rectangle attribute, which is initialized in RectangleSelector. + self._rect = self.to_draw + self._rect.set_animated(True) + + self.maxdist = maxdist + self.active_handle = None + self._extents_on_press = None + + if on_enter is None: + def on_enter(extents): + print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents) + self.callback_on_enter = on_enter + + props = dict(mec=props['edgecolor']) + self._corner_order = ['NW', 'NE', 'SE', 'SW'] + xc, yc = self.corners + self._corner_handles = ToolHandles(ax, xc, yc, marker_props=props) + + self._edge_order = ['W', 'N', 'E', 'S'] + xe, ye = self.edge_centers + self._edge_handles = ToolHandles(ax, xe, ye, marker='s', + marker_props=props) + + self._artists = [self._rect, + self._corner_handles.artist, + self._edge_handles.artist] + + @property + def _rect_bbox(self): + x0 = self._rect.get_x() + y0 = self._rect.get_y() + width = self._rect.get_width() + height = self._rect.get_height() + return x0, y0, width, height + + @property + def corners(self): + """Corners of rectangle from lower left, moving clockwise.""" + x0, y0, width, height = self._rect_bbox + xc = x0, x0 + width, x0 + width, x0 + yc = y0, y0, y0 + height, y0 + height + return xc, yc + + @property + def edge_centers(self): + """Midpoint of rectangle edges from left, moving clockwise.""" + x0, y0, width, height = self._rect_bbox + w = width / 2. + h = height / 2. + xe = x0, x0 + w, x0 + width, x0 + w + ye = y0 + h, y0, y0 + h, y0 + height + return xe, ye + + @property + def extents(self): + """Return (xmin, xmax, ymin, ymax).""" + x0, y0, width, height = self._rect_bbox + xmin, xmax = sorted([x0, x0 + width]) + ymin, ymax = sorted([y0, y0 + height]) + return xmin, xmax, ymin, ymax + + @extents.setter + def extents(self, extents): + x1, x2, y1, y2 = extents + xmin, xmax = sorted([x1, x2]) + ymin, ymax = sorted([y1, y2]) + # Update displayed rectangle + self._rect.set_x(xmin) + self._rect.set_y(ymin) + self._rect.set_width(xmax - xmin) + self._rect.set_height(ymax - ymin) + # Update displayed handles + self._corner_handles.set_data(*self.corners) + self._edge_handles.set_data(*self.edge_centers) + + self.set_visible(True) + self.redraw() + + def release(self, event): + if event.button != 1: + return + if not self.ax.in_axes(event): + self.eventpress = None + return + RectangleSelector.release(self, event) + self._extents_on_press = None + # Undo hiding of rectangle and redraw. + self.set_visible(True) + self.redraw() + self.callback_on_release(self.geometry) + + def press(self, event): + if event.button != 1 or not self.ax.in_axes(event): + return + self._set_active_handle(event) + if self.active_handle is None: + # Clear previous rectangle before drawing new rectangle. + self.set_visible(False) + self.redraw() + self.set_visible(True) + RectangleSelector.press(self, event) + + def _set_active_handle(self, event): + """Set active handle based on the location of the mouse event""" + # Note: event.xdata/ydata in data coordinates, event.x/y in pixels + c_idx, c_dist = self._corner_handles.closest(event.x, event.y) + e_idx, e_dist = self._edge_handles.closest(event.x, event.y) + + # Set active handle as closest handle, if mouse click is close enough. + if c_dist > self.maxdist and e_dist > self.maxdist: + self.active_handle = None + return + elif c_dist < e_dist: + self.active_handle = self._corner_order[c_idx] + else: + self.active_handle = self._edge_order[e_idx] + + # Save coordinates of rectangle at the start of handle movement. + x1, x2, y1, y2 = self.extents + # Switch variables so that only x2 and/or y2 are updated on move. + if self.active_handle in ['W', 'SW', 'NW']: + x1, x2 = x2, event.xdata + if self.active_handle in ['N', 'NW', 'NE']: + y1, y2 = y2, event.ydata + self._extents_on_press = x1, x2, y1, y2 + + def onmove(self, event): + if self.eventpress is None or not self.ax.in_axes(event): + return + + if self.active_handle is None: + # New rectangle + x1 = self.eventpress.xdata + y1 = self.eventpress.ydata + x2, y2 = event.xdata, event.ydata + else: + x1, x2, y1, y2 = self._extents_on_press + if self.active_handle in ['E', 'W'] + self._corner_order: + x2 = event.xdata + if self.active_handle in ['N', 'S'] + self._corner_order: + y2 = event.ydata + self.extents = (x1, x2, y1, y2) + self.callback_on_move(self.geometry) + + @property + def geometry(self): + return self.extents + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + from skimage import data + + f, ax = plt.subplots() + ax.imshow(data.camera(), interpolation='nearest') + + rect_tool = RectangleTool(ax) + plt.show() + print("Final selection:") + rect_tool.callback_on_enter(rect_tool.extents) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 198bac6a..5adf986e 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -9,11 +9,6 @@ except ImportError: QDialog = object # hack to prevent nosetest and autodoc errors print("Could not import PyQt4 -- skimage.viewer not available.") -try: - import matplotlib as mpl -except ImportError: - print("Could not import matplotlib -- skimage.viewer not available.") - from ..utils import RequiredAttr, init_qtapp @@ -49,8 +44,9 @@ class Plugin(QDialog): name : str Name of plugin. This is displayed as the window title. artist : list - List of Matplotlib artists. Any artists created by the plugin should - be added to this list so that it gets cleaned up on close. + List of Matplotlib artists and canvastools. Any artists created by the + plugin should be added to this list so that it gets cleaned up on + close. Examples -------- @@ -79,9 +75,8 @@ class Plugin(QDialog): """ name = 'Plugin' image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) - draws_on_image = False - def __init__(self, image_filter=None, height=0, width=400, useblit=None): + def __init__(self, image_filter=None, height=0, width=400, useblit=True): init_qtapp() super(Plugin, self).__init__() @@ -98,8 +93,6 @@ class Plugin(QDialog): self.arguments = [] self.keyword_arguments= {} - if useblit is None: - useblit = True if mpl.backends.backend.endswith('Agg') else False self.useblit = useblit self.cids = [] self.artists = [] @@ -123,8 +116,6 @@ class Plugin(QDialog): #TODO: Always passing image as first argument may be bad assumption. self.arguments.append(self.image_viewer.original_image) - if self.draws_on_image: - self.connect_image_event('draw_event', self.on_draw) # Call filter so that filtered image matches widget values self.filter_image() @@ -155,15 +146,6 @@ class Plugin(QDialog): self.add_widget(widget) return self - def on_draw(self, event): - """Save image background when blitting. - - The saved image is used to "clear" the figure before redrawing artists. - """ - if self.useblit: - bbox = self.image_viewer.ax.bbox - self.img_background = self.image_viewer.canvas.copy_from_bbox(bbox) - def filter_image(self, *widget_arg): """Call `image_filter` with widget args and kwargs @@ -184,6 +166,11 @@ class Plugin(QDialog): # If param is a widget, return its `val` attribute. return param if not hasattr(param, 'val') else param.val + @property + def filtered_image(self): + """Return filtered image.""" + return self.image_viewer.image + def display_filtered_image(self, image): """Display the filtered image on image viewer. @@ -204,38 +191,18 @@ class Plugin(QDialog): def closeEvent(self, event): """On close disconnect all artists and events from ImageViewer. - Note that events must be connected using `self.connect_image_event` and - artists must be appended to `self.artists`. + Note that artists must be appended to `self.artists`. """ - self.disconnect_image_events() + self.clean_up() + self.close() + + def clean_up(self): self.remove_image_artists() self.image_viewer.plugins.remove(self) self.image_viewer.reset_image() self.image_viewer.redraw() - self.close() - - def connect_image_event(self, event, callback): - """Connect callback with an event in the image viewer. - - This should be used in lieu of `figure.canvas.mpl_connect` since this - function stores call back ids for later clean up. - - Parameters - ---------- - event : str - Matplotlib event. - callback : function - Callback function with a matplotlib Event object as its argument. - """ - cid = self.image_viewer.connect_event(event, callback) - self.cids.append(cid) - - def disconnect_image_events(self): - """Disconnect all events created by this widget.""" - for c in self.cids: - self.image_viewer.disconnect_event(c) def remove_image_artists(self): - """Disconnect artists that are connected to the image viewer.""" + """Remove artists that are connected to the image viewer.""" for a in self.artists: - self.image_viewer.remove_artist(a) + a.remove() diff --git a/skimage/viewer/plugins/canny.py b/skimage/viewer/plugins/canny.py index 7c7bfb3b..c2294ba8 100644 --- a/skimage/viewer/plugins/canny.py +++ b/skimage/viewer/plugins/canny.py @@ -1,5 +1,7 @@ -from skimage.filter import canny +import numpy as np +import skimage +from skimage.filter import canny from .overlayplugin import OverlayPlugin from ..widgets import Slider, ComboBox @@ -12,7 +14,17 @@ class CannyPlugin(OverlayPlugin): def __init__(self, *args, **kwargs): super(CannyPlugin, self).__init__(image_filter=canny, **kwargs) + def attach(self, image_viewer): + image = image_viewer.image + imin, imax = skimage.dtype_limits(image) + itype = 'float' if np.issubdtype(image.dtype, float) else 'int' self.add_widget(Slider('sigma', 0, 5, update_on='release')) - self.add_widget(Slider('low threshold', 0, 255, update_on='release')) - self.add_widget(Slider('high threshold', 0, 255, update_on='release')) + self.add_widget(Slider('low threshold', imin, imax, value_type=itype, + update_on='release')) + self.add_widget(Slider('high threshold', imin, imax, value_type=itype, + update_on='release')) self.add_widget(ComboBox('color', self.color_names, ptype='plugin')) + # Call parent method at end b/c it calls `filter_image`, which needs + # the values specified by the widgets. Alternatively, move call to + # parent method to beginning and add a call to `self.filter_image()` + super(CannyPlugin,self).attach(image_viewer) diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py new file mode 100644 index 00000000..166b2805 --- /dev/null +++ b/skimage/viewer/plugins/color_histogram.py @@ -0,0 +1,68 @@ +import numpy as np +import matplotlib.pyplot as plt + +from skimage import color +from skimage import exposure +from .plotplugin import PlotPlugin +from ..canvastools import RectangleTool + + +class ColorHistogram(PlotPlugin): + name = 'Color Histogram' + + def __init__(self, **kwargs): + super(ColorHistogram, self).__init__(height=400, **kwargs) + + print(self.help()) + + def attach(self, image_viewer): + super(ColorHistogram, self).attach(image_viewer) + + self.rect_tool = RectangleTool(self.ax, on_release=self.ab_selected) + self.lab_image = color.rgb2lab(image_viewer.image) + + # Calculate color histogram in the Lab colorspace: + L, a, b = self.lab_image.T + left, right = -100, 100 + ab_extents = [left, right, right, left] + bins = np.arange(left, right) + hist, x_edges, y_edges = np.histogram2d(a.flatten(), b.flatten(), bins, + normed=True) + + # Clip bin heights that dominate a-b histogram + max_val = pct_total_area(hist, percentile=99) + hist = exposure.rescale_intensity(hist, in_range=(0, max_val)) + self.ax.imshow(hist, extent=ab_extents, cmap=plt.cm.gray) + + self.ax.set_title('Color Histogram') + self.ax.set_xlabel('b') + self.ax.set_ylabel('a') + + def help(self): + helpstr = ("Color Histogram tool:", + "Select region of a-b colorspace to highlight on image.") + return '\n'.join(helpstr) + + def ab_selected(self, extents): + x0, x1, y0, y1 = extents + + lab_masked = self.lab_image.copy() + L, a, b = lab_masked.T + + mask = ((a > y0) & (a < y1)) & ((b > x0) & (b < x1)) + lab_masked[..., 1:][~mask.T] = 0 + + self.image_viewer.image = color.lab2rgb(lab_masked) + + +def pct_total_area(image, percentile=80): + """Return threshold value based on percentage of total area. + + The specified percent of pixels less than the given intensity threshold. + """ + idx = int((image.size - 1) * percentile / 100.0) + sorted_pixels = np.sort(image.flat) + return sorted_pixels[idx] + + + diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py new file mode 100644 index 00000000..61f61034 --- /dev/null +++ b/skimage/viewer/plugins/crop.py @@ -0,0 +1,35 @@ +from .base import Plugin +from ..canvastools import RectangleTool +from skimage.viewer.widgets import SaveButtons + + +__all__ = ['Crop'] + + +class Crop(Plugin): + name = 'Crop' + + def __init__(self, maxdist=10, **kwargs): + super(Crop, self).__init__(**kwargs) + self.maxdist = maxdist + self.add_widget(SaveButtons()) + print(self.help()) + + def attach(self, image_viewer): + super(Crop, self).attach(image_viewer) + + self.rect_tool = RectangleTool(self.image_viewer.ax, + maxdist=self.maxdist, + on_enter=self.crop) + self.artists.append(self.rect_tool) + + def help(self): + helpstr = ("Crop tool", + "Select rectangular region and press enter to crop.") + return '\n'.join(helpstr) + + def crop(self, extents): + xmin, xmax, ymin, ymax = extents + image = self.image_viewer.image[ymin:ymax+1, xmin:xmax+1] + self.image_viewer.image = image + self.image_viewer.ax.relim() diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py new file mode 100644 index 00000000..b3c289f1 --- /dev/null +++ b/skimage/viewer/plugins/labelplugin.py @@ -0,0 +1,63 @@ +import numpy as np + +from .base import Plugin +from ..widgets import ComboBox, Slider +from ..canvastools import PaintTool + + +__all__ = ['LabelPainter'] + + +rad2deg = 180 / np.pi + + +class LabelPainter(Plugin): + name = 'LabelPainter' + + def __init__(self, max_radius=20, **kwargs): + super(LabelPainter, self).__init__(**kwargs) + + # These widgets adjust plugin properties instead of an image filter. + self._radius_widget = Slider('radius', low=1, high=max_radius, + value=5, value_type='int', ptype='plugin') + labels = [str(i) for i in range(6)] + labels[0] = 'Erase' + self._label_widget = ComboBox('label', labels, ptype='plugin') + self.add_widget(self._radius_widget) + self.add_widget(self._label_widget) + + print(self.help()) + + def help(self): + helpstr = ("Label painter", + "Hold left-mouse button and paint on canvas.") + return '\n'.join(helpstr) + + def attach(self, image_viewer): + super(LabelPainter, self).attach(image_viewer) + + image = image_viewer.original_image + self.paint_tool = PaintTool(self.image_viewer.ax, image.shape, + on_enter=self.on_enter) + self.paint_tool.radius = self.radius + self.paint_tool.label = self._label_widget.index = 1 + self.artists.append(self.paint_tool) + + def on_enter(self, overlay): + pass + + @property + def radius(self): + return self._radius_widget.val + + @radius.setter + def radius(self, val): + self.paint_tool.radius = val + + @property + def label(self): + return self._label_widget.val + + @label.setter + def label(self, val): + self.paint_tool.label = val diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 69c9ebfb..c9ceedc5 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -1,14 +1,15 @@ +import warnings + import numpy as np import scipy.ndimage as ndi from skimage.util.dtype import dtype_range from .plotplugin import PlotPlugin +from ..canvastools import ThickLineTool __all__ = ['LineProfile'] -#TODO: Extract line tool and add it to a new `canvastools` subpackage. - class LineProfile(PlotPlugin): """Plugin to compute interpolated intensity under a scan line on an image. @@ -17,10 +18,10 @@ class LineProfile(PlotPlugin): Parameters ---------- - linewidth : float - Line width for interpolation. Wider lines average over more pixels. - epsilon : float + maxdist : float Maximum pixel distance allowed when selecting end point of scan line. + epsilon : float + Deprecated. Use `maxdist` instead. limits : tuple or {None, 'image', 'dtype'} (minimum, maximum) intensity limits for plotted profile. The following special values are defined: @@ -30,17 +31,17 @@ class LineProfile(PlotPlugin): 'dtype' : fixed scale based on min/max intensity of image dtype. """ name = 'Line Profile' - draws_on_image = True - def __init__(self, linewidth=1, epsilon=5, limits='image', **kwargs): + def __init__(self, maxdist=10, epsilon='deprecated', + limits='image', **kwargs): super(LineProfile, self).__init__(**kwargs) - self.linewidth = linewidth - self.epsilon = epsilon - self._active_pt = None + + if not epsilon == 'deprecated': + warnings.warn("Parameter `epsilon` deprecated; use `maxdist`.") + maxdist = epsilon + self.maxdist = maxdist self._limit_type = limits - self.line_kwargs = dict(color='y', lw=linewidth, alpha=0.5, marker='s', - markersize=5, solid_capstyle='butt') - print self.help() + print(self.help()) def attach(self, image_viewer): super(LineProfile, self).attach(image_viewer) @@ -50,7 +51,7 @@ class LineProfile(PlotPlugin): if self._limit_type == 'image': self.limits = (np.min(image), np.max(image)) elif self._limit_type == 'dtype': - self.self._limit_type = dtype_range[image.dtype.type] + self._limit_type = dtype_range[image.dtype.type] elif self._limit_type is None or len(self._limit_type) == 2: self.limits = self._limit_type else: @@ -60,25 +61,19 @@ class LineProfile(PlotPlugin): self.ax.set_ylim(self.limits) h, w = image.shape - self._init_end_pts = np.array([[w / 3, h / 2], [2 * w / 3, h / 2]]) - self.end_pts = self._init_end_pts.copy() + x = [w / 3, 2 * w / 3] + y = [h / 2] * 2 - x, y = np.transpose(self.end_pts) - self.scan_line = image_viewer.ax.plot(x, y, **self.line_kwargs)[0] - self.artists.append(self.scan_line) + self.line_tool = ThickLineTool(self.image_viewer.ax, + maxdist=self.maxdist, + on_move=self.line_changed, + on_change=self.line_changed) + self.line_tool.end_points = np.transpose([x, y]) - scan_data = profile_line(image, self.end_pts) + scan_data = profile_line(image, self.line_tool.end_points) self.profile = self.ax.plot(scan_data, 'k-')[0] self._autoscale_view() - self.connect_image_event('key_press_event', self.on_key_press) - self.connect_image_event('button_press_event', self.on_mouse_press) - self.connect_image_event('button_release_event', self.on_mouse_release) - self.connect_image_event('motion_notify_event', self.on_move) - self.connect_image_event('scroll_event', self.on_scroll) - - self.image_viewer.redraw() - def help(self): helpstr = ("Line profile tool", "+ and - keys or mouse scroll changes width of scan line.", @@ -90,41 +85,13 @@ class LineProfile(PlotPlugin): Returns ------- - end_pts: (2, 2) array + end_points: (2, 2) array The positions ((x1, y1), (x2, y2)) of the line ends. profile: 1d array Profile of intensity values. """ - end_pts = self.scan_line.get_xydata() profile = self.profile.get_ydata() - return end_pts, profile - - def on_scroll(self, event): - if not event.inaxes: - return - if event.button == 'up': - self._thicken_scan_line() - elif event.button == 'down': - self._shrink_scan_line() - - def on_key_press(self, event): - if not event.inaxes: - return - elif event.key == '+': - self._thicken_scan_line() - elif event.key == '-': - self._shrink_scan_line() - elif event.key == 'r': - self.reset() - - def _thicken_scan_line(self): - self.linewidth += 1 - self.line_changed(None, None) - - def _shrink_scan_line(self): - if self.linewidth > 1: - self.linewidth -= 1 - self.line_changed(None, None) + return self.line_tool.end_points, profile def _autoscale_view(self): if self.limits is None: @@ -132,78 +99,32 @@ class LineProfile(PlotPlugin): else: self.ax.autoscale_view(scaley=False, tight=True) - def get_pt_under_cursor(self, event): - """Return index of the end point under cursor, if sufficiently close""" - xy = np.asarray(self.scan_line.get_xydata()) - xyt = self.scan_line.get_transform().transform(xy) - xt, yt = xyt[:, 0], xyt[:, 1] - d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2) - indseq = np.nonzero(np.equal(d, np.amin(d)))[0] - ind = indseq[0] - if d[ind] >= self.epsilon: - ind = None - return ind + def line_changed(self, end_points): + x, y = np.transpose(end_points) + self.line_tool.end_points = end_points + scan = profile_line(self.image_viewer.original_image, end_points, + linewidth=self.line_tool.linewidth) - def on_mouse_press(self, event): - if event.button != 1: - return - if event.inaxes == None: - return - self._active_pt = self.get_pt_under_cursor(event) - - def on_mouse_release(self, event): - if event.button != 1: - return - self._active_pt = None - - def on_move(self, event): - if event.button != 1: - return - if self._active_pt is None: - return - if not self.image_viewer.ax.in_axes(event): - return - x, y = event.xdata, event.ydata - self.line_changed(x, y) - - def reset(self): - self.end_pts = self._init_end_pts.copy() - self.scan_line.set_data(np.transpose(self.end_pts)) - self.line_changed(None, None) - - def line_changed(self, x, y): - if x is not None: - self.end_pts[self._active_pt, :] = x, y - self.scan_line.set_data(np.transpose(self.end_pts)) - self.scan_line.set_linewidth(self.linewidth) - - scan = profile_line(self.image_viewer.original_image, self.end_pts, - linewidth=self.linewidth) self.profile.set_xdata(np.arange(scan.shape[0])) self.profile.set_ydata(scan) self.ax.relim() if self.useblit: - self.image_viewer.canvas.restore_region(self.img_background) - self.ax.draw_artist(self.scan_line) self.ax.draw_artist(self.profile) - self.image_viewer.canvas.blit(self.image_viewer.ax.bbox) self._autoscale_view() - - self.image_viewer.redraw() self.redraw() -def profile_line(img, end_pts, linewidth=1): +def profile_line(img, end_points, linewidth=1): """Return the intensity profile of an image measured along a scan line. Parameters ---------- img : 2d array The image. - end_pts: (2, 2) list + end_points: (2, 2) list End points ((x1, y1), (x2, y2)) of scan line. linewidth: int Width of the scan, perpendicular to the line @@ -214,7 +135,7 @@ def profile_line(img, end_pts, linewidth=1): The intensity profile along the scan line. The length of the profile is the ceil of the computed length of the scan line. """ - point1, point2 = end_pts + point1, point2 = end_points x1, y1 = point1 = np.asarray(point1, dtype=float) x2, y2 = point2 = np.asarray(point2, dtype=float) dx, dy = point2 - point1 diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py new file mode 100644 index 00000000..71412a3b --- /dev/null +++ b/skimage/viewer/plugins/measure.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +import numpy as np + +from .base import Plugin +from ..widgets import Text +from ..canvastools import LineTool + + +__all__ = ['Measure'] + + +rad2deg = 180 / np.pi + + +class Measure(Plugin): + name = 'Measure' + + def __init__(self, maxdist=10, **kwargs): + super(Measure, self).__init__(**kwargs) + + self.maxdist = maxdist + self._length = Text('Length:') + self._angle = Text('Angle:') + + self.add_widget(self._length) + self.add_widget(self._angle) + + print(self.help()) + + def attach(self, image_viewer): + super(Measure, self).attach(image_viewer) + + image = image_viewer.original_image + h, w = image.shape + self.line_tool = LineTool(self.image_viewer.ax, + maxdist=self.maxdist, + on_move=self.line_changed) + self.artists.append(self.line_tool) + + def help(self): + helpstr = ("Measure tool", + "Select line to measure distance and angle.") + return '\n'.join(helpstr) + + def line_changed(self, end_points): + x, y = np.transpose(end_points) + dx = np.diff(x)[0] + dy = np.diff(y)[0] + self._length.text = '%.1f' % np.hypot(dx, dy) + self._angle.text = u'%.1f°' % (180 - np.arctan2(dy, dx) * rad2deg) diff --git a/skimage/viewer/plugins/overlayplugin.py b/skimage/viewer/plugins/overlayplugin.py index f9d37e59..278f072c 100644 --- a/skimage/viewer/plugins/overlayplugin.py +++ b/skimage/viewer/plugins/overlayplugin.py @@ -1,8 +1,19 @@ +from warnings import warn + from skimage.util.dtype import dtype_range from .base import Plugin from ..utils import ClearColormap +__all__ = ['OverlayPlugin'] + + +def recent_mpl_version(): + import matplotlib + version = matplotlib.__version__.split('.') + return int(version[0]) == 1 and int(version[1]) >= 2 + + class OverlayPlugin(Plugin): """Plugin for ImageViewer that displays an overlay on top of main image. @@ -25,6 +36,9 @@ class OverlayPlugin(Plugin): 'cyan': (0, 1, 1)} def __init__(self, **kwargs): + if not recent_mpl_version(): + msg = "Matplotlib >= 1.2 required for OverlayPlugin." + warn(RuntimeWarning(msg)) super(OverlayPlugin, self).__init__(**kwargs) self._overlay_plot = None self._overlay = None @@ -74,6 +88,14 @@ class OverlayPlugin(Plugin): self._overlay_plot.set_cmap(self.cmap) self.image_viewer.redraw() + @property + def filtered_image(self): + """Return filtered image. + + This "filtered image" is used when saving from the plugin. + """ + return self.overlay + def display_filtered_image(self, image): """Display filtered image as an overlay on top of image in viewer.""" self.overlay = image diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index fa06088d..850d0b71 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -1,22 +1,16 @@ import numpy as np -from PyQt4 import QtGui -import matplotlib.pyplot as plt +try: + from PyQt4 import QtGui +except ImportError: + print("Could not import PyQt4 -- skimage.viewer not available.") -from ..utils import MatplotlibCanvas +from ..utils import new_plot from .base import Plugin -class PlotCanvas(MatplotlibCanvas): - """Canvas for displaying images. +__all__ = ['PlotPlugin'] - This canvas derives from Matplotlib, and has attributes `fig` and `ax`, - which point to Matplotlib figure and axes. - """ - def __init__(self, parent, height, width, **kwargs): - self.fig, self.ax = plt.subplots(figsize=(height, width), **kwargs) - super(PlotCanvas, self).__init__(parent, self.fig, **kwargs) - self.setMinimumHeight(150) class PlotPlugin(Plugin): """Plugin for ImageViewer that contains a plot canvas. @@ -37,8 +31,9 @@ class PlotPlugin(Plugin): self.canvas.draw_idle() def add_plot(self, height=4, width=4): - self.canvas = PlotCanvas(self, height, width) - self.fig = self.canvas.fig + self.fig, self.ax = new_plot(figsize=(height, width)) + self.canvas = self.fig.canvas + self.canvas.setMinimumHeight(150) #TODO: Converted color is slightly different than Qt background. qpalette = QtGui.QPalette() qcolor = qpalette.color(QtGui.QPalette.Window) @@ -46,5 +41,4 @@ class PlotPlugin(Plugin): if np.isscalar(bgcolor): bgcolor = str(bgcolor / 255.) self.fig.patch.set_facecolor(bgcolor) - self.ax = self.canvas.ax self.layout.addWidget(self.canvas, self.row, 0) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index cf632d5a..38bf7c6c 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -3,8 +3,11 @@ import warnings import numpy as np try: - import matplotlib.pyplot as plt + import matplotlib as mpl + from matplotlib.figure import Figure + from matplotlib import _pylab_helpers from matplotlib.colors import LinearSegmentedColormap + from matplotlib.backends.backend_qt4 import FigureManagerQT from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg except ImportError: FigureCanvasQTAgg = object # hack to prevent nosetest and autodoc errors @@ -18,7 +21,7 @@ except ImportError: __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', - 'LinearColormap', 'ClearColormap', 'MatplotlibCanvas'] + 'LinearColormap', 'ClearColormap', 'FigureCanvas', 'new_plot'] QApp = None @@ -55,38 +58,6 @@ class RequiredAttr(object): self.val = val -def figimage(image, scale=1, dpi=None, **kwargs): - """Return figure and axes with figure tightly surrounding image. - - Unlike pyplot.figimage, this actually plots onto an axes object, which - fills the figure. Plotting the image onto an axes allows for subsequent - overlays of axes artists. - - Parameters - ---------- - image : array - image to plot - scale : float - If scale is 1, the figure and axes have the same dimension as the - image. Smaller values of `scale` will shrink the figure. - dpi : int - Dots per inch for figure. If None, use the default rcParam. - """ - dpi = dpi if dpi is not None else plt.rcParams['figure.dpi'] - kwargs.setdefault('interpolation', 'nearest') - kwargs.setdefault('cmap', 'gray') - - h, w, d = np.atleast_3d(image).shape - figsize = np.array((w, h), dtype=float) / dpi * scale - - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) - fig.subplots_adjust(left=0, bottom=0, right=1, top=1) - - ax.set_axis_off() - ax.imshow(image, **kwargs) - return fig, ax - - class LinearColormap(LinearSegmentedColormap): """LinearSegmentedColormap in which color varies smoothly. @@ -124,14 +95,85 @@ class ClearColormap(LinearColormap): LinearColormap.__init__(self, name, cg_speq) -class MatplotlibCanvas(FigureCanvasQTAgg): +class FigureCanvas(FigureCanvasQTAgg): """Canvas for displaying images.""" - def __init__(self, parent, figure, **kwargs): + def __init__(self, figure, **kwargs): self.fig = figure FigureCanvasQTAgg.__init__(self, self.fig) FigureCanvasQTAgg.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) - # Note: `setParent` must be called after `FigureCanvasQTAgg.__init__`. - self.setParent(parent) + + def resizeEvent(self, event): + FigureCanvasQTAgg.resizeEvent(self, event) + # Call to `resize_event` missing in FigureManagerQT. + # See https://github.com/matplotlib/matplotlib/pull/1585 + self.resize_event() + + +def new_canvas(*args, **kwargs): + """Return a new figure canvas.""" + allnums = _pylab_helpers.Gcf.figs.keys() + num = max(allnums) + 1 if allnums else 1 + + FigureClass = kwargs.pop('FigureClass', Figure) + figure = FigureClass(*args, **kwargs) + canvas = FigureCanvas(figure) + fig_manager = FigureManagerQT(canvas, num) + return fig_manager.canvas + + +def new_plot(parent=None, subplot_kw=None, **fig_kw): + """Return new figure and axes. + + Parameters + ---------- + parent : QtWidget + Qt widget that displays the plot objects. If None, you must manually + call ``canvas.setParent`` and pass the parent widget. + subplot_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure.add_subplot``. + fig_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure``. + """ + if subplot_kw is None: + subplot_kw = {} + canvas = new_canvas(**fig_kw) + canvas.setParent(parent) + + fig = canvas.figure + ax = fig.add_subplot(1, 1, 1, **subplot_kw) + return fig, ax + + +def figimage(image, scale=1, dpi=None, **kwargs): + """Return figure and axes with figure tightly surrounding image. + + Unlike pyplot.figimage, this actually plots onto an axes object, which + fills the figure. Plotting the image onto an axes allows for subsequent + overlays of axes artists. + + Parameters + ---------- + image : array + image to plot + scale : float + If scale is 1, the figure and axes have the same dimension as the + image. Smaller values of `scale` will shrink the figure. + dpi : int + Dots per inch for figure. If None, use the default rcParam. + """ + dpi = dpi if dpi is not None else mpl.rcParams['figure.dpi'] + kwargs.setdefault('interpolation', 'nearest') + kwargs.setdefault('cmap', 'gray') + + h, w, d = np.atleast_3d(image).shape + figsize = np.array((w, h), dtype=float) / dpi * scale + + fig, ax = new_plot(figsize=figsize, dpi=dpi) + fig.subplots_adjust(left=0, bottom=0, right=1, top=1) + + ax.set_axis_off() + ax.imshow(image, **kwargs) + return fig, ax diff --git a/skimage/viewer/utils/dialogs.py b/skimage/viewer/utils/dialogs.py new file mode 100644 index 00000000..849fcad7 --- /dev/null +++ b/skimage/viewer/utils/dialogs.py @@ -0,0 +1,26 @@ +import os + +try: + from PyQt4 import QtGui +except ImportError: + print("Could not import PyQt4 -- skimage.viewer not available.") + + +def open_file_dialog(default_format='png'): + """Return user-selected file path.""" + filename = str(QtGui.QFileDialog.getOpenFileName()) + if len(filename) == 0: + return None + return filename + + +def save_file_dialog(default_format='png'): + """Return user-selected file path.""" + filename = str(QtGui.QFileDialog.getSaveFileName()) + if len(filename) == 0: + return None + #TODO: io plugins should assign default image formats + basename, ext = os.path.splitext(filename) + if not ext: + filename = '%s.%s' % (filename, default_format) + return filename diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index e11967f7..193c5114 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -8,19 +8,28 @@ except ImportError: QMainWindow = object # hack to prevent nosetest and autodoc errors print("Could not import PyQt4 -- skimage.viewer not available.") +from skimage import io, img_as_float from skimage.util.dtype import dtype_range +from skimage.exposure import rescale_intensity +import numpy as np from .. import utils from ..widgets import Slider +from ..utils import dialogs __all__ = ['ImageViewer', 'CollectionViewer'] -class ImageCanvas(utils.MatplotlibCanvas): - """Canvas for displaying images.""" - def __init__(self, parent, image, **kwargs): - self.fig, self.ax = utils.figimage(image, **kwargs) - super(ImageCanvas, self).__init__(parent, self.fig, **kwargs) +def mpl_image_to_rgba(mpl_image): + """Return RGB image from the given matplotlib image object. + + Each image in a matplotlib figure has it's own colormap and normalization + function. Return RGBA (RGB + alpha channel) image with float dtype. + """ + input_range = (mpl_image.norm.vmin, mpl_image.norm.vmax) + image = rescale_intensity(mpl_image.get_array(), in_range=input_range) + image = mpl_image.cmap(img_as_float(image)) # cmap complains on bool arrays + return img_as_float(image) class ImageViewer(QMainWindow): @@ -65,16 +74,21 @@ class ImageViewer(QMainWindow): self.setWindowTitle("Image Viewer") self.file_menu = QtGui.QMenu('&File', self) - self.file_menu.addAction('&Quit', self.close, + self.file_menu.addAction('Open file', self.open_file, + QtCore.Qt.CTRL + QtCore.Qt.Key_O) + self.file_menu.addAction('Save to file', self.save_to_file, + QtCore.Qt.CTRL + QtCore.Qt.Key_S) + self.file_menu.addAction('Quit', self.close, QtCore.Qt.CTRL + QtCore.Qt.Key_Q) self.menuBar().addMenu(self.file_menu) self.main_widget = QtGui.QWidget() self.setCentralWidget(self.main_widget) - self.canvas = ImageCanvas(self.main_widget, image) - self.fig = self.canvas.fig - self.ax = self.canvas.ax + self.fig, self.ax = utils.figimage(image) + self.canvas = self.fig.canvas + self.canvas.setParent(self) + self.ax.autoscale(enable=False) self._image_plot = self.ax.images[0] @@ -83,14 +97,6 @@ class ImageViewer(QMainWindow): self.image = image.copy() self.plugins = [] - # List of axes artists to check for removal. - self._axes_artists = [self.ax.artists, - self.ax.collections, - self.ax.images, - self.ax.lines, - self.ax.patches, - self.ax.texts] - self.layout = QtGui.QVBoxLayout(self.main_widget) self.layout.addWidget(self.canvas) @@ -107,6 +113,43 @@ class ImageViewer(QMainWindow): plugin.attach(self) return self + def open_file(self): + """Open image file and display in viewer.""" + filename = dialogs.open_file_dialog() + if filename is None: + return + image = io.imread(filename) + self.original_image = image # update saved image + self.image = image # update displayed image + + def save_to_file(self): + """Save current image to file. + + The current behavior is not ideal: It saves the image displayed on + screen, so all images will be converted to RGB, and the image size is + not preserved (resizing the viewer window will alter the size of the + saved image). + """ + filename = dialogs.save_file_dialog() + if filename is None: + return + if len(self.ax.images) == 1: + io.imsave(filename, self.image) + else: + underlay = mpl_image_to_rgba(self.ax.images[0]) + overlay = mpl_image_to_rgba(self.ax.images[1]) + alpha = overlay[:, :, 3] + + # alpha can be set by channel of array or by a scalar value. + # Prefer the alpha channel, but fall back to scalar value. + if np.all(alpha == 1): + alpha = np.ones_like(alpha) * self.ax.images[1].get_alpha() + + alpha = alpha[:, :, np.newaxis] + composite = (overlay[:, :, :3] * alpha + + underlay[:, :, :3] * (1 - alpha)) + io.imsave(filename, composite) + def closeEvent(self, event): self.close() @@ -143,10 +186,21 @@ class ImageViewer(QMainWindow): def image(self, image): self._img = image self._image_plot.set_array(image) + + # Adjust size if new image shape doesn't match the original + h, w = image.shape[:2] + # update data coordinates (otherwise pixel coordinates are off) + self._image_plot.set_extent((0, w, h, 0)) + # update display (otherwise image doesn't fill the canvas) + self.ax.set_xlim(0, w) + self.ax.set_ylim(h, 0) + + # update color range clim = dtype_range[image.dtype.type] if clim[0] < 0 and image.min() >= 0: clim = (0, clim[1]) self._image_plot.set_clim(clim) + self.redraw() def reset_image(self): @@ -161,25 +215,6 @@ class ImageViewer(QMainWindow): """Disconnect callback by its id (returned by `connect_event`).""" self.canvas.mpl_disconnect(callback_id) - def remove_artist(self, artist): - """Disconnect matplotlib artist from image viewer. - - The `closeEvent` method of a Plugin should remove artists (Matplotlib - lines, markers, etc.) from the viewer so that they aren't stranded. - - Parameters - ---------- - artist : Matplotlib Artist - Artists created by Matplotlib functions (e.g., `plot` returns list - of `Line2D` artists) should be saved by the plugin for removal. - """ - # Note: an `add_artist` method is unnecessary since Matplotlib - - # There's probably a smarter way to find where the artist is stored. - for artist_list in self._axes_artists: - if artist in artist_list: - artist_list.remove(artist) - def _update_status_bar(self, event): if event.inaxes and event.inaxes.get_navigate(): self.status_message(self._format_coord(event.xdata, event.ydata)) @@ -274,6 +309,8 @@ class CollectionViewer(ImageViewer): if 48 <= key < 58: index = 0.1 * int(key - 48) * self.num_images self.update_index('', index) - event.accept() + event.accept() + else: + event.ignore() else: event.ignore() diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 9382652b..14259f1e 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -27,7 +27,7 @@ except ImportError: from ..utils import RequiredAttr -__all__ = ['BaseWidget', 'Slider', 'ComboBox'] +__all__ = ['BaseWidget', 'Slider', 'ComboBox', 'Text'] class BaseWidget(QWidget): @@ -50,6 +50,28 @@ class BaseWidget(QWidget): self.callback(self.name, value) +class Text(BaseWidget): + + def __init__(self, name=None, text=''): + super(Text, self).__init__(name) + self._label = QtGui.QLabel() + self.text = text + self.layout = QtGui.QHBoxLayout(self) + if name is not None: + name_label = QtGui.QLabel() + name_label.setText(name) + self.layout.addWidget(name_label) + self.layout.addWidget(self._label) + + @property + def text(self): + return self._label.text() + + @text.setter + def text(self, text_str): + self._label.setText(text_str) + + class Slider(BaseWidget): """Slider widget for adjusting numeric parameters. @@ -131,6 +153,7 @@ class Slider(BaseWidget): self.slider.sliderReleased.connect(self._on_slider_changed) else: raise ValueError("Unexpected value %s for 'update_on'" % update_on) + self.slider.setFocusPolicy(QtCore.Qt.StrongFocus) self.name_label = QtGui.QLabel() self.name_label.setText(self.name) @@ -227,3 +250,11 @@ class ComboBox(BaseWidget): @property def val(self): return self._combo_box.value() + + @property + def index(self): + return self._combo_box.currentIndex() + + @index.setter + def index(self, i): + self._combo_box.setCurrentIndex(i) diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index efc8a21c..e3aaa938 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -1,13 +1,16 @@ -import os from textwrap import dedent try: - from PyQt4 import QtGui + from PyQt4 import QtGui, QtCore except ImportError: print("Could not import PyQt4 -- skimage.viewer not available.") +import numpy as np + +import skimage from skimage import io from .core import BaseWidget +from ..utils import dialogs __all__ = ['OKCancelButtons', 'SaveButtons'] @@ -26,9 +29,11 @@ class OKCancelButtons(BaseWidget): self.ok = QtGui.QPushButton('OK') self.ok.clicked.connect(self.update_original_image) self.ok.setMaximumWidth(button_width) + self.ok.setFocusPolicy(QtCore.Qt.NoFocus) self.cancel = QtGui.QPushButton('Cancel') self.cancel.clicked.connect(self.close_plugin) self.cancel.setMaximumWidth(button_width) + self.cancel.setFocusPolicy(QtCore.Qt.NoFocus) self.layout = QtGui.QHBoxLayout(self) self.layout.addStretch() @@ -58,8 +63,10 @@ class SaveButtons(BaseWidget): self.save_file = QtGui.QPushButton('File') self.save_file.clicked.connect(self.save_to_file) + self.save_file.setFocusPolicy(QtCore.Qt.NoFocus) self.save_stack = QtGui.QPushButton('Stack') self.save_stack.clicked.connect(self.save_to_stack) + self.save_stack.setFocusPolicy(QtCore.Qt.NoFocus) self.layout = QtGui.QHBoxLayout(self) self.layout.addWidget(self.name_label) @@ -67,7 +74,7 @@ class SaveButtons(BaseWidget): self.layout.addWidget(self.save_file) def save_to_stack(self): - image = self.plugin.image_viewer.image.copy() + image = self.plugin.filtered_image.copy() io.push(image) msg = dedent('''\ @@ -77,14 +84,14 @@ class SaveButtons(BaseWidget): notify(msg) def save_to_file(self): - filename = str(QtGui.QFileDialog.getSaveFileName()) - if len(filename) == 0: + filename = dialogs.save_file_dialog() + if filename is None: return - #TODO: io plugins should assign default image formats - basename, ext = os.path.splitext(filename) - if not ext: - filename = '%s.%s' % (filename, self.default_format) - io.imsave(filename, self.plugin.image_viewer.image) + image = self.plugin.filtered_image + if image.dtype == np.bool: + #TODO: This check/conversion should probably be in `imsave`. + image = skimage.img_as_ubyte(image) + io.imsave(filename, image) def notify(msg): diff --git a/viewer_examples/plugins/canny_simple.py b/viewer_examples/plugins/canny_simple.py index 49bc15eb..912401e1 100644 --- a/viewer_examples/plugins/canny_simple.py +++ b/viewer_examples/plugins/canny_simple.py @@ -3,18 +3,22 @@ from skimage.filter import canny from skimage.viewer import ImageViewer from skimage.viewer.widgets import Slider +from skimage.viewer.widgets.history import SaveButtons from skimage.viewer.plugins.overlayplugin import OverlayPlugin image = data.camera() -# Note: ImageViewer must be called before Plugin b/c it starts the event loop. -viewer = ImageViewer(image) + # You can create a UI for a filter just by passing a filter function... plugin = OverlayPlugin(image_filter=canny) # ... and adding widgets to adjust parameter values. plugin += Slider('sigma', 0, 5, update_on='release') plugin += Slider('low threshold', 0, 255, update_on='release') plugin += Slider('high threshold', 0, 255, update_on='release') -# Finally, attach the plugin to the image viewer. +# ... and we can also add buttons to save the overlay: +plugin += SaveButtons(name='Save overlay to:') + +# Finally, attach the plugin to an image viewer. +viewer = ImageViewer(image) viewer += plugin viewer.show() diff --git a/viewer_examples/plugins/color_histogram.py b/viewer_examples/plugins/color_histogram.py new file mode 100644 index 00000000..0b654b3e --- /dev/null +++ b/viewer_examples/plugins/color_histogram.py @@ -0,0 +1,9 @@ +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.color_histogram import ColorHistogram +from skimage import data + + +image = data.load('color.png') +viewer = ImageViewer(image) +viewer += ColorHistogram() +viewer.show() diff --git a/viewer_examples/plugins/croptool.py b/viewer_examples/plugins/croptool.py new file mode 100644 index 00000000..0eb44903 --- /dev/null +++ b/viewer_examples/plugins/croptool.py @@ -0,0 +1,9 @@ +from skimage import data +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.crop import Crop + + +image = data.camera() +viewer = ImageViewer(image) +viewer += Crop() +viewer.show() diff --git a/viewer_examples/plugins/measure.py b/viewer_examples/plugins/measure.py new file mode 100644 index 00000000..6200a717 --- /dev/null +++ b/viewer_examples/plugins/measure.py @@ -0,0 +1,9 @@ +from skimage import data +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.measure import Measure + + +image = data.camera() +viewer = ImageViewer(image) +viewer += Measure() +viewer.show() diff --git a/viewer_examples/plugins/median_filter.py b/viewer_examples/plugins/median_filter.py index 3a050382..a20ad8f3 100644 --- a/viewer_examples/plugins/median_filter.py +++ b/viewer_examples/plugins/median_filter.py @@ -2,8 +2,7 @@ from skimage import data from skimage.filter import median_filter from skimage.viewer import ImageViewer -from skimage.viewer.widgets import Slider -from skimage.viewer.widgets.history import OKCancelButtons, SaveButtons +from skimage.viewer.widgets import Slider, OKCancelButtons, SaveButtons from skimage.viewer.plugins.base import Plugin diff --git a/viewer_examples/plugins/watershed_demo.py b/viewer_examples/plugins/watershed_demo.py new file mode 100644 index 00000000..683e8a30 --- /dev/null +++ b/viewer_examples/plugins/watershed_demo.py @@ -0,0 +1,42 @@ +import matplotlib.pyplot as plt + +from skimage import data +from skimage import filter +from skimage import morphology +from skimage.viewer import ImageViewer +from skimage.viewer.widgets import history +from skimage.viewer.plugins.labelplugin import LabelPainter + +class OKCancelButtons(history.OKCancelButtons): + + def update_original_image(self): + # OKCancelButtons updates the original image with the filtered image + # by default. Override this method to update the overlay. + self.plugin._show_watershed() + self.plugin.close() + + +class WatershedPlugin(LabelPainter): + + def help(self): + helpstr = ("Watershed plugin", + "----------------", + "Use mouse to paint each region with a different label.", + "Press OK to display segmented image.") + return '\n'.join(helpstr) + + def _show_watershed(self): + viewer = self.image_viewer + edge_image = filter.sobel(viewer.image) + labels = morphology.watershed(edge_image, self.paint_tool.overlay) + viewer.ax.imshow(labels, cmap=plt.cm.jet, alpha=0.5) + viewer.redraw() + + +image = data.coins() +plugin = WatershedPlugin() +plugin += OKCancelButtons() + +viewer = ImageViewer(image) +viewer += plugin +viewer.show() diff --git a/viewer_examples/viewers/collection_viewer.py b/viewer_examples/viewers/collection_viewer.py index 62cdbb26..61df3dd9 100644 --- a/viewer_examples/viewers/collection_viewer.py +++ b/viewer_examples/viewers/collection_viewer.py @@ -18,7 +18,6 @@ home/end keys First/last image in collection. """ -import numpy as np from skimage import data from skimage.viewer import CollectionViewer from skimage.transform import pyramid_gaussian