diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 5f18e821..c73b14e2 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -107,7 +107,7 @@ - Johannes Schönberger Drawing functions, adaptive thresholding, regionprops, geometric - transformations, LBPs, polygon approximations, and more. + transformations, LBPs, polygon approximations, web layout, and more. - Pavel Campr Fixes and tests for Histograms of Oriented Gradients. diff --git a/DEVELOPMENT.txt b/DEVELOPMENT.txt index 0dd0a102..509cf322 100644 --- a/DEVELOPMENT.txt +++ b/DEVELOPMENT.txt @@ -4,8 +4,8 @@ Development process :doc:`Read this overview ` of how to use Git with ``skimage``. Here's the long and short of it: - * Go to `https://github.com/scikits-image/scikits-image - `_ and follow the + * Go to `https://github.com/scikit-image/scikit-image + `_ and follow the instructions on making your own fork. * Create a new branch for the feature you want to work on. Since the branch name will appear in the merge message, use a sensible name @@ -42,7 +42,7 @@ Guidelines * Follow the `Python PEPs `_ where possible. * No major changes should be committed without review. Ask on the - `mailing list `_ if + `mailing list `_ if you get no response to your pull request. * Examples in the gallery should have a maximum figure width of 8 inches. @@ -99,4 +99,4 @@ detailing the test coverage:: Bugs ```` -Please `report bugs on Github `_. +Please `report bugs on Github `_. diff --git a/LICENSE.txt b/LICENSE.txt index b75d150d..6586c853 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ Unless otherwise specified by LICENSE.txt files in individual directories, all code is -Copyright (C) 2011, the scikits-image team +Copyright (C) 2011, the scikit-image team All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index a419b83a..508db396 100644 --- a/README.rst +++ b/README.rst @@ -3,11 +3,11 @@ Image Processing SciKit Source ------ -https://github.com/scikits-image/scikits-image +https://github.com/scikit-image/scikit-image Mailing List ------------ -http://groups.google.com/group/scikits-image +http://groups.google.com/group/scikit-image Installation from source ------------------------ diff --git a/RELEASE.txt b/RELEASE.txt index 96cd2abc..ce05e24e 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -38,7 +38,7 @@ How to make a new release of ``skimage`` - In ``bento.info``, set to ``0.X.dev0``. - Update the web frontpage: - The webpage is kept in a separate repo: scikits-image-web + The webpage is kept in a separate repo: scikit-image-web - Sync your branch with the remote repo: ``git pull``. If you try to ``make gh-pages`` when your branch is out of sync, it diff --git a/TASKS.txt b/TASKS.txt index da715573..a2fcffe2 100644 --- a/TASKS.txt +++ b/TASKS.txt @@ -16,122 +16,14 @@ How to contribute to ``skimage`` Developing Open Source is great fun! Join us on the `skimage mailing -list `_ and tell us which of the +list `_ and tell us which of the following challenges you'd like to solve. * Mentoring is available for those new to scientific programming in Python. * The technical detail of the `development process`_ is given below. * :doc:`How to use GitHub ` when developing skimage +* If you're looking something to implement, you can find a list of `requested features on github `__. In addition, you can browse the `open issues on github `__. .. contents:: :local: -Tasks ------ - -.. :doc:`gsoc2011` -.. :doc:`coverage_table` - -Implement Algorithms -```````````````````` -- Graph cut segmentation -- `Image colorization `__ -- Fast 2D convex hull (consider using CellProfiler version) - `Algorithm overview `__. - `One free implementation - `_. - [Compare against current implementation] -- Convex hulls of objects in a labels matrix (simply adapt current convex hull - image code--this one's low hanging fruit). Generalise this solution to also - skeletonize objects in a labels matrix. -- Add binary features (BRIEF, BRISK, FREAK) -- Add `STAR features `__ -- `Blurring kernel estimation `__ - -Drawing (directly on an ndarray) -```````````````````````````````` -- Wu's algorithm for circles -- Text rendering -- Add anti-aliasing - -Infrastructure --------------- -- Add @greyimage decorator to check if input is a greyscale image -- :strike:`Implement a new backend system so that we may start including - PyOpenCL-based algorithms` - -Adapt existing code for use -``````````````````````````` -These snippets and packages have already been written. Some need to be -modified to work as part of the scikit, others may be lacking in documentation -or tests. - - * :strike:`Connected components` - * Nadav's bilateral filtering (first compare against CellProfiler's - code, based on http://groups.csail.mit.edu/graphics/bilagrid/bilagrid_web.pdf) - Also see https://github.com/stefanv/scikits-image/tree/bilateral - * 2D image warping via thin-plate splines [ask Zach Pincus] - -Merge code provided by `CellProfiler `_ team -````````````````````````````````````````````````````````````````````````` -* Roberts filter - convolution with diagonal and anti-diagonal - kernels to detect edges -* Minimum enclosing circles of objects in a labels matrix -* spur removal, thinning, thickening, and other morphological operations on - binary images, framework for creating arbitrary morphological operations - using a 3x3 grid. - -Their SVN repository is read-accessible at - -- https://svn.broadinstitute.org/CellProfiler/trunk/CellProfiler/cellprofiler - -The files for the above algorithms are - -- https://svn.broadinstitute.org/CellProfiler/trunk/CellProfiler/cellprofiler/cpmath/cpmorphology.py -- https://svn.broadinstitute.org/CellProfiler/trunk/CellProfiler/cellprofiler/cpmath/filter.py - -There are test suites for the files at - -- https://svn.broadinstitute.org/CellProfiler/trunk/CellProfiler/cellprofiler/cpmath/tests/test_cpmorphology.py -- https://svn.broadinstitute.org/CellProfiler/trunk/CellProfiler/cellprofiler/cpmath/tests/test_filter.py - -Quoting a message from Lee Kamentsky to Stefan van der Walt sent on -5 August 2009:: - - We're part of the Broad Institute which is non-profit. We would be happy - to include our algorithm code in SciPy under the BSD license since that is - more appropriate for a library that might be integrated into a - commercial product whereas CellProfiler needs the more stringent - protection of GPL as an application. - -In 2010, Vebjorn Ljosa officially released parts of the code under a -BSD license (:doc:`cell_profiler` | `original message -`_). - -Thanks to Lee Kamentsky, Thouis Jones and Anne Carpenter and their colleagues -who contributed. - -Rework linear filters -````````````````````` -* Fast, SSE2 convolution (high priority) (see prototype in pull requests) -* Should take kernel or function for parameter (currently only takes function) -* Kernel shape should be specifiable (currently defaults to image shape) - -io -`` -* Update ``qt_plugin.py`` and other plugins to view collections. -* Rewrite GTK backend using GObject Introspection for Py3K compatibility. -* Add DICOM plugin for `GDCM `__. -* Add ``imread_collection`` to all ``imread`` backends -* Better video loading (move to plugin framework, add backends) - -viewer -`````` -* Using the visualization tools, add an FFT-domain image editor - -docs -```` -* Add examples to the gallery -* Write topics for the `user guide - `_ -* Integrate BiBTeX plugin into Sphinx build diff --git a/bento.info b/bento.info index 3e630bc0..bd831849 100644 --- a/bento.info +++ b/bento.info @@ -1,15 +1,15 @@ -Name: scikits-image +Name: scikit-image Version: 0.8.dev0 Summary: Image processing routines for SciPy -Url: http://scikits-image.org -DownloadUrl: http://github.com/scikits-image/scikits-image +Url: http://scikit-image.org +DownloadUrl: http://github.com/scikit-image/scikit-image Description: Image Processing SciKit Image processing algorithms for SciPy, including IO, morphology, filtering, warping, color manipulation, object detection, etc. Please refer to the online documentation at - http://scikits-image.org/ + http://scikit-image.org/ Maintainer: Stefan van der Walt MaintainerEmail: stefan@sun.ac.za License: Modified BSD @@ -64,6 +64,9 @@ Library: Extension: skimage.filter._ctmf Sources: skimage/filter/_ctmf.pyx + Extension: skimage.filter._denoise + Sources: + skimage/filter/_denoise.pyx Extension: skimage.morphology.ccomp Sources: skimage/morphology/ccomp.pyx diff --git a/check_bento_build.py b/check_bento_build.py index d085b3ba..108b0aad 100644 --- a/check_bento_build.py +++ b/check_bento_build.py @@ -69,7 +69,8 @@ def print_results(cy_bento, cy_setup): print(text) print('-' * len(text)) - print # blank line; just for aesthetics + print "Bento errors:" + print "-------------" if cy_bento: info("The following extensions in 'bento.info' were not found:") diff --git a/doc/Makefile b/doc/Makefile index f750fb18..7552be6b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -77,17 +77,17 @@ qthelp: @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in build/qthelp, like this:" - @echo "# qcollectiongenerator build/qthelp/scikitsimage.qhcp" + @echo "# qcollectiongenerator build/qthelp/scikitimage.qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile build/qthelp/scikitsimage.qhc" + @echo "# assistant -collectionFile build/qthelp/scikitimage.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(DEST)/devhelp @echo @echo "Build finished." @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/scikitsimage" - @echo "# ln -s build/devhelp $$HOME/.local/share/devhelp/scikitsimage" + @echo "# mkdir -p $$HOME/.local/share/devhelp/scikitimage" + @echo "# ln -s build/devhelp $$HOME/.local/share/devhelp/scikitimage" @echo "# devhelp" latex: diff --git a/doc/examples/plot_denoise.py b/doc/examples/plot_denoise.py new file mode 100644 index 00000000..8f02f21b --- /dev/null +++ b/doc/examples/plot_denoise.py @@ -0,0 +1,66 @@ +""" +============================= +Denoising the picture of Lena +============================= + +In this example, we denoise a noisy version of the picture of Lena using the +total variation and bilateral denoising filter. + +These algorithms typically produce "posterized" images with flat domains +separated by sharp edges. It is possible to change the degree of posterization +by controlling the tradeoff between denoising and faithfulness to the original +image. + +Total variation filter +---------------------- + +The result of this filter is an image that has a minimal total variation norm, +while being as close to the initial image as possible. The total variation is +the L1 norm of the gradient of the image. + +Bilateral filter +---------------- + +A bilateral filter is an edge-preserving and noise reducing filter. It averages +pixels based on their spatial closeness and radiometric similarity. + +""" + +import numpy as np +import matplotlib.pyplot as plt + +from skimage import data, color, img_as_float +from skimage.filter import denoise_tv, denoise_bilateral + +lena = img_as_float(data.lena()) +lena = lena[220:300, 220:320] + +noisy = lena + 0.5 * lena.std() * np.random.random(lena.shape) +noisy = np.clip(noisy, 0, 1) + +fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(8, 5)) + +ax[0, 0].imshow(noisy) +ax[0, 0].axis('off') +ax[0, 0].set_title('noisy') +ax[0, 1].imshow(denoise_tv(noisy, weight=0.1)) +ax[0, 1].axis('off') +ax[0, 1].set_title('TV') +ax[0, 2].imshow(denoise_bilateral(noisy, sigma_range=0.03, sigma_spatial=15)) +ax[0, 2].axis('off') +ax[0, 2].set_title('Bilateral') + +ax[1, 0].imshow(denoise_tv(noisy, weight=0.2)) +ax[1, 0].axis('off') +ax[1, 0].set_title('(more) TV') +ax[1, 1].imshow(denoise_bilateral(noisy, sigma_range=0.06, sigma_spatial=15)) +ax[1, 1].axis('off') +ax[1, 1].set_title('(more) Bilateral') +ax[1, 2].imshow(lena) +ax[1, 2].axis('off') +ax[1, 2].set_title('original') + +fig.subplots_adjust(wspace=0.02, hspace=0.2, + top=0.9, bottom=0.05, left=0, right=1) + +plt.show() diff --git a/doc/examples/plot_lena_tv_denoise.py b/doc/examples/plot_lena_tv_denoise.py deleted file mode 100644 index bce07845..00000000 --- a/doc/examples/plot_lena_tv_denoise.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -==================================================== -Denoising the picture of Lena using total variation -==================================================== - -In this example, we denoise a noisy version of the picture of Lena -using the total variation denoising filter. The result of this filter -is an image that has a minimal total variation norm, while being as -close to the initial image as possible. The total variation is the L1 -norm of the gradient of the image, and minimizing the total variation -typically produces "posterized" images with flat domains separated by -sharp edges. - -It is possible to change the degree of posterization by controlling -the tradeoff between denoising and faithfulness to the original image. - -""" - -import numpy as np -import matplotlib.pyplot as plt - -from skimage import data, color, img_as_ubyte -from skimage.filter import tv_denoise - -l = img_as_ubyte(color.rgb2gray(data.lena())) -l = l[230:290, 220:320] - -noisy = l + 0.4 * l.std() * np.random.random(l.shape) - -tv_denoised = tv_denoise(noisy, weight=10) - -plt.figure(figsize=(8, 2)) - -plt.subplot(131) -plt.imshow(noisy, cmap=plt.cm.gray, vmin=40, vmax=220) -plt.axis('off') -plt.title('noisy', fontsize=20) -plt.subplot(132) -plt.imshow(tv_denoised, cmap=plt.cm.gray, vmin=40, vmax=220) -plt.axis('off') -plt.title('TV denoising', fontsize=20) - -tv_denoised = tv_denoise(noisy, weight=50) -plt.subplot(133) -plt.imshow(tv_denoised, cmap=plt.cm.gray, vmin=40, vmax=220) -plt.axis('off') -plt.title('(more) TV denoising', fontsize=20) - -plt.subplots_adjust(wspace=0.02, hspace=0.02, top=0.9, bottom=0, left=0, - right=1) -plt.show() diff --git a/doc/examples/plot_segmentations.py b/doc/examples/plot_segmentations.py index 99bfefcc..418a0903 100644 --- a/doc/examples/plot_segmentations.py +++ b/doc/examples/plot_segmentations.py @@ -63,8 +63,8 @@ import matplotlib.pyplot as plt import numpy as np from skimage.data import lena -from skimage.segmentation import felzenszwalb, \ - visualize_boundaries, slic, quickshift +from skimage.segmentation import felzenszwalb, slic, quickshift +from skimage.segmentation import mark_boundaries from skimage.util import img_as_float img = img_as_float(lena()[::2, ::2]) @@ -80,11 +80,11 @@ fig, ax = plt.subplots(1, 3) fig.set_size_inches(8, 3, forward=True) plt.subplots_adjust(0.05, 0.05, 0.95, 0.95, 0.05, 0.05) -ax[0].imshow(visualize_boundaries(img, segments_fz)) +ax[0].imshow(mark_boundaries(img, segments_fz)) ax[0].set_title("Felzenszwalbs's method") -ax[1].imshow(visualize_boundaries(img, segments_slic)) +ax[1].imshow(mark_boundaries(img, segments_slic)) ax[1].set_title("SLIC") -ax[2].imshow(visualize_boundaries(img, segments_quick)) +ax[2].imshow(mark_boundaries(img, segments_quick)) ax[2].set_title("Quickshift") for a in ax: a.set_xticks(()) diff --git a/doc/ext/plot2rst.py b/doc/ext/plot2rst.py index 92978723..65c77521 100644 --- a/doc/ext/plot2rst.py +++ b/doc/ext/plot2rst.py @@ -27,9 +27,10 @@ plot2rst_rcparams : dict plot2rst_default_thumb : str Path (relative to doc root) of default thumbnail image. -plot2rst_thumb_scale : float - Scale factor for thumbnail (e.g., 0.2 to scale plot to 1/5th the - original size). +plot2rst_thumb_shape : float + Shape of thumbnail in pixels. The image is resized to fit within this shape + and the excess is filled with white pixels. This fixed size ensures that + that gallery images are displayed in a grid. plot2rst_plot_tag : str When this tag is found in the example file, the current plot is saved and @@ -73,7 +74,10 @@ import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt -from matplotlib import image + +from skimage import io +from skimage import transform +from skimage.util.dtype import dtype_range LITERALINCLUDE = """ @@ -160,7 +164,7 @@ def setup(app): ('../examples', 'auto_examples'), True) app.add_config_value('plot2rst_rcparams', {}, True) app.add_config_value('plot2rst_default_thumb', None, True) - app.add_config_value('plot2rst_thumb_scale', 0.25, True) + app.add_config_value('plot2rst_thumb_shape', (250, 300), True) app.add_config_value('plot2rst_plot_tag', 'PLOT2RST.current_figure', True) app.add_config_value('plot2rst_index_name', 'index', True) @@ -335,7 +339,8 @@ def write_example(src_name, src_dir, rst_dir, cfg): thumb_path = thumb_dir.pjoin(src_name[:-3] + '.png') first_image_file = image_dir.pjoin(figure_list[0].lstrip('/')) if first_image_file.exists: - image.thumbnail(first_image_file, thumb_path, cfg.plot2rst_thumb_scale) + first_image = io.imread(first_image_file) + save_thumbnail(first_image, thumb_path, cfg.plot2rst_thumb_shape) if not thumb_path.exists: if cfg.plot2rst_default_thumb is None: @@ -345,6 +350,28 @@ def write_example(src_name, src_dir, rst_dir, cfg): shutil.copy(cfg.plot2rst_default_thumb, thumb_path) +def save_thumbnail(image, thumb_path, shape): + """Save image as a thumbnail with the specified shape. + + The image is first resized to fit within the specified shape and then + centered in an array of the specified shape before saving. + """ + rescale = min(float(w_1) / w_2 for w_1, w_2 in zip(shape, image.shape)) + small_shape = (rescale * np.asarray(image.shape[:2])).astype(int) + small_image = transform.resize(image, small_shape) + + if len(image.shape) == 3: + shape = shape + (image.shape[2],) + background_value = dtype_range[small_image.dtype.type][1] + thumb = background_value * np.ones(shape, dtype=small_image.dtype) + + i = (shape[0] - small_shape[0]) // 2 + j = (shape[1] - small_shape[1]) // 2 + thumb[i:i+small_shape[0], j:j+small_shape[1]] = small_image + + io.imsave(thumb_path, thumb) + + def _plots_are_current(src_path, image_path): first_image_file = Path(image_path.format(1)) needs_replot = (not first_image_file.exists or diff --git a/doc/logo/Makefile b/doc/logo/Makefile index 46c36fda..1cf861bd 100644 --- a/doc/logo/Makefile +++ b/doc/logo/Makefile @@ -1,11 +1,11 @@ .PHONY: logo logo: green_orange_snake.png snake_logo.svg - inkscape --export-png=scikits_image_logo.png --export-dpi=100 \ + inkscape --export-png=scikit_image_logo.png --export-dpi=100 \ --export-area-drawing --export-background-opacity=1 \ snake_logo.svg python shrink_logo.py green_orange_snake.png: - python scikits_image_logo.py --no-plot + python scikit_image_logo.py --no-plot diff --git a/doc/logo/scikits_image_logo.py b/doc/logo/scikit_image_logo.py similarity index 100% rename from doc/logo/scikits_image_logo.py rename to doc/logo/scikit_image_logo.py diff --git a/doc/logo/shrink_logo.py b/doc/logo/shrink_logo.py index 3b7d91ba..205e1bf4 100644 --- a/doc/logo/shrink_logo.py +++ b/doc/logo/shrink_logo.py @@ -2,7 +2,7 @@ from skimage import io, transform s = 0.7 -img = io.imread('scikits_image_logo.png') +img = io.imread('scikit_image_logo.png') h, w, c = img.shape print "\nScaling down logo by %.1fx..." % s @@ -13,4 +13,4 @@ img = transform.homography(img, [[s, 0, 0], output_shape=(int(h*s), int(w*s), 4), order=3) -io.imsave('scikits_image_logo_small.png', img) +io.imsave('scikit_image_logo_small.png', img) diff --git a/doc/make.bat b/doc/make.bat index 9d4d3185..e16c8d40 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -74,9 +74,9 @@ if "%1" == "qthelp" ( echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in build/qthelp, like this: - echo.^> qcollectiongenerator build\qthelp\scikitsimage.qhcp + echo.^> qcollectiongenerator build\qthelp\scikitimage.qhcp echo.To view the help file: - echo.^> assistant -collectionFile build\qthelp\scikitsimage.ghc + echo.^> assistant -collectionFile build\qthelp\scikitimage.ghc goto end ) diff --git a/doc/release/release_0.3.txt b/doc/release/release_0.3.txt index 617c3614..5446003d 100644 --- a/doc/release/release_0.3.txt +++ b/doc/release/release_0.3.txt @@ -9,11 +9,11 @@ Python (>=2.6 or 3.x), NumPy and SciPy can be installed. For more information, visit our website - http://scikit-image.org + http://scikits-image.org or the examples gallery at - http://scikit-image.org/docs/0.3/auto_examples/ + http://scikits-image.org/docs/0.3/auto_examples/ New Features ------------ diff --git a/doc/release/release_0.4.txt b/doc/release/release_0.4.txt index 04679271..765b4760 100644 --- a/doc/release/release_0.4.txt +++ b/doc/release/release_0.4.txt @@ -1,26 +1,26 @@ -Announcement: scikit-image 0.4 +Announcement: scikits-image 0.4 =============================== -We're happy to announce the 0.4 release of scikit-image, an image processing +We're happy to announce the 0.4 release of scikits-image, an image processing toolbox for SciPy. Please visit our examples gallery to see what we've been up to: - http://scikit-image.org/docs/0.4/auto_examples/ + http://scikits-image.org/docs/0.4/auto_examples/ Note that, in this release, we renamed the module from ``scikits.image`` to ``skimage``, to work around name space conflicts with other scikits (similarly, the machine learning scikit is now imported as ``sklearn``). A big shout-out also to everyone currently at SciPy India; have fun, and -remember to join the scikit-image sprint! +remember to join the scikits-image sprint! This release runs under all major operating systems where Python (>=2.6 or 3.x), NumPy and SciPy can be installed. For more information, visit our website - http://scikit-image.org + http://scikits-image.org New Features ------------ diff --git a/doc/release/release_0.5.txt b/doc/release/release_0.5.txt index 652f45fa..74a5b3fe 100644 --- a/doc/release/release_0.5.txt +++ b/doc/release/release_0.5.txt @@ -1,12 +1,12 @@ -Announcement: scikit-image 0.5 +Announcement: scikits-image 0.5 =============================== -We're happy to announce the 0.5 release of scikit-image, our image processing +We're happy to announce the 0.5 release of scikits-image, our image processing toolbox for SciPy. For more information, please visit our website - http://scikit-image.org + http://scikits-image.org New Features ------------ diff --git a/doc/release/release_0.6.txt b/doc/release/release_0.6.txt index df2582cc..8c525cb7 100644 --- a/doc/release/release_0.6.txt +++ b/doc/release/release_0.6.txt @@ -1,9 +1,9 @@ -Announcement: scikit-image 0.6 +Announcement: scikits-image 0.6 =============================== -We're happy to announce the 6th version of scikit-image! +We're happy to announce the 6th version of scikits-image! -scikit-image is an image processing toolbox for SciPy that includes algorithms +Scikits-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. diff --git a/doc/release/release_0.7.txt b/doc/release/release_0.7.txt index 6637cd51..a6b591cb 100644 --- a/doc/release/release_0.7.txt +++ b/doc/release/release_0.7.txt @@ -1,9 +1,9 @@ -Announcement: scikit-image 0.7.0 +Announcement: scikits-image 0.7.0 ================================= -We're happy to announce the 7th version of scikit-image! +We're happy to announce the 7th version of scikits-image! -scikit-image is an image processing toolbox for SciPy that includes algorithms +Scikits-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. @@ -15,7 +15,7 @@ For more information, examples, and documentation, please visit our website New Features ------------ -It's been only 3 months since scikit-image 0.6 was released, but in that short +It's been only 3 months since scikits-image 0.6 was released, but in that short time, we've managed to add plenty of new features and enhancements, including - Geometric image transforms diff --git a/doc/source/_static/docversions.js b/doc/source/_static/docversions.js index 660553e0..0b414325 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.6', '0.5', '0.4', '0.3']; + var labels = ['dev', '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/_templates/navbar.html b/doc/source/_templates/navbar.html index b0fa8026..267af8e7 100644 --- a/doc/source/_templates/navbar.html +++ b/doc/source/_templates/navbar.html @@ -1,5 +1,5 @@ -
  • Home
  • +
  • Home
  • Download
  • Gallery
  • -
  • Documentation
  • +
  • Documentation
  • Source
  • diff --git a/doc/source/conf.py b/doc/source/conf.py index 71033adc..5071fab1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -176,7 +176,7 @@ html_sidebars = { #html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'scikitsimagedoc' +htmlhelp_basename = 'scikitimagedoc' # -- Options for LaTeX output -------------------------------------------------- @@ -190,7 +190,7 @@ htmlhelp_basename = 'scikitsimagedoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('contents', 'scikitsimage.tex', u'The Image Scikit Documentation', + ('contents', 'scikitimage.tex', u'The Image Scikit Documentation', u'SciPy Developers', 'manual'), ] diff --git a/doc/source/install.txt b/doc/source/install.txt index 81652ab1..7c886549 100644 --- a/doc/source/install.txt +++ b/doc/source/install.txt @@ -1,8 +1,6 @@ Pre-built installation ---------------------- -.. !! Also update scikit-image-web !! - `Windows binaries `__ are kindly provided by Christoph Gohlke. @@ -23,7 +21,7 @@ or :: - pip -U scikit-image + pip install -U scikit-image Installation from source ------------------------ diff --git a/doc/source/themes/scikit-image/static/css/custom.css b/doc/source/themes/scikit-image/static/css/custom.css index df60739e..85a174df 100644 --- a/doc/source/themes/scikit-image/static/css/custom.css +++ b/doc/source/themes/scikit-image/static/css/custom.css @@ -14,10 +14,9 @@ pre { font-size: 11px; } -h1, h2, h3, h4 { - clear: left; +h1, h2, h3, h4, h5, h6 { + clear: left; } - h1 { font-size: 30px; line-height: 36px; diff --git a/doc/source/themes/scikit-image/static/img/favicon.ico b/doc/source/themes/scikit-image/static/img/favicon.ico new file mode 100644 index 00000000..d7ae6012 Binary files /dev/null and b/doc/source/themes/scikit-image/static/img/favicon.ico differ diff --git a/doc/source/user_guide/data_types.txt b/doc/source/user_guide/data_types.txt index 9801c244..c3b87b2e 100644 --- a/doc/source/user_guide/data_types.txt +++ b/doc/source/user_guide/data_types.txt @@ -71,11 +71,6 @@ issued:: float64 to uint8 array([ 0, 128, 255], dtype=uint8) -Wherever possible, functions should try to handle input without explicit -conversion. For example, there is no need to force values to a specific type -for doing a convolution; a plotting function, on the other hand, needs to know -the range of the input. - Output types ============ diff --git a/setup.py b/setup.py index 9823352c..390f007c 100644 --- a/setup.py +++ b/setup.py @@ -6,17 +6,17 @@ Image processing algorithms for SciPy, including IO, morphology, filtering, warping, color manipulation, object detection, etc. Please refer to the online documentation at -http://scikits-image.org/ +http://scikit-image.org/ """ -DISTNAME = 'scikits-image' +DISTNAME = 'scikit-image' DESCRIPTION = 'Image processing routines for SciPy' LONG_DESCRIPTION = descr MAINTAINER = 'Stefan van der Walt' MAINTAINER_EMAIL = 'stefan@sun.ac.za' -URL = 'http://scikits-image.org' +URL = 'http://scikit-image.org' LICENSE = 'Modified BSD' -DOWNLOAD_URL = 'http://github.com/scikits-image/scikits-image' +DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image' VERSION = '0.8dev' PYTHON_VERSION = (2, 5) DEPENDENCIES = { diff --git a/skimage/__init__.py b/skimage/__init__.py index c8935cb6..1ac121b3 100644 --- a/skimage/__init__.py +++ b/skimage/__init__.py @@ -1,6 +1,6 @@ """Image Processing SciKit (Toolbox for SciPy) -``scikits-image`` (a.k.a. ``skimage``) is a collection of algorithms for image +``scikit-image`` (a.k.a. ``skimage``) is a collection of algorithms for image processing and computer vision. The main package of ``skimage`` only provides a few utilities for converting @@ -65,7 +65,7 @@ except ImportError: def _setup_test(verbose=False): import functools - args = ['', '--exe', '-w', pkg_dir] + args = ['', pkg_dir, '--exe'] if verbose: args.extend(['-v', '-s']) diff --git a/skimage/_shared/interpolation.pxd b/skimage/_shared/interpolation.pxd index f43ff25e..4e00dfb0 100644 --- a/skimage/_shared/interpolation.pxd +++ b/skimage/_shared/interpolation.pxd @@ -18,7 +18,10 @@ cdef double bicubic_interpolation(double* image, int rows, int cols, double r, double c, char mode, double cval) -cdef double get_pixel(double* image, int rows, int cols, int r, int 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_pixel3d(double* image, int rows, int cols, int dims, int r, + int c, int d, char mode, double cval) cdef int coord_map(int dim, int coord, char mode) diff --git a/skimage/_shared/interpolation.pyx b/skimage/_shared/interpolation.pyx index e0fd0067..b34ba507 100644 --- a/skimage/_shared/interpolation.pyx +++ b/skimage/_shared/interpolation.pyx @@ -35,8 +35,8 @@ cdef inline double nearest_neighbour_interpolation(double* image, int rows, """ - return get_pixel(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, @@ -72,10 +72,10 @@ cdef inline double bilinear_interpolation(double* image, int rows, int cols, maxc = ceil(c) dr = r - minr dc = c - minc - top = (1 - dc) * get_pixel(image, rows, cols, minr, minc, mode, cval) \ - + dc * get_pixel(image, rows, cols, minr, maxc, mode, cval) - bottom = (1 - dc) * get_pixel(image, rows, cols, maxr, minc, mode, cval) \ - + dc * get_pixel(image, rows, cols, maxr, maxc, mode, cval) + top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \ + + dc * get_pixel2d(image, rows, cols, minr, maxc, mode, cval) + bottom = (1 - dc) * get_pixel2d(image, rows, cols, maxr, minc, mode, cval) \ + + dc * get_pixel2d(image, rows, cols, maxr, maxc, mode, cval) return (1 - dr) * top + dr * bottom @@ -144,7 +144,7 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols, # row-wise cubic interpolation for pr in range(r0, r0 + 3): for pc in range(c0, c0 + 3): - fc[pc - c0] = get_pixel(image, rows, cols, pr, pc, mode, cval) + fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval) fr[pr - r0] = quadratic_interpolation(xc, fc) # cubic interpolation for interpolated values of each row @@ -216,15 +216,15 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, # row-wise cubic interpolation for pr in range(r0, r0 + 4): for pc in range(c0, c0 + 4): - fc[pc - c0] = get_pixel(image, rows, cols, pr, pc, mode, cval) + fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval) fr[pr - r0] = cubic_interpolation(xc, fc) # cubic interpolation for interpolated values of each row return cubic_interpolation(xr, fr) -cdef inline double get_pixel(double* image, int rows, int cols, int r, int c, - char mode, double cval): +cdef inline double get_pixel2d(double* image, int rows, int cols, int r, int c, + char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -255,6 +255,40 @@ cdef inline double get_pixel(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): + """Get a pixel from the image, taking wrapping mode into consideration. + + Parameters + ---------- + image : double array + Input image. + rows, cols, dims : int + Shape of image. + r, c, d : int + Position at which to get the pixel. + mode : {'C', 'W', 'R', 'N'} + Wrapping mode. Constant, Wrap, Reflect or Nearest. + cval : double + Constant value to use for constant mode. + + Returns + ------- + value : double + Pixel value at given position. + + """ + if mode == 'C': + if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1): + return cval + else: + return image[r * cols * dims + c * dims + d] + else: + return image[coord_map(rows, r, mode) * cols * dims + + coord_map(cols, c, mode) * dims + + d] + + cdef inline int coord_map(int dim, int coord, char mode): """ Wrap a coordinate, according to a given mode. diff --git a/skimage/_shared/setup.py b/skimage/_shared/setup.py index 81113656..765c30de 100644 --- a/skimage/_shared/setup.py +++ b/skimage/_shared/setup.py @@ -28,11 +28,11 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='Scikits-image Developers', - author='Scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + author='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Transforms', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py new file mode 100644 index 00000000..4075ddb4 --- /dev/null +++ b/skimage/_shared/utils.py @@ -0,0 +1,43 @@ +import warnings +import functools + + +__all__ = ['deprecated'] + + +class deprecated(object): + """Decorator to mark deprecated functions with warning. + + Adapted from . + + Parameters + ---------- + alt_func : str + If given, tell user what function to use instead. + behavior : {'warn', 'raise'} + Behavior during call to deprecated function: 'warn' = warn user that + function is deprecated; 'raise' = raise error. + """ + + def __init__(self, alt_func=None, behavior='warn'): + self.alt_func = alt_func + self.behavior = behavior + + def __call__(self, func): + + msg = "Call to deprecated function `%s`." % func.__name__ + if self.alt_func is not None: + msg = msg + " Use `%s` instead." % self.alt_func + + @functools.wraps(func) + def wrapped(*args, **kwargs): + if self.behavior == 'warn': + warnings.warn_explicit(msg, + category=DeprecationWarning, + filename=func.func_code.co_filename, + lineno=func.func_code.co_firstlineno + 1) + elif self.behavior == 'raise': + raise DeprecationWarning(msg) + return func(*args, **kwargs) + + return wrapped diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index ec5551d6..38eab995 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -45,7 +45,7 @@ from __future__ import division __all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb', 'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb', - 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab' + 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'is_rgb', 'is_gray' ] __docformat__ = "restructuredtext en" @@ -55,6 +55,31 @@ from scipy import linalg from ..util import dtype +def is_rgb(image): + """Test whether the image is RGB or RGBA. + + Parameters + ---------- + image : ndarray + Input image. + + """ + return (image.ndim == 3 and image.shape[2] in (3, 4)) + + +def is_gray(image): + """Test whether the image is gray (i.e. has only one color band). + + Parameters + ---------- + image : ndarray + Input image. + + """ + return image.ndim == 2 + + + def convert_colorspace(arr, fromspace, tospace): """Convert an image array to a new color space. @@ -281,7 +306,7 @@ rgbcie_from_rgb = np.dot(rgbcie_from_xyz, xyz_from_rgb) rgb_from_rgbcie = np.dot(rgb_from_xyz, xyz_from_rgbcie) -grey_from_rgb = np.array([[0.2125, 0.7154, 0.0721], +gray_from_rgb = np.array([[0.2125, 0.7154, 0.0721], [0, 0, 0], [0, 0, 0]]) @@ -458,7 +483,7 @@ def rgbcie2rgb(rgbcie): return _convert(rgb_from_rgbcie, rgbcie) -def rgb2grey(rgb): +def rgb2gray(rgb): """Compute luminance of an RGB image. Parameters @@ -475,7 +500,7 @@ def rgb2grey(rgb): Raises ------ ValueError - If `rgb2grey` is not a 3-D array of shape (.., .., 3) or + If `rgb2gray` is not a 3-D array of shape (.., .., 3) or (.., .., 4). References @@ -493,21 +518,21 @@ def rgb2grey(rgb): Examples -------- - >>> from skimage.color import rgb2grey + >>> from skimage.color import rgb2gray >>> from skimage import data >>> lena = data.lena() - >>> lena_grey = rgb2grey(lena) + >>> lena_gray = rgb2gray(lena) """ if rgb.ndim == 2: return rgb - return _convert(grey_from_rgb, rgb[:, :, :3])[..., 0] + return _convert(gray_from_rgb, rgb[:, :, :3])[..., 0] -rgb2gray = rgb2grey +rgb2grey = rgb2gray def gray2rgb(image): - """Create an RGB representation of a grey-level image. + """Create an RGB representation of a gray-level image. Parameters ---------- @@ -525,11 +550,12 @@ def gray2rgb(image): If the input is not 2-dimensional. """ - if image.ndim != 2: - raise ValueError('Gray-level image should be two-dimensional.') - - M, N = image.shape - return np.dstack((image, image, image)) + if is_rgb(image): + return image + elif is_gray(image): + return np.dstack((image, image, image)) + else: + raise ValueError("Input image expected to be RGB, RGBA or gray.") def xyz2lab(xyz): diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 316d801a..c09c9d22 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -25,10 +25,11 @@ from skimage.color import ( convert_colorspace, rgb2grey, gray2rgb, xyz2lab, lab2xyz, - lab2rgb, rgb2lab + lab2rgb, rgb2lab, + is_rgb, is_gray ) -from skimage import data_dir +from skimage import data_dir, data import colorsys @@ -196,5 +197,23 @@ def test_gray2rgb(): assert_equal(z[..., 0], x) assert_equal(z[0, 1, :], [128, 128, 128]) + +def test_gray2rgb_rgb(): + x = np.random.random((5, 5, 4)) + y = gray2rgb(x) + assert_equal(x, y) + + +def test_is_rgb(): + color = data.lena() + gray = data.camera() + + assert is_rgb(color) + assert not is_gray(color) + + assert is_gray(gray) + assert not is_gray(color) + + if __name__ == "__main__": run_module_suite() diff --git a/skimage/data/__init__.py b/skimage/data/__init__.py index 5e51d353..d2fba7db 100644 --- a/skimage/data/__init__.py +++ b/skimage/data/__init__.py @@ -114,3 +114,16 @@ def page(): """ return load("page.png") + + +def clock(): + """Motion blurred clock. + + This photograph of a wall clock was taken while moving the camera in an + aproximately horizontal direction. It may be used to illustrate + inverse filters and deconvolution. + + Released into the public domain by the photographer (Stefan van der Walt). + + """ + return load("clock_motion.png") diff --git a/skimage/data/clock_motion.png b/skimage/data/clock_motion.png new file mode 100644 index 00000000..3a81bd74 Binary files /dev/null and b/skimage/data/clock_motion.png differ diff --git a/skimage/data/tests/test_data.py b/skimage/data/tests/test_data.py index 1d85802b..f660c69c 100644 --- a/skimage/data/tests/test_data.py +++ b/skimage/data/tests/test_data.py @@ -15,9 +15,30 @@ def test_camera(): def test_checkerboard(): - """ Test that checkerboard image can be loaded. """ + """ Test that "checkerboard" image can be loaded. """ data.checkerboard() + +def test_text(): + """ Test that "text" image can be loaded. """ + data.text() + + +def test_moon(): + """ Test that "moon" image can be loaded. """ + data.moon() + + +def test_page(): + """ Test that "page" image can be loaded. """ + data.page() + + +def test_page(): + """ Test that "clock" image can be loaded. """ + data.clock() + + if __name__ == "__main__": from numpy.testing import run_module_suite run_module_suite() diff --git a/skimage/draw/setup.py b/skimage/draw/setup.py index 59067f9a..5b8e237d 100644 --- a/skimage/draw/setup.py +++ b/skimage/draw/setup.py @@ -21,11 +21,11 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image developers', - author='scikits-image developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image developers', + author='scikit-image developers', + maintainer_email='scikit-image@googlegroups.com', description='Drawing', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/feature/setup.py b/skimage/feature/setup.py index 9c9074be..6f820163 100644 --- a/skimage/feature/setup.py +++ b/skimage/feature/setup.py @@ -24,11 +24,11 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - author='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + author='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Features', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/filter/__init__.py b/skimage/filter/__init__.py index bdbbb531..f1c1fd49 100644 --- a/skimage/filter/__init__.py +++ b/skimage/filter/__init__.py @@ -1,7 +1,9 @@ from .lpi_filter import * from .ctmf import median_filter from ._canny import canny -from .edges import sobel, hsobel, vsobel, hprewitt, vprewitt, prewitt -from ._tv_denoise import tv_denoise +from .edges import (sobel, hsobel, vsobel, scharr, hscharr, vscharr, prewitt, + hprewitt, vprewitt) +from .denoise import tv_denoise, denoise_tv +from ._denoise import denoise_bilateral from ._rank_order import rank_order from .thresholding import threshold_otsu, threshold_adaptive diff --git a/skimage/filter/_denoise.pyx b/skimage/filter/_denoise.pyx new file mode 100644 index 00000000..b60a5a85 --- /dev/null +++ b/skimage/filter/_denoise.pyx @@ -0,0 +1,177 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + +cimport numpy as cnp +import numpy as np +from libc.math cimport exp, fabs, sqrt +from libc.stdlib cimport malloc, free +from skimage._shared.interpolation cimport get_pixel3d +from skimage.util import img_as_float + + +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* color_lut = malloc(bins * sizeof(double)) + Py_ssize_t b + + for b in range(bins): + color_lut[b] = _gaussian_weight(sigma, b * max_value / bins) + + return color_lut + + +cdef double* _compute_range_lut(int win_size, double sigma): + + cdef: + double* range_lut = malloc(win_size**2 * sizeof(double)) + Py_ssize_t kr, kc + Py_ssize_t window_ext = (win_size - 1) / 2 + double dist + + for kr in range(win_size): + for kc in range(win_size): + dist = sqrt((kr - window_ext)**2 + (kc - window_ext)**2) + range_lut[kr * win_size + kc] = _gaussian_weight(sigma, dist) + + 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): + """Denoise image using bilateral filter. + + This is an edge-preserving and noise reducing denoising filter. It averages + pixels based on their spatial closeness and radiometric similarity. + + Spatial closeness is measured by the gaussian function of the euclidian + distance between two pixels and a certain standard deviation + (`sigma_spatial`). + + Radiometric similarity is measured by the gaussian function of the euclidian + distance between two color values and a certain standard deviation + (`sigma_range`). + + Parameters + ---------- + image : ndarray + Input image. + win_size : int + Window size for filtering. + sigma_range : float + Standard deviation for grayvalue/color distance (radiometric + similarity). A larger value results in averaging of pixels with larger + radiometric differences. Note, that the image will be converted using + the `img_as_float` function and thus the standard deviation is in + respect to the range `[0, 1]`. + sigma_spatial : float + Standard deviation for range distance. A larger value results in + averaging of pixels with larger spatial differences. + bins : int + Number of discrete values for gaussian weights of color filtering. + A larger value results in improved accuracy. + mode : string + How to handle values outside the image borders. See + `scipy.ndimage.map_coordinates` for detail. + cval : string + Used in conjunction with mode 'constant', the value outside + the image boundaries. + + Returns + ------- + denoised : ndarray + Denoised image. + + References + ---------- + .. [1] http://users.soe.ucsc.edu/~manduchi/Papers/ICCV98.pdf + + """ + + image = np.atleast_3d(img_as_float(image)) + + cdef: + Py_ssize_t rows = image.shape[0] + Py_ssize_t cols = image.shape[1] + Py_ssize_t dims = image.shape[2] + Py_ssize_t window_ext = (win_size - 1) / 2 + + double max_value = image.max() + + cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] cimage = \ + np.ascontiguousarray(image) + cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] out = \ + np.zeros((rows, cols, dims), dtype=np.double) + + double* image_data = cimage.data + double* out_data = out.data + + double* color_lut = _compute_color_lut(bins, sigma_range, max_value) + double* range_lut = _compute_range_lut(win_size, sigma_spatial) + + Py_ssize_t r, c, d, wr, wc, kr, kc, rr, cc, pixel_addr + double value, weight, dist, total_weight, csigma_range, color_weight, \ + range_weight + double dist_scale = bins / dims / max_value + double* values = malloc(dims * sizeof(double)) + double* centres = malloc(dims * sizeof(double)) + double* total_values = malloc(dims * sizeof(double)) + + if sigma_range is None: + csigma_range = image.std() + else: + csigma_range = sigma_range + + if mode not in ('constant', 'wrap', 'reflect', 'nearest'): + raise ValueError("Invalid mode specified. Please use " + "`constant`, `nearest`, `wrap` or `reflect`.") + cdef char cmode = ord(mode[0].upper()) + + for r in range(rows): + for c in range(cols): + pixel_addr = r * cols * dims + c * dims + total_weight = 0 + for d in range(dims): + total_values[d] = 0 + centres[d] = image_data[pixel_addr + d] + for wr in range(-window_ext, window_ext + 1): + rr = wr + r + kr = wr + window_ext + for wc in range(-window_ext, window_ext + 1): + cc = wc + c + kc = wc + window_ext + + # save pixel values for all dims and compute euclidian + # distance between centre stack and current position + dist = 0 + for d in range(dims): + value = get_pixel3d(image_data, rows, cols, dims, + rr, cc, d, cmode, cval) + values[d] = value + dist += (centres[d] - value)**2 + dist = sqrt(dist) + + range_weight = range_lut[kr * win_size + kc] + color_weight = color_lut[(dist * dist_scale)] + + weight = range_weight * color_weight + for d in range(dims): + total_values[d] += values[d] * weight + total_weight += weight + for d in range(dims): + out_data[pixel_addr + d] = total_values[d] / total_weight + + free(color_lut) + free(range_lut) + free(values) + free(centres) + free(total_values) + + return out diff --git a/skimage/filter/_tv_denoise.py b/skimage/filter/denoise.py similarity index 75% rename from skimage/filter/_tv_denoise.py rename to skimage/filter/denoise.py index 3302319c..351b2482 100644 --- a/skimage/filter/_tv_denoise.py +++ b/skimage/filter/denoise.py @@ -1,37 +1,35 @@ import numpy as np from skimage import img_as_float +from skimage._shared.utils import deprecated -def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200): - """ - Perform total-variation denoising on 3-D arrays +def _denoise_tv_3d(im, weight=100, eps=2.e-4, n_iter_max=200): + """Perform total-variation denoising on 3-D arrays. Parameters ---------- im: ndarray - 3-D input data to be denoised - + 3-D input data to be denoised. weight: float, optional - denoising weight. The greater ``weight``, the more denoising (at - the expense of fidelity to ``input``) - + Denoising weight. The greater ``weight``, the more denoising (at + the expense of fidelity to ``input``). eps: float, optional - relative difference of the value of the cost function that determines + Relative difference of the value of the cost function that determines the stop criterion. The algorithm stops when: (E_(n-1) - E_n) < eps * E_0 n_iter_max: int, optional - maximal number of iterations used for the optimization. + Maximal number of iterations used for the optimization. Returns ------- out: ndarray - denoised array of floats + Denoised array of floats. Notes ----- - Rudin, Osher and Fatemi algorithm + Rudin, Osher and Fatemi algorithm. Examples --------- @@ -40,7 +38,7 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200): >>> mask = (x -22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2 >>> mask = mask.astype(np.float) >>> mask += 0.2*np.random.randn(*mask.shape) - >>> res = tv_denoise_3d(mask, weight=100) + >>> res = denoise_tv_3d(mask, weight=100) """ px = np.zeros_like(im) py = np.zeros_like(im) @@ -85,44 +83,40 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200): return out -def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200): - """ - Perform total-variation denoising +def _denoise_tv_2d(im, weight=50, eps=2.e-4, n_iter_max=200): + """Perform total-variation denoising. Parameters ---------- im: ndarray - input data to be denoised - + Input data to be denoised. weight: float, optional - denoising weight. The greater ``weight``, the more denoising (at + Denoising weight. The greater ``weight``, the more denoising (at the expense of fidelity to ``input``) - eps: float, optional - relative difference of the value of the cost function that determines + Relative difference of the value of the cost function that determines the stop criterion. The algorithm stops when: (E_(n-1) - E_n) < eps * E_0 n_iter_max: int, optional - maximal number of iterations used for the optimization. + Maximal number of iterations used for the optimization. Returns ------- out: ndarray - denoised array of floats + Denoised array of floats. Notes ----- The principle of total variation denoising is explained in - http://en.wikipedia.org/wiki/Total_variation_denoising + http://en.wikipedia.org/wiki/Total_variation_denoising. This code is an implementation of the algorithm of Rudin, Fatemi and Osher that was proposed by Chambolle in [1]_. References ---------- - .. [1] A. Chambolle, An algorithm for total variation minimization and applications, Journal of Mathematical Imaging and Vision, Springer, 2004, 20, 89-97. @@ -134,7 +128,7 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200): >>> import scipy >>> lena = scipy.lena().astype(np.float) >>> lena += 0.5 * lena.std()*np.random.randn(*lena.shape) - >>> denoised_lena = tv_denoise(lena, weight=60.0) + >>> denoised_lena = denoise_tv(lena, weight=60.0) """ px = np.zeros_like(im) py = np.zeros_like(im) @@ -172,34 +166,31 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200): return out -def tv_denoise(im, weight=50, eps=2.e-4, n_iter_max=200): - """ - Perform total-variation denoising on 2-d and 3-d images +def denoise_tv(im, weight=50, eps=2.e-4, n_iter_max=200): + """Perform total-variation denoising on 2-d and 3-d images. Parameters ---------- im: ndarray (2d or 3d) of ints, uints or floats - input data to be denoised. `im` can be of any numeric type, + Input data to be denoised. `im` can be of any numeric type, but it is cast into an ndarray of floats for the computation of the denoised image. - weight: float, optional - denoising weight. The greater ``weight``, the more denoising (at - the expense of fidelity to ``input``) - + Denoising weight. The greater ``weight``, the more denoising (at + the expense of fidelity to ``input``). eps: float, optional - relative difference of the value of the cost function that + Relative difference of the value of the cost function that determines the stop criterion. The algorithm stops when: (E_(n-1) - E_n) < eps * E_0 n_iter_max: int, optional - maximal number of iterations used for the optimization. + Maximal number of iterations used for the optimization. Returns ------- out: ndarray - denoised array of floats + Denoised array of floats. Notes ----- @@ -217,7 +208,6 @@ def tv_denoise(im, weight=50, eps=2.e-4, n_iter_max=200): References ---------- - .. [1] A. Chambolle, An algorithm for total variation minimization and applications, Journal of Mathematical Imaging and Vision, Springer, 2004, 20, 89-97. @@ -230,23 +220,26 @@ def tv_denoise(im, weight=50, eps=2.e-4, n_iter_max=200): >>> import scipy >>> lena = scipy.lena().astype(np.float) >>> lena += 0.5 * lena.std()*np.random.randn(*lena.shape) - >>> denoised_lena = tv_denoise(lena, weight=60) + >>> denoised_lena = denoise_tv(lena, weight=60) >>> # 3D example on synthetic data >>> 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) >>> mask += 0.2*np.random.randn(*mask.shape) - >>> res = tv_denoise_3d(mask, weight=100) + >>> res = denoise_tv_3d(mask, weight=100) """ im_type = im.dtype if not im_type.kind == 'f': im = img_as_float(im) if im.ndim == 2: - out = _tv_denoise_2d(im, weight, eps, n_iter_max) + out = _denoise_tv_2d(im, weight, eps, n_iter_max) elif im.ndim == 3: - out = _tv_denoise_3d(im, weight, eps, n_iter_max) + out = _denoise_tv_3d(im, weight, eps, n_iter_max) else: raise ValueError('only 2-d and 3-d images may be denoised with this ' 'function') return out + + +tv_denoise = deprecated('skimage.filter.denoise_tv')(denoise_tv) diff --git a/skimage/filter/edges.py b/skimage/filter/edges.py index 134aa796..fcd9f548 100644 --- a/skimage/filter/edges.py +++ b/skimage/filter/edges.py @@ -1,6 +1,7 @@ -"""edges.py - Sobel edge filter +"""edges.py - Edge filters -Originally part of CellProfiler, code licensed under both GPL and BSD licenses. +Sobel and Prewitt filters originally part of CellProfiler, code licensed under +both GPL and BSD licenses. Website: http://www.cellprofiler.org Copyright (c) 2003-2009 Massachusetts Institute of Technology Copyright (c) 2009-2011 Broad Institute @@ -34,13 +35,13 @@ def _mask_filter_result(result, mask): def sobel(image, mask=None): - """Calculate the absolute magnitude Sobel to find edges. + """Find the edge magnitude using the Sobel transform. Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process. - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area. Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -48,7 +49,7 @@ def sobel(image, mask=None): Returns ------- output : ndarray - The Sobel edge map. + The Sobel edge map. Notes ----- @@ -67,9 +68,9 @@ def hsobel(image, mask=None): Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process. - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area. Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -77,7 +78,7 @@ def hsobel(image, mask=None): Returns ------- output : ndarray - The Sobel edge map. + The Sobel edge map. Notes ----- @@ -102,9 +103,9 @@ def vsobel(image, mask=None): Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -112,7 +113,7 @@ def vsobel(image, mask=None): Returns ------- output : ndarray - The Sobel edge map. + The Sobel edge map. Notes ----- @@ -132,14 +133,14 @@ def vsobel(image, mask=None): return _mask_filter_result(result, mask) -def prewitt(image, mask=None): - """Find the edge magnitude using the Prewitt transform. +def scharr(image, mask=None): + """Find the edge magnitude using the Scharr transform. Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process. - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area. Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -147,7 +148,119 @@ def prewitt(image, mask=None): Returns ------- output : ndarray - The Prewitt edge map. + The Scharr edge map. + + Notes + ----- + Take the square root of the sum of the squares of the horizontal and + vertical Scharrs to get a magnitude that's somewhat insensitive to + direction. + + References + ---------- + .. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization + of Kernel Based Image Derivatives. + + """ + return np.sqrt(hscharr(image, mask)**2 + vscharr(image, mask)**2) + + +def hscharr(image, mask=None): + """Find the horizontal edges of an image using the Scharr transform. + + Parameters + ---------- + image : 2-D array + Image to process. + mask : 2-D array, optional + An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. + + Returns + ------- + output : ndarray + The Scharr edge map. + + Notes + ----- + We use the following kernel and return the absolute value of the + result at each point:: + + 3 10 3 + 0 0 0 + -3 -10 -3 + + References + ---------- + .. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization + of Kernel Based Image Derivatives. + + """ + image = img_as_float(image) + result = np.abs(convolve(image, + np.array([[ 3, 10, 3], + [ 0, 0, 0], + [-3, -10, -3]]).astype(float) / 16.0)) + return _mask_filter_result(result, mask) + + +def vscharr(image, mask=None): + """Find the vertical edges of an image using the Scharr transform. + + Parameters + ---------- + image : 2-D array + Image to process + mask : 2-D array, optional + An optional mask to limit the application to a certain area + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. + + Returns + ------- + output : ndarray + The Scharr edge map. + + Notes + ----- + We use the following kernel and return the absolute value of the + result at each point:: + + 3 0 -3 + 10 0 -10 + 3 0 -3 + + References + ---------- + .. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization + of Kernel Based Image Derivatives. + + """ + image = img_as_float(image) + result = np.abs(convolve(image, + np.array([[ 3, 0, -3], + [10, 0, -10], + [ 3, 0, -3]]).astype(float) / 16.0)) + return _mask_filter_result(result, mask) + + +def prewitt(image, mask=None): + """Find the edge magnitude using the Prewitt transform. + + Parameters + ---------- + image : 2-D array + Image to process. + mask : 2-D array, optional + An optional mask to limit the application to a certain area. + Note that pixels surrounding masked regions are also masked to + prevent masked regions from affecting the result. + + Returns + ------- + output : ndarray + The Prewitt edge map. Notes ----- @@ -162,9 +275,9 @@ def hprewitt(image, mask=None): Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process. - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area. Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -172,7 +285,7 @@ def hprewitt(image, mask=None): Returns ------- output : ndarray - The Prewitt edge map. + The Prewitt edge map. Notes ----- @@ -197,9 +310,9 @@ def vprewitt(image, mask=None): Parameters ---------- - image : array_like, dtype=float + image : 2-D array Image to process. - mask : array_like, dtype=bool, optional + mask : 2-D array, optional An optional mask to limit the application to a certain area. Note that pixels surrounding masked regions are also masked to prevent masked regions from affecting the result. @@ -207,7 +320,7 @@ def vprewitt(image, mask=None): Returns ------- output : ndarray - The Prewitt edge map. + The Prewitt edge map. Notes ----- diff --git a/skimage/filter/lpi_filter.py b/skimage/filter/lpi_filter.py index 3826f5e7..2af74d7a 100644 --- a/skimage/filter/lpi_filter.py +++ b/skimage/filter/lpi_filter.py @@ -75,7 +75,7 @@ class LPIFilter2D(object): >>> filter = LPIFilter2D(filt_func) """ - if impulse_response is None: + if not callable(impulse_response): raise ValueError("Impulse response must be a callable.") self.impulse_response = impulse_response diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 03ea7def..b996055b 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -13,19 +13,22 @@ def configuration(parent_package='', top_path=None): config.add_data_dir('tests') cython(['_ctmf.pyx'], working_path=base_path) + cython(['_denoise.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], include_dirs=[get_numpy_include_dirs()]) + config.add_extension('_denoise', sources=['_denoise.c'], + include_dirs=[get_numpy_include_dirs(), '../_shared']) return config if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - author='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + author='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Filters', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/filter/tests/test_canny.py b/skimage/filter/tests/test_canny.py index 1e406008..2c758edf 100644 --- a/skimage/filter/tests/test_canny.py +++ b/skimage/filter/tests/test_canny.py @@ -61,3 +61,8 @@ class TestCanny(unittest.TestCase): def test_image_shape(self): self.assertRaises(TypeError, F.canny, np.zeros((20, 20, 20)), 4, 0, 0) + + def test_mask_none(self): + result1 = F.canny(np.zeros((20, 20)), 4, 0, 0, np.ones((20, 20), bool)) + result2 = F.canny(np.zeros((20, 20)), 4, 0, 0) + self.assertTrue(np.all(result1 == result2)) diff --git a/skimage/filter/tests/test_ctmf.py b/skimage/filter/tests/test_ctmf.py index fd820469..c4f6d10e 100644 --- a/skimage/filter/tests/test_ctmf.py +++ b/skimage/filter/tests/test_ctmf.py @@ -117,5 +117,11 @@ def test_insufficient_size(): median_filter(img, radius=1) +@raises(TypeError) +def test_wrong_shape(): + img = np.empty((10, 10, 3)) + median_filter(img) + + if __name__ == "__main__": np.testing.run_module_suite() diff --git a/skimage/filter/tests/test_denoise.py b/skimage/filter/tests/test_denoise.py new file mode 100644 index 00000000..63a5a5e0 --- /dev/null +++ b/skimage/filter/tests/test_denoise.py @@ -0,0 +1,90 @@ +import numpy as np +from numpy.testing import run_module_suite, assert_raises + +from skimage import filter, data, color, img_as_float + + +lena = img_as_float(data.lena()[:256, :256]) +lena_gray = color.rgb2gray(lena) + + +def test_denoise_tv_2d(): + # lena image + img = lena_gray + # add noise to lena + img += 0.5 * img.std() * np.random.random(img.shape) + # clip noise so that it does not exceed allowed range for float images. + img = np.clip(img, 0, 1) + # denoise + denoised_lena = filter.denoise_tv(img, weight=60.0) + # which dtype? + assert denoised_lena.dtype in [np.float, np.float32, np.float64] + from scipy import ndimage + grad = ndimage.morphological_gradient(img, size=((3, 3))) + grad_denoised = ndimage.morphological_gradient( + denoised_lena, size=((3, 3))) + # test if the total variation has decreased + assert grad_denoised.dtype == np.float + assert (np.sqrt((grad_denoised**2).sum()) + < np.sqrt((grad**2).sum()) / 2) + + +def test_denoise_tv_float_result_range(): + # lena image + img = lena_gray + int_lena = np.multiply(img, 255).astype(np.uint8) + assert np.max(int_lena) > 1 + denoised_int_lena = filter.denoise_tv(int_lena, weight=60.0) + # test if the value range of output float data is within [0.0:1.0] + assert denoised_int_lena.dtype == np.float + assert np.max(denoised_int_lena) <= 1.0 + assert np.min(denoised_int_lena) >= 0.0 + + +def test_denoise_tv_3d(): + """Apply the TV denoising algorithm on a 3D image representing a sphere.""" + x, y, z = np.ogrid[0:40, 0:40, 0:40] + mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2 + mask = 100 * mask.astype(np.float) + mask += 60 + mask += 20 * np.random.random(mask.shape) + mask[mask < 0] = 0 + mask[mask > 255] = 255 + res = filter.denoise_tv(mask.astype(np.uint8), weight=100) + assert res.dtype == np.float + assert res.std() * 255 < mask.std() + + # test wrong number of dimensions + assert_raises(ValueError, filter.denoise_tv, np.random.random((8, 8, 8, 8))) + + +def test_denoise_bilateral_2d(): + img = lena_gray + # add some random noise + img += 0.5 * img.std() * np.random.random(img.shape) + img = np.clip(img, 0, 1) + + out1 = filter.denoise_bilateral(img, sigma_range=0.1, sigma_spatial=20) + out2 = filter.denoise_bilateral(img, sigma_range=0.2, sigma_spatial=30) + + # make sure noise is reduced + assert img.std() > out1.std() + assert out1.std() > out2.std() + + +def test_denoise_bilateral_3d(): + img = lena + # add some random noise + img += 0.5 * img.std() * np.random.random(img.shape) + img = np.clip(img, 0, 1) + + out1 = filter.denoise_bilateral(img, sigma_range=0.1, sigma_spatial=20) + out2 = filter.denoise_bilateral(img, sigma_range=0.2, sigma_spatial=30) + + # make sure noise is reduced + assert img.std() > out1.std() + assert out1.std() > out2.std() + + +if __name__ == "__main__": + run_module_suite() diff --git a/skimage/filter/tests/test_edges.py b/skimage/filter/tests/test_edges.py index a5d52aa5..628de16d 100644 --- a/skimage/filter/tests/test_edges.py +++ b/skimage/filter/tests/test_edges.py @@ -9,6 +9,7 @@ def test_sobel_zeros(): result = F.sobel(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_sobel_mask(): """Sobel on a masked array should be zero""" np.random.seed(0) @@ -16,6 +17,7 @@ def test_sobel_mask(): np.zeros((10, 10), bool)) assert (np.all(result == 0)) + def test_sobel_horizontal(): """Sobel on an edge should be a horizontal line""" i, j = np.mgrid[-5:6, -5:6] @@ -26,6 +28,7 @@ def test_sobel_horizontal(): assert (np.all(result[i == 0] == 1)) assert (np.all(result[np.abs(i) > 1] == 0)) + def test_sobel_vertical(): """Sobel on a vertical edge should be a vertical line""" i, j = np.mgrid[-5:6, -5:6] @@ -41,6 +44,7 @@ def test_hsobel_zeros(): result = F.hsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_hsobel_mask(): """Horizontal Sobel on a masked array should be zero""" np.random.seed(0) @@ -48,6 +52,7 @@ def test_hsobel_mask(): np.zeros((10, 10), bool)) assert (np.all(result == 0)) + def test_hsobel_horizontal(): """Horizontal Sobel on an edge should be a horizontal line""" i, j = np.mgrid[-5:6, -5:6] @@ -58,6 +63,7 @@ def test_hsobel_horizontal(): assert (np.all(result[i == 0] == 1)) assert (np.all(result[np.abs(i) > 1] == 0)) + def test_hsobel_vertical(): """Horizontal Sobel on a vertical edge should be zero""" i, j = np.mgrid[-5:6, -5:6] @@ -71,6 +77,7 @@ def test_vsobel_zeros(): result = F.vsobel(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_vsobel_mask(): """Vertical Sobel on a masked array should be zero""" np.random.seed(0) @@ -78,6 +85,7 @@ def test_vsobel_mask(): np.zeros((10, 10), bool)) assert (np.all(result == 0)) + def test_vsobel_vertical(): """Vertical Sobel on an edge should be a vertical line""" i, j = np.mgrid[-5:6, -5:6] @@ -88,6 +96,7 @@ def test_vsobel_vertical(): assert (np.all(result[j == 0] == 1)) assert (np.all(result[np.abs(j) > 1] == 0)) + def test_vsobel_horizontal(): """vertical Sobel on a horizontal edge should be zero""" i, j = np.mgrid[-5:6, -5:6] @@ -97,11 +106,114 @@ def test_vsobel_horizontal(): assert (np.all(np.abs(result) < eps)) +def test_scharr_zeros(): + """Scharr on an array of all zeros""" + result = F.scharr(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_scharr_mask(): + """Scharr on a masked array should be zero""" + np.random.seed(0) + result = F.scharr(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_scharr_horizontal(): + """Scharr on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.scharr(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + assert (np.all(result[i == 0] == 1)) + assert (np.all(result[np.abs(i) > 1] == 0)) + + +def test_scharr_vertical(): + """Scharr on a vertical edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.scharr(image) + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + assert (np.all(result[np.abs(j) > 1] == 0)) + + +def test_hscharr_zeros(): + """Horizontal Scharr on an array of all zeros""" + result = F.hscharr(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_hscharr_mask(): + """Horizontal Scharr on a masked array should be zero""" + np.random.seed(0) + result = F.hscharr(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_hscharr_horizontal(): + """Horizontal Scharr on an edge should be a horizontal line""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.hscharr(image) + # Fudge the eroded points + i[np.abs(j) == 5] = 10000 + assert (np.all(result[i == 0] == 1)) + assert (np.all(result[np.abs(i) > 1] == 0)) + + +def test_hscharr_vertical(): + """Horizontal Scharr on a vertical edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.hscharr(image) + assert (np.all(result == 0)) + + +def test_vscharr_zeros(): + """Vertical Scharr on an array of all zeros""" + result = F.vscharr(np.zeros((10, 10)), np.ones((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_vscharr_mask(): + """Vertical Scharr on a masked array should be zero""" + np.random.seed(0) + result = F.vscharr(np.random.uniform(size=(10, 10)), + np.zeros((10, 10), bool)) + assert (np.all(result == 0)) + + +def test_vscharr_vertical(): + """Vertical Scharr on an edge should be a vertical line""" + i, j = np.mgrid[-5:6, -5:6] + image = (j >= 0).astype(float) + result = F.vscharr(image) + # Fudge the eroded points + j[np.abs(i) == 5] = 10000 + assert (np.all(result[j == 0] == 1)) + assert (np.all(result[np.abs(j) > 1] == 0)) + + +def test_vscharr_horizontal(): + """vertical Scharr on a horizontal edge should be zero""" + i, j = np.mgrid[-5:6, -5:6] + image = (i >= 0).astype(float) + result = F.vscharr(image) + eps = .000001 + assert (np.all(np.abs(result) < eps)) + + def test_prewitt_zeros(): """Prewitt on an array of all zeros""" result = F.prewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_prewitt_mask(): """Prewitt on a masked array should be zero""" np.random.seed(0) @@ -110,6 +222,7 @@ def test_prewitt_mask(): eps = .000001 assert (np.all(np.abs(result) < eps)) + def test_prewitt_horizontal(): """Prewitt on an edge should be a horizontal line""" i, j = np.mgrid[-5:6, -5:6] @@ -121,6 +234,7 @@ def test_prewitt_horizontal(): assert (np.all(result[i == 0] == 1)) assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) + def test_prewitt_vertical(): """Prewitt on a vertical edge should be a vertical line""" i, j = np.mgrid[-5:6, -5:6] @@ -137,6 +251,7 @@ def test_hprewitt_zeros(): result = F.hprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_hprewitt_mask(): """Horizontal prewitt on a masked array should be zero""" np.random.seed(0) @@ -145,6 +260,7 @@ def test_hprewitt_mask(): eps = .000001 assert (np.all(np.abs(result) < eps)) + def test_hprewitt_horizontal(): """Horizontal prewitt on an edge should be a horizontal line""" i, j = np.mgrid[-5:6, -5:6] @@ -156,6 +272,7 @@ def test_hprewitt_horizontal(): assert (np.all(result[i == 0] == 1)) assert (np.all(np.abs(result[np.abs(i) > 1]) < eps)) + def test_hprewitt_vertical(): """Horizontal prewitt on a vertical edge should be zero""" i, j = np.mgrid[-5:6, -5:6] @@ -170,6 +287,7 @@ def test_vprewitt_zeros(): result = F.vprewitt(np.zeros((10, 10)), np.ones((10, 10), bool)) assert (np.all(result == 0)) + def test_vprewitt_mask(): """Vertical prewitt on a masked array should be zero""" np.random.seed(0) @@ -177,6 +295,7 @@ def test_vprewitt_mask(): np.zeros((10, 10), bool)) assert (np.all(result == 0)) + def test_vprewitt_vertical(): """Vertical prewitt on an edge should be a vertical line""" i, j = np.mgrid[-5:6, -5:6] @@ -188,6 +307,7 @@ def test_vprewitt_vertical(): eps = .000001 assert (np.all(np.abs(result[np.abs(j) > 1]) < eps)) + def test_vprewitt_horizontal(): """Vertical prewitt on a horizontal edge should be zero""" i, j = np.mgrid[-5:6, -5:6] @@ -209,7 +329,7 @@ def test_horizontal_mask_line(): expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image, expected[4:7, 1:-1] = 0 # but line and neighbors masked - for grad_func in (F.hprewitt, F.hsobel): + for grad_func in (F.hprewitt, F.hsobel, F.hscharr): result = grad_func(vgrad, mask) yield assert_close, result, expected @@ -226,7 +346,7 @@ def test_vertical_mask_line(): expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image, expected[1:-1, 4:7] = 0 # but line and neighbors masked - for grad_func in (F.vprewitt, F.vsobel): + for grad_func in (F.vprewitt, F.vsobel, F.vscharr): result = grad_func(hgrad, mask) yield assert_close, result, expected diff --git a/skimage/filter/tests/test_lpi_filter.py b/skimage/filter/tests/test_lpi_filter.py index 08e46a58..f6176691 100644 --- a/skimage/filter/tests/test_lpi_filter.py +++ b/skimage/filter/tests/test_lpi_filter.py @@ -8,7 +8,8 @@ from skimage.io import * from skimage.filter import * -class TestLPIFilter2D(): +class TestLPIFilter2D(object): + img = imread(os.path.join(data_dir, 'camera.png'), flatten=True)[:50, :50] @@ -55,5 +56,9 @@ class TestLPIFilter2D(): g1 = wiener(F[::-1, ::-1], self.filt_func) assert ((g - g1[::-1, ::-1]).sum() < 1) + def test_non_callable(self): + assert_raises(ValueError, LPIFilter2D, None) + + if __name__ == "__main__": run_module_suite() diff --git a/skimage/filter/tests/test_tv_denoise.py b/skimage/filter/tests/test_tv_denoise.py deleted file mode 100644 index cc4fae7e..00000000 --- a/skimage/filter/tests/test_tv_denoise.py +++ /dev/null @@ -1,69 +0,0 @@ -import numpy as np -from numpy.testing import run_module_suite - -from skimage import filter, data, color - - -class TestTvDenoise(): - - def test_tv_denoise_2d(self): - """ - Apply the TV denoising algorithm on the lena image provided - by scipy - """ - # lena image - lena = color.rgb2gray(data.lena())[:256, :256] - # add noise to lena - lena += 0.5 * lena.std() * np.random.randn(*lena.shape) - # clip noise so that it does not exceed allowed range for float images. - lena = np.clip(lena, 0, 1) - # denoise - denoised_lena = filter.tv_denoise(lena, weight=60.0) - # which dtype? - assert denoised_lena.dtype in [np.float, np.float32, np.float64] - from scipy import ndimage - grad = ndimage.morphological_gradient(lena, size=((3, 3))) - grad_denoised = ndimage.morphological_gradient( - denoised_lena, size=((3, 3))) - # test if the total variation has decreased - assert grad_denoised.dtype == np.float - assert (np.sqrt((grad_denoised**2).sum()) - < np.sqrt((grad**2).sum()) / 2) - - def test_tv_denoise_float_result_range(self): - # lena image - lena = color.rgb2gray(data.lena())[:256, :256] - int_lena = np.multiply(lena, 255).astype(np.uint8) - assert np.max(int_lena) > 1 - denoised_int_lena = filter.tv_denoise(int_lena, weight=60.0) - # test if the value range of output float data is within [0.0:1.0] - assert denoised_int_lena.dtype == np.float - assert np.max(denoised_int_lena) <= 1.0 - assert np.min(denoised_int_lena) >= 0.0 - - def test_tv_denoise_3d(self): - """ - Apply the TV denoising algorithm on a 3D image representing - a sphere. - """ - x, y, z = np.ogrid[0:40, 0:40, 0:40] - mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2 - mask = 100 * mask.astype(np.float) - mask += 60 - mask += 20 * np.random.randn(*mask.shape) - mask[mask < 0] = 0 - mask[mask > 255] = 255 - res = filter.tv_denoise(mask.astype(np.uint8), weight=100) - assert res.dtype == np.float - assert res.std() * 255 < mask.std() - - # test wrong number of dimensions - a = np.random.random((8, 8, 8, 8)) - try: - res = filter.tv_denoise(a) - except ValueError: - pass - - -if __name__ == "__main__": - run_module_suite() diff --git a/skimage/graph/setup.py b/skimage/graph/setup.py index 2c432011..463d2739 100644 --- a/skimage/graph/setup.py +++ b/skimage/graph/setup.py @@ -29,10 +29,10 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Graph-based Image-processing Algorithms', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='Modified BSD', **(configuration(top_path='').todict()) ) diff --git a/skimage/io/_plugins/skivi.py b/skimage/io/_plugins/skivi.py index fb652638..fcfe8bc6 100644 --- a/skimage/io/_plugins/skivi.py +++ b/skimage/io/_plugins/skivi.py @@ -3,9 +3,9 @@ Skivi is written/maintained/developed by: S. Chris Colbert - sccolbert@gmail.com -Skivi is free software and is part of the scikits-image project. +Skivi is free software and is part of the scikit-image project. -Skivi is governed by the licenses of the scikits-image project. +Skivi is governed by the licenses of the scikit-image project. Please report any bugs to the author. diff --git a/skimage/io/setup.py b/skimage/io/setup.py index 55526461..4e3c09c3 100644 --- a/skimage/io/setup.py +++ b/skimage/io/setup.py @@ -31,10 +31,10 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Image I/O Routines', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='Modified BSD', **(configuration(top_path='').todict()) ) diff --git a/skimage/measure/_polygon.py b/skimage/measure/_polygon.py index add4b5fb..7b9b920b 100644 --- a/skimage/measure/_polygon.py +++ b/skimage/measure/_polygon.py @@ -28,7 +28,7 @@ def approximate_polygon(coords, tolerance): ---------- .. [1] http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm """ - if tolerance == 0: + if tolerance <= 0: return coords chain = np.zeros(coords.shape[0], 'bool') diff --git a/skimage/measure/find_contours.py b/skimage/measure/find_contours.py index 68e1d53c..3a546ccf 100755 --- a/skimage/measure/find_contours.py +++ b/skimage/measure/find_contours.py @@ -98,7 +98,7 @@ def find_contours(array, level, """ array = np.asarray(array, dtype=np.double) if array.ndim != 2: - raise RuntimeError('Only 2D arrays are supported.') + raise ValueError('Only 2D arrays are supported.') level = float(level) if (fully_connected not in _param_options or positive_orientation not in _param_options): diff --git a/skimage/measure/setup.py b/skimage/measure/setup.py index 7e4ecf0f..21d9964e 100644 --- a/skimage/measure/setup.py +++ b/skimage/measure/setup.py @@ -24,10 +24,10 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Graph-based Image-processing Algorithms', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='Modified BSD', **(configuration(top_path='').todict()) ) diff --git a/skimage/measure/tests/test_find_contours.py b/skimage/measure/tests/test_find_contours.py index 62b39b3f..11b9b443 100644 --- a/skimage/measure/tests/test_find_contours.py +++ b/skimage/measure/tests/test_find_contours.py @@ -21,34 +21,37 @@ r = np.sqrt(x**2 + y**2) def test_binary(): - contours = find_contours(a, 0.5) + ref = [[6. , 1.5], + [5. , 1.5], + [4. , 1.5], + [3. , 1.5], + [2. , 1.5], + [1.5, 2. ], + [1.5, 3. ], + [1.5, 4. ], + [1.5, 5. ], + [1.5, 6. ], + [1. , 6.5], + [0.5, 6. ], + [0.5, 5. ], + [0.5, 4. ], + [0.5, 3. ], + [0.5, 2. ], + [0.5, 1. ], + [1. , 0.5], + [2. , 0.5], + [3. , 0.5], + [4. , 0.5], + [5. , 0.5], + [6. , 0.5], + [6.5, 1. ], + [6. , 1.5]] + + contours = find_contours(a, 0.5, positive_orientation='high') assert len(contours) == 1 - assert_array_equal(contours[0], - [[6. , 1.5], - [5. , 1.5], - [4. , 1.5], - [3. , 1.5], - [2. , 1.5], - [1.5, 2. ], - [1.5, 3. ], - [1.5, 4. ], - [1.5, 5. ], - [1.5, 6. ], - [1. , 6.5], - [0.5, 6. ], - [0.5, 5. ], - [0.5, 4. ], - [0.5, 3. ], - [0.5, 2. ], - [0.5, 1. ], - [1. , 0.5], - [2. , 0.5], - [3. , 0.5], - [4. , 0.5], - [5. , 0.5], - [6. , 0.5], - [6.5, 1. ], - [6. , 1.5]]) + assert_array_equal(contours[0][::-1], ref) + + def test_float(): @@ -70,6 +73,11 @@ def test_memory_order(): assert len(contours) == 1 +def test_invalid_input(): + assert_raises(ValueError, find_contours, r, 0.5, 'foo', 'bar') + assert_raises(ValueError, find_contours, r[..., None], 0.5) + + if __name__ == '__main__': from numpy.testing import run_module_suite run_module_suite() diff --git a/skimage/measure/tests/test_polygon.py b/skimage/measure/tests/test_polygon.py index 1907d7cb..c96aed88 100644 --- a/skimage/measure/tests/test_polygon.py +++ b/skimage/measure/tests/test_polygon.py @@ -22,6 +22,8 @@ def test_approximate_polygon(): out = approximate_polygon(square, -1) np.testing.assert_array_equal(out, square) + out = approximate_polygon(square, 0) + np.testing.assert_array_equal(out, square) def test_subdivide_polygon(): @@ -51,6 +53,10 @@ def test_subdivide_polygon(): np.testing.assert_equal(new_square3.shape[0], 2 * (square3.shape[0] - mask_len + 2)) + # not supported B-Spline degree + np.testing.assert_raises(ValueError, subdivide_polygon, square, 0) + np.testing.assert_raises(ValueError, subdivide_polygon, square, 8) + if __name__ == "__main__": np.testing.run_module_suite() diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 271c1ec4..bd23d8b3 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -1,9 +1,9 @@ from numpy.testing import assert_array_equal, assert_almost_equal, \ - assert_array_almost_equal + assert_array_almost_equal, assert_raises import numpy as np import math -from skimage.measure import regionprops +from skimage.measure._regionprops import regionprops, PROPS, perimeter SAMPLE = np.array( @@ -22,6 +22,16 @@ INTENSITY_SAMPLE = SAMPLE.copy() INTENSITY_SAMPLE[1, 9:11] = 2 +def test_unsupported_dtype(): + assert_raises(TypeError, regionprops, np.zeros((10, 10), dtype=np.double)) + + +def test_all_props(): + props = regionprops(SAMPLE, 'all', INTENSITY_SAMPLE)[0] + for prop in PROPS: + assert prop in props + + def test_area(): area = regionprops(SAMPLE, ['Area'])[0]['Area'] assert area == np.sum(SAMPLE) @@ -36,6 +46,7 @@ def test_bbox(): bbox = regionprops(SAMPLE_mod, ['BoundingBox'])[0]['BoundingBox'] assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1)) + def test_central_moments(): mu = regionprops(SAMPLE, ['CentralMoments'])[0]['CentralMoments'] #: determined with OpenCV @@ -48,6 +59,7 @@ def test_central_moments(): assert_almost_equal(mu[2,1], 2000.296296296291) assert_almost_equal(mu[3,0], -760.0246913580195) + def test_centroid(): centroid = regionprops(SAMPLE, ['Centroid'])[0]['Centroid'] # determined with MATLAB @@ -90,6 +102,11 @@ def test_eccentricity(): eps = regionprops(SAMPLE, ['Eccentricity'])[0]['Eccentricity'] assert_almost_equal(eps, 0.814629313427) + img = np.zeros((5, 5), dtype=np.int) + img[2, 2] = 1 + eps = regionprops(img, ['Eccentricity'])[0]['Eccentricity'] + assert_almost_equal(eps, 0) + def test_equiv_diameter(): diameter = regionprops(SAMPLE, ['EquivDiameter'])[0]['EquivDiameter'] @@ -142,6 +159,11 @@ def test_filled_area(): assert area == np.sum(SAMPLE) +def test_filled_image(): + img = regionprops(SAMPLE, ['FilledImage'])[0]['FilledImage'] + assert_array_equal(img, SAMPLE) + + def test_major_axis_length(): length = regionprops(SAMPLE, ['MajorAxisLength'])[0]['MajorAxisLength'] # MATLAB has different interpretation of ellipse than found in literature, @@ -188,6 +210,7 @@ def test_moments(): assert_almost_equal(m[2,1], 43882.0) assert_almost_equal(m[3,0], 95588.0) + def test_normalized_moments(): nu = regionprops(SAMPLE, ['NormalizedMoments'])[0]['NormalizedMoments'] #: determined with OpenCV @@ -198,6 +221,7 @@ def test_normalized_moments(): assert_almost_equal(nu[2,1], 0.045473992910668816) assert_almost_equal(nu[3,0], -0.017278118992041805) + def test_orientation(): orientation = regionprops(SAMPLE, ['Orientation'])[0]['Orientation'] # determined with MATLAB @@ -219,15 +243,21 @@ def test_orientation(): )[0]['Orientation'] assert_almost_equal(orientation_diag, -math.pi / 4) + def test_perimeter(): - perimeter = regionprops(SAMPLE, ['Perimeter'])[0]['Perimeter'] - assert_almost_equal(perimeter, 59.2132034355964) + per = regionprops(SAMPLE, ['Perimeter'])[0]['Perimeter'] + assert_almost_equal(per, 59.2132034355964) + + per = perimeter(SAMPLE.astype('double'), neighbourhood=8) + assert_almost_equal(per, 43.1213203436) + def test_solidity(): solidity = regionprops(SAMPLE, ['Solidity'])[0]['Solidity'] # determined with MATLAB assert_almost_equal(solidity, 0.580645161290323) + def test_weighted_central_moments(): wmu = regionprops(SAMPLE, ['WeightedCentralMoments'], INTENSITY_SAMPLE )[0]['WeightedCentralMoments'] @@ -281,6 +311,7 @@ def test_weighted_moments(): ) assert_array_almost_equal(wm, ref) + def test_weighted_normalized_moments(): wnu = regionprops(SAMPLE, ['WeightedNormalizedMoments'], INTENSITY_SAMPLE )[0]['WeightedNormalizedMoments'] @@ -292,6 +323,7 @@ def test_weighted_normalized_moments(): ) assert_array_almost_equal(wnu, ref) + if __name__ == "__main__": from numpy.testing import run_module_suite run_module_suite() diff --git a/skimage/measure/tests/test_structural_similarity.py b/skimage/measure/tests/test_structural_similarity.py index 3eb2a7e9..ac45072a 100644 --- a/skimage/measure/tests/test_structural_similarity.py +++ b/skimage/measure/tests/test_structural_similarity.py @@ -1,5 +1,5 @@ import numpy as np -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_raises from skimage.measure import structural_similarity as ssim @@ -24,20 +24,19 @@ def test_ssim_image(): S1 = ssim(X, Y, win_size=3) assert(S1 < 0.3) -## Come up with a better way of testing the gradient -## -## def test_ssim_grad(): -## N = 30 -## X = np.random.random((N, N)) * 255 -## Y = np.random.random((N, N)) * 255 -## def func(Y): -## return ssim(X, Y, dynamic_range=255) +# NOTE: This test is known to randomly fail on some systems (Mac OS X 10.6) +def test_ssim_grad(): + N = 30 + X = np.random.random((N, N)) * 255 + Y = np.random.random((N, N)) * 255 -## def grad(Y): -## return ssim(X, Y, dynamic_range=255, gradient=True)[1] + f = ssim(X, Y, dynamic_range=255) + g = ssim(X, Y, dynamic_range=255, gradient=True) -## assert(np.all(opt.check_grad(func, grad, Y) < 0.05)) + assert f < 0.05 + assert g[0] < 0.05 + assert np.all(g[1] < 0.05) def test_ssim_dtype(): @@ -56,5 +55,15 @@ def test_ssim_dtype(): assert S2 < 0.1 +def test_invalid_input(): + X = np.zeros((3, 3), dtype=np.double) + Y = np.zeros((3, 3), dtype=np.int) + assert_raises(ValueError, ssim, X, Y) + + Y = np.zeros((4, 4), dtype=np.double) + assert_raises(ValueError, ssim, X, Y) + + assert_raises(ValueError, ssim, X, X, win_size=8) + if __name__ == "__main__": np.testing.run_module_suite() diff --git a/skimage/morphology/grey.py b/skimage/morphology/grey.py index e7e52de4..2cca69b6 100644 --- a/skimage/morphology/grey.py +++ b/skimage/morphology/grey.py @@ -1,10 +1,3 @@ -""" -:author: Damian Eads, 2009 -:license: modified BSD -""" - -__docformat__ = 'restructuredtext en' - import warnings from skimage import img_as_ubyte @@ -45,6 +38,7 @@ def erosion(image, selem, out=None, shift_x=False, shift_y=False): Examples -------- >>> # Erosion shrinks bright regions + >>> import numpy as np >>> from skimage.morphology import square >>> bright_square = np.array([[0, 0, 0, 0, 0], ... [0, 1, 1, 1, 0], @@ -97,6 +91,7 @@ def dilation(image, selem, out=None, shift_x=False, shift_y=False): Examples -------- >>> # Dilation enlarges bright regions + >>> import numpy as np >>> from skimage.morphology import square >>> bright_pixel = np.array([[0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0], @@ -146,6 +141,7 @@ def opening(image, selem, out=None): Examples -------- >>> # Open up gap between two bright regions (but also shrink regions) + >>> import numpy as np >>> from skimage.morphology import square >>> bad_connection = np.array([[1, 0, 0, 0, 1], ... [1, 1, 0, 1, 1], @@ -196,6 +192,7 @@ def closing(image, selem, out=None): Examples -------- >>> # Close a gap between two bright lines + >>> import numpy as np >>> from skimage.morphology import square >>> broken_line = np.array([[0, 0, 0, 0, 0], ... [0, 0, 0, 0, 0], @@ -245,6 +242,7 @@ def white_tophat(image, selem, out=None): Examples -------- >>> # Subtract grey background from bright peak + >>> import numpy as np >>> from skimage.morphology import square >>> bright_on_grey = np.array([[2, 3, 3, 3, 2], ... [3, 4, 5, 4, 3], @@ -261,7 +259,6 @@ def white_tophat(image, selem, out=None): """ if image is out: raise NotImplementedError("Cannot perform white top hat in place.") - image = img_as_ubyte(image) out = opening(image, selem, out=out) out = image - out @@ -294,6 +291,7 @@ def black_tophat(image, selem, out=None): Examples -------- >>> # Change dark peak to bright peak and subtract background + >>> import numpy as np >>> from skimage.morphology import square >>> dark_on_grey = np.array([[7, 6, 6, 6, 7], ... [6, 5, 4, 5, 6], @@ -311,7 +309,6 @@ def black_tophat(image, selem, out=None): if image is out: raise NotImplementedError("Cannot perform white top hat in place.") - image = img_as_ubyte(image) out = closing(image, selem, out=out) out = out - image diff --git a/skimage/morphology/setup.py b/skimage/morphology/setup.py index 02c39ae7..b8564ce4 100644 --- a/skimage/morphology/setup.py +++ b/skimage/morphology/setup.py @@ -39,11 +39,11 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', + setup(maintainer='scikit-image Developers', author='Damian Eads', - maintainer_email='scikits-image@googlegroups.com', + maintainer_email='scikit-image@googlegroups.com', description='Morphology Wrapper', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/segmentation/__init__.py b/skimage/segmentation/__init__.py index 9dca2bc3..7bd37026 100644 --- a/skimage/segmentation/__init__.py +++ b/skimage/segmentation/__init__.py @@ -2,5 +2,5 @@ from .random_walker_segmentation import random_walker from ._felzenszwalb import felzenszwalb from ._slic import slic from ._quickshift import quickshift -from .boundaries import find_boundaries, visualize_boundaries +from .boundaries import find_boundaries, visualize_boundaries, mark_boundaries from ._clear_border import clear_border diff --git a/skimage/segmentation/boundaries.py b/skimage/segmentation/boundaries.py index b40ecb01..25656a9b 100644 --- a/skimage/segmentation/boundaries.py +++ b/skimage/segmentation/boundaries.py @@ -1,19 +1,45 @@ import numpy as np from ..morphology import dilation, square from ..util import img_as_float +from ..color import gray2rgb +from .._shared.utils import deprecated def find_boundaries(label_img): + """Return bool array where boundaries between labeled regions are True.""" boundaries = np.zeros(label_img.shape, dtype=np.bool) boundaries[1:, :] += label_img[1:, :] != label_img[:-1, :] boundaries[:, 1:] += label_img[:, 1:] != label_img[:, :-1] return boundaries -def visualize_boundaries(img, label_img): - img = img_as_float(img, force_copy=True) +def mark_boundaries(image, label_img, color=(1, 1, 0), outline_color=None): + """Return image with boundaries between labeled regions highlighted. + + Parameters + ---------- + image : (M, N[, 3]) array + Grayscale or RGB image. + label_img : (M, N) array + Label array where regions are marked by different integer values. + color : length-3 sequence + RGB color of boundaries in the output image. + outline_color : length-3 sequence + RGB color surrounding boundaries in the output image. If None, no + outline is drawn. + """ + if image.ndim == 2: + image = gray2rgb(image) + image = img_as_float(image, force_copy=True) + boundaries = find_boundaries(label_img) - outer_boundaries = dilation(boundaries.astype(np.uint8), square(2)) - img[outer_boundaries != 0, :] = np.array([0, 0, 0]) # black - img[boundaries, :] = np.array([1, 1, 0]) # yellow - return img + if outline_color is not None: + outer_boundaries = dilation(boundaries.astype(np.uint8), square(2)) + image[outer_boundaries != 0, :] = np.array(outline_color) + image[boundaries, :] = np.array(color) + return image + + +@deprecated('mark_boundaries') +def visualize_boundaries(*args, **kwargs): + return mark_boundaries(*args, **kwargs) diff --git a/skimage/segmentation/setup.py b/skimage/segmentation/setup.py index b9e19078..ec4ac4ff 100644 --- a/skimage/segmentation/setup.py +++ b/skimage/segmentation/setup.py @@ -25,10 +25,10 @@ def configuration(parent_package='', top_path=None): if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Segmentation Algorithms', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py index a55d5df4..befcc23f 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 +from ._warps import swirl, homography, resize, rotate, rescale from .pyramids import (pyramid_reduce, pyramid_expand, pyramid_gaussian, pyramid_laplacian) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 6e04a271..1179811d 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1015,7 +1015,8 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, if mode == 'constant' and not (0 <= cval <= 1): clipped[out == cval] = cval - if clipped.shape[0] == 1 or clipped.shape[1] == 1: + if clipped.ndim == 3 and orig_ndim == 2: + # remove singleton dim introduced by atleast_3d + return clipped[..., 0] + else: return clipped - else: # remove singleton dim introduced by atleast_3d - return clipped.squeeze() diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 72f90050..6689430b 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -1,17 +1,21 @@ import numpy as np +from scipy import ndimage from ._geometric import (warp, SimilarityTransform, AffineTransform, ProjectiveTransform) def resize(image, output_shape, order=1, mode='constant', cval=0.): - """Resize image. + """Resize image to match a certain size. Parameters ---------- image : ndarray Input image. output_shape : tuple or ndarray - Size of the generated output image `(rows, cols)`. + Size of the generated output image `(rows, cols[, dim])`. If `dim` is + not provided, the number of channels are preserved. In case the number + of input channels does not equal the number of output channels a + 3-dimensional interpolation is applied. Returns ------- @@ -32,24 +36,88 @@ def resize(image, output_shape, order=1, mode='constant', cval=0.): """ - rows, cols = output_shape + rows, cols = output_shape[0], output_shape[1] orig_rows, orig_cols = image.shape[0], image.shape[1] - rscale = float(orig_rows) / rows - cscale = float(orig_cols) / cols + row_scale = float(orig_rows) / rows + col_scale = float(orig_cols) / cols - # 3 control points necessary to estimate exact AffineTransform - src_corners = np.array([[1, 1], [1, rows], [cols, rows]]) - 1 - dst_corners = np.zeros(src_corners.shape, dtype=np.double) - # take into account that 0th pixel is at position (0.5, 0.5) - dst_corners[:, 0] = cscale * (src_corners[:, 0] + 0.5) - 0.5 - dst_corners[:, 1] = rscale * (src_corners[:, 1] + 0.5) - 0.5 + # 3-dimensional interpolation + if len(output_shape) == 3 and (image.ndim == 2 + or output_shape[2] != image.shape[2]): + dim = output_shape[2] + orig_dim = 1 if image.ndim == 2 else image.shape[2] + dim_scale = float(orig_dim) / dim - tform = AffineTransform() - tform.estimate(src_corners, dst_corners) + map_rows, map_cols, map_dims = np.mgrid[:rows, :cols, :dim] + map_rows = row_scale * (map_rows + 0.5) - 0.5 + map_cols = col_scale * (map_cols + 0.5) - 0.5 + map_dims = dim_scale * (map_dims + 0.5) - 0.5 - return warp(image, tform, output_shape=output_shape, order=order, - mode=mode, cval=cval) + coord_map = np.array([map_rows, map_cols, map_dims]) + + out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, + cval=cval) + + else: # 2-dimensional interpolation + + # 3 control points necessary to estimate exact AffineTransform + src_corners = np.array([[1, 1], [1, rows], [cols, rows]]) - 1 + dst_corners = np.zeros(src_corners.shape, dtype=np.double) + # take into account that 0th pixel is at position (0.5, 0.5) + dst_corners[:, 0] = col_scale * (src_corners[:, 0] + 0.5) - 0.5 + dst_corners[:, 1] = row_scale * (src_corners[:, 1] + 0.5) - 0.5 + + tform = AffineTransform() + tform.estimate(src_corners, dst_corners) + + out = warp(image, tform, output_shape=output_shape, order=order, + mode=mode, cval=cval) + + return out + + +def rescale(image, scale, order=1, mode='constant', cval=0.): + """Scale image by a certain factor. + + Parameters + ---------- + image : ndarray + Input image. + scale : {float, tuple of floats} + Scale factors. Separate scale factors can be defined as + `(row_scale, col_scale)`. + + Returns + ------- + scaled : ndarray + Scaled version of the input. + + Other parameters + ---------------- + order : int + Order of splines used in interpolation. See + `scipy.ndimage.map_coordinates` for detail. + mode : string + How to handle values outside the image borders. See + `scipy.ndimage.map_coordinates` for detail. + cval : string + Used in conjunction with mode 'constant', the value outside + the image boundaries. + + """ + + try: + row_scale, col_scale = scale + except TypeError: + row_scale = col_scale = scale + + orig_rows, orig_cols = image.shape[0], image.shape[1] + rows = np.round(row_scale * orig_rows) + cols = np.round(col_scale * orig_cols) + output_shape = (rows, cols) + + return resize(image, output_shape, order=order, mode=mode, cval=cval) def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.): @@ -280,3 +348,6 @@ def homography(image, H, output_shape=None, order=1, 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/pyramids.py b/skimage/transform/pyramids.py index ccace457..19001de8 100644 --- a/skimage/transform/pyramids.py +++ b/skimage/transform/pyramids.py @@ -57,7 +57,7 @@ def pyramid_reduce(image, downscale=2, sigma=None, order=1, References ---------- - ..[1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf + .. [1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf """ @@ -111,7 +111,7 @@ def pyramid_expand(image, upscale=2, sigma=None, order=1, References ---------- - ..[1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf + .. [1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf """ @@ -176,7 +176,7 @@ def pyramid_gaussian(image, max_layer=-1, downscale=2, sigma=None, order=1, References ---------- - ..[1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf + .. [1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf """ @@ -257,8 +257,8 @@ def pyramid_laplacian(image, max_layer=-1, downscale=2, sigma=None, order=1, References ---------- - ..[1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf - ..[2] http://sepwww.stanford.edu/~morgan/texturematch/paper_html/node3.html + .. [1] http://web.mit.edu/persci/people/adelson/pub_pdfs/pyramid83.pdf + .. [2] http://sepwww.stanford.edu/~morgan/texturematch/paper_html/node3.html """ diff --git a/skimage/transform/setup.py b/skimage/transform/setup.py index cd2a0f8a..b0093d87 100644 --- a/skimage/transform/setup.py +++ b/skimage/transform/setup.py @@ -20,19 +20,17 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('_warps_cy', sources=['_warps_cy.c'], - include_dirs=[get_numpy_include_dirs(), '../_shared'], - extra_compile_args=['-fopenmp'], - extra_link_args=['-fopenmp']) + include_dirs=[get_numpy_include_dirs(), '../_shared']) return config if __name__ == '__main__': from numpy.distutils.core import setup - setup(maintainer='Scikits-image Developers', - author='Scikits-image Developers', - maintainer_email='scikits-image@googlegroups.com', + setup(maintainer='scikit-image Developers', + author='scikit-image Developers', + maintainer_email='scikit-image@googlegroups.com', description='Transforms', - url='https://github.com/scikits-image/scikits-image', + url='https://github.com/scikit-image/scikit-image', license='SciPy License (BSD Style)', **(configuration(top_path='').todict()) ) diff --git a/skimage/transform/tests/test_pyramids.py b/skimage/transform/tests/test_pyramids.py index 611ecdec..6d0609e0 100644 --- a/skimage/transform/tests/test_pyramids.py +++ b/skimage/transform/tests/test_pyramids.py @@ -1,41 +1,72 @@ -from numpy.testing import assert_array_equal, run_module_suite +from numpy.testing import assert_array_equal, assert_raises, run_module_suite from skimage import data -from skimage.transform import (pyramid_reduce, pyramid_expand, - pyramid_gaussian, pyramid_laplacian) +from skimage.transform import pyramids image = data.lena() +image_gray = image[..., 0] -def test_pyramid_reduce(): +def test_pyramid_reduce_rgb(): rows, cols, dim = image.shape - out = pyramid_reduce(image, downscale=2) + out = pyramids.pyramid_reduce(image, downscale=2) assert_array_equal(out.shape, (rows / 2, cols / 2, dim)) -def test_pyramid_expand(): +def test_pyramid_reduce_gray(): + rows, cols = image_gray.shape + out = pyramids.pyramid_reduce(image_gray, downscale=2) + assert_array_equal(out.shape, (rows / 2, cols / 2)) + + +def test_pyramid_expand_rgb(): rows, cols, dim = image.shape - out = pyramid_expand(image, upscale=2) + out = pyramids.pyramid_expand(image, upscale=2) assert_array_equal(out.shape, (rows * 2, cols * 2, dim)) -def test_build_gaussian_pyramid(): - rows, cols, dim = image.shape - pyramid = pyramid_gaussian(image, downscale=2) +def test_pyramid_expand_gray(): + rows, cols = image_gray.shape + out = pyramids.pyramid_expand(image_gray, upscale=2) + assert_array_equal(out.shape, (rows * 2, cols * 2)) + +def test_build_gaussian_pyramid_rgb(): + rows, cols, dim = image.shape + pyramid = pyramids.pyramid_gaussian(image, downscale=2) for layer, out in enumerate(pyramid): layer_shape = (rows / 2 ** layer, cols / 2 ** layer, dim) assert_array_equal(out.shape, layer_shape) -def test_build_laplacian_pyramid(): - rows, cols, dim = image.shape - pyramid = pyramid_laplacian(image, downscale=2) +def test_build_gaussian_pyramid_gray(): + rows, cols = image_gray.shape + pyramid = pyramids.pyramid_gaussian(image_gray, downscale=2) + for layer, out in enumerate(pyramid): + layer_shape = (rows / 2 ** layer, cols / 2 ** layer) + assert_array_equal(out.shape, layer_shape) + +def test_build_laplacian_pyramid_rgb(): + rows, cols, dim = image.shape + pyramid = pyramids.pyramid_laplacian(image, downscale=2) for layer, out in enumerate(pyramid): layer_shape = (rows / 2 ** layer, cols / 2 ** layer, dim) assert_array_equal(out.shape, layer_shape) +def test_build_laplacian_pyramid_gray(): + rows, cols = image_gray.shape + pyramid = pyramids.pyramid_laplacian(image_gray, downscale=2) + for layer, out in enumerate(pyramid): + layer_shape = (rows / 2 ** layer, cols / 2 ** layer) + assert_array_equal(out.shape, layer_shape) + + +def test_check_factor(): + assert_raises(ValueError, pyramids._check_factor, 0.99) + assert_raises(ValueError, pyramids._check_factor, - 2) + + if __name__ == "__main__": run_module_suite() diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index b705ac47..1993f932 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -2,7 +2,7 @@ from numpy.testing import assert_array_almost_equal, run_module_suite import numpy as np from scipy.ndimage import map_coordinates -from skimage.transform import (warp, warp_coords, rotate, resize, +from skimage.transform import (warp, warp_coords, rotate, resize, rescale, AffineTransform, ProjectiveTransform, SimilarityTransform, homography) @@ -85,7 +85,25 @@ def test_rotate(): assert_array_almost_equal(x90, np.rot90(x)) -def test_resize(): +def test_rescale(): + # same scale factor + x = np.zeros((5, 5), dtype=np.double) + x[1, 1] = 1 + scaled = rescale(x, 2, order=0) + ref = np.zeros((10, 10)) + ref[2:4, 2:4] = 1 + assert_array_almost_equal(scaled, ref) + + # different scale factors + x = np.zeros((5, 5), dtype=np.double) + x[1, 1] = 1 + scaled = rescale(x, (2, 1), order=0) + ref = np.zeros((10, 5)) + ref[2:4, 1] = 1 + assert_array_almost_equal(scaled, ref) + + +def test_resize2d(): x = np.zeros((5, 5), dtype=np.double) x[1, 1] = 1 resized = resize(x, (10, 10), order=0) @@ -94,6 +112,42 @@ def test_resize(): assert_array_almost_equal(resized, ref) +def test_resize3d_keep(): + # keep 3rd dimension + x = np.zeros((5, 5, 3), dtype=np.double) + x[1, 1, :] = 1 + resized = resize(x, (10, 10), order=0) + ref = np.zeros((10, 10, 3)) + ref[2:4, 2:4, :] = 1 + assert_array_almost_equal(resized, ref) + resized = resize(x, (10, 10, 3), order=0) + assert_array_almost_equal(resized, ref) + + +def test_resize3d_resize(): + # resize 3rd dimension + x = np.zeros((5, 5, 3), dtype=np.double) + x[1, 1, :] = 1 + resized = resize(x, (10, 10, 1), order=0) + ref = np.zeros((10, 10, 1)) + ref[2:4, 2:4] = 1 + assert_array_almost_equal(resized, ref) + + +def test_resize3d_bilinear(): + # bilinear 3rd dimension + x = np.zeros((5, 5, 2), dtype=np.double) + x[1, 1, 0] = 0 + x[1, 1, 1] = 1 + resized = resize(x, (10, 10, 1), order=1) + ref = np.zeros((10, 10, 1)) + ref[1:5, 1:5, :] = 0.03125 + ref[1:5, 2:4, :] = 0.09375 + ref[2:4, 1:5, :] = 0.09375 + ref[2:4, 2:4, :] = 0.28125 + assert_array_almost_equal(resized, ref) + + def test_swirl(): image = img_as_float(data.checkerboard()) diff --git a/skimage/util/montage.py b/skimage/util/montage.py index 1a8cda23..587c0939 100644 --- a/skimage/util/montage.py +++ b/skimage/util/montage.py @@ -71,6 +71,8 @@ def montage2d(arr_in, fill='mean', rescale_intensity=False): assert arr_in.ndim == 3 n_images, height, width = arr_in.shape + + arr_in = arr_in.copy() # -- rescale intensity if necessary if rescale_intensity: