Merge branch 'master' of git://github.com/scikit-image/scikit-image into filter_median_dtypes

This commit is contained in:
Marianne Corvellec
2013-10-14 11:25:08 -04:00
206 changed files with 12274 additions and 5331 deletions
+6 -2
View File
@@ -21,10 +21,12 @@ install:
- sudo apt-get install libfreeimage3
- if [[ $PYVER == '2.7' ]]; then sudo apt-get install $PYTHON-matplotlib; fi
- if [[ $PYVER == '3.2' ]]; then sudo pip-$PYVER install git+git://github.com/matplotlib/matplotlib.git@v1.2.x; fi
- sudo pip-$PYVER install flake8 --use-mirrors
- sudo pip-$PYVER install flake8
- $PYTHON setup.py build
- sudo $PYTHON setup.py install
script:
# Check if setup.py's match bento.info
- $PYTHON check_bento_build.py
# Change into an innocuous directory and find tests from installation
- mkdir $HOME/.matplotlib
- "echo 'backend : Agg' > $HOME/.matplotlib/matplotlibrc"
@@ -35,4 +37,6 @@ script:
# Change back to repository root directory and run all doc examples
- cd ..
- for f in doc/examples/*.py; do $PYTHON "$f"; if [ $? -ne 0 ]; then exit 1; fi done
- flake8 --exit-zero skimage doc/examples viewer_examples
- for f in doc/examples/applications/*.py; do $PYTHON "$f"; if [ $? -ne 0 ]; then exit 1; fi done
# Run pep8 and flake tests
- flake8 --exit-zero --exclude=test_*,six.py skimage doc/examples viewer_examples
+20
View File
@@ -154,6 +154,26 @@ detailing the test coverage::
skimage/filter/__init__ 1 1 100%
...
Activate Travis-CI for your fork (optional)
-------------------------------------------
Travis-CI checks all unittests in the project to prevent breakage.
Before sending a pull request, you may want to check that Travis-CI
successfully passes all tests. To do so,
* Go to `Travis-CI <http://travis-ci.org/>`__ and follow the Sign In link at the top
* Go to your `profile page <https://travis-ci.org/profile>`__ and switch on your
scikit-image fork
It corresponds to steps one and two in
`Travis-CI documentation <http://about.travis-ci.org/docs/user/getting-started/>`__
(Step three is already done in scikit-image).
Thus, as soon as you push your code to your fork, it will trigger Travis-CI,
and you will receive an email notification when the process is done.
Bugs
----
+16 -2
View File
@@ -114,7 +114,7 @@
- Joshua Warner
Multichannel random walker segmentation, unified peak finder backend,
n-dimensional array padding, bug and doc fixes.
n-dimensional array padding, marching cubes, bug and doc fixes.
- Petter Strandmark
Perimeter calculation in regionprops.
@@ -132,7 +132,8 @@
Dense DAISY feature description, circle perimeter drawing.
- François Boulogne
Drawing: Andres Method for circle perimeter, ellipse perimeter drawing, Bezier curve.
Drawing: Andres Method for circle perimeter, ellipse perimeter drawing,
Bezier curve, anti-aliasing.
Circular and elliptical Hough Transforms
Various fixes
@@ -144,3 +145,16 @@
- Jostein Bø Fløystad
Reconstruction circle mode for Radon transform
Simultaneous Algebraic Reconstruction Technique for inverse Radon transform
- Matt Terry
Color difference functions
- Eugene Dvoretsky
Yen threshold implementation.
- Riaan van den Dool
skimage.io plugin: GDAL
- Fedor Morozov
Drawing: Wu's anti-aliased circle
+17 -3
View File
@@ -2,11 +2,15 @@ Build Requirements
------------------
* `Python >= 2.5 <http://python.org>`__
* `Numpy >= 1.6 <http://numpy.scipy.org/>`__
* `Cython >= 0.15 <http://www.cython.org/>`__
* `Cython >= 0.17 <http://www.cython.org/>`__
`Matplotlib >= 1.0 <http://matplotlib.sf.net>`__ is needed to generate the
examples in the documentation.
You can use pip to automatically install the base dependencies as follows::
$ pip install -r requirements.txt
Runtime requirements
--------------------
* `SciPy >= 0.10 <http://scipy.org>`__
@@ -31,10 +35,20 @@ Optional Requirements
You can use this scikit with the basic requirements listed above, but some
functionality is only available with the following installed:
`PyQt4 <http://wiki.python.org/moin/PyQt>`__
* `PyQt4 <http://wiki.python.org/moin/PyQt>`__
The ``qt`` plugin that provides ``imshow(x, fancy=True)`` and `skivi`.
`FreeImage <http://freeimage.sf.net>`__
* `FreeImage <http://freeimage.sf.net>`__
The ``freeimage`` plugin provides support for reading various types of
image file formats, including multi-page TIFFs.
* `PyAMG <http://pyamg.org/>`__
The ``pyamg`` module is used for the fast `cg_mg` mode of random
walker segmentation.
Testing requirements
--------------------
* `Nose <https://nose.readthedocs.org/en/latest/>`__
A Python Unit Testing Framework
* `Coverage.py <http://nedbatchelder.com/code/coverage/>`__
A tool that generates a unit test code coverage report
+2
View File
@@ -1,6 +1,8 @@
How to make a new release of ``skimage``
========================================
- Check ``TODO.txt`` for any outstanding tasks.
- Update release notes.
- To show a list contributors, run ``doc/release/contributors.sh <commit>``,
+20
View File
@@ -0,0 +1,20 @@
Version 0.10
------------
* Remove deprecated functions in `skimage.filter.rank.*`
* Remove deprecated parameter `epsilon` of `skimage.viewer.LineProfile`
* Remove backwards-compatability of `skimage.measure.regionprops`
* Remove {`ratio`, `sigma`} deprecation warnings of `skimage.segmentation.slic`
* Change default mode of random_walker segmentation to 'cg_mg' > 'cg' > 'bf',
depending on which optional dependencies are available.
* Remove deprecated `out` parameter of `skimage.morphology.binary_*`
* Remove deprecated parameter `depth` in `skimage.segmentation.random_walker`
* Remove deprecated logger function in ``skimage/__init__.py``
Version 0.9
-----------
* Remove deprecated functions
- `skimage.filter.denoise_tv_chambolle`
- `skimage.morphology.is_local_maximum`
- `skimage.transform.hough`
- `skimage.transform.probabilistic_hough`
- `skimage.transform.hough_peaks`
+20 -26
View File
@@ -51,6 +51,9 @@ Library:
Extension: skimage.measure._moments
Sources:
skimage/measure/_moments.pyx
Extension: skimage.measure._marching_cubes_cy
Sources:
skimage/measure/_marching_cubes_cy.pyx
Extension: skimage.graph._mcp
Sources:
skimage/graph/_mcp.pyx
@@ -90,6 +93,12 @@ Library:
Extension: skimage.morphology._greyreconstruct
Sources:
skimage/morphology/_greyreconstruct.pyx
Extension: skimage.feature.censure_cy
Sources:
skimage/feature/censure_cy.pyx
Extension: skimage.feature._brief_cy
Sources:
skimage/feature/_brief_cy.pyx
Extension: skimage.feature.corner_cy
Sources:
skimage/feature/corner_cy.pyx
@@ -108,6 +117,9 @@ Library:
Extension: skimage.morphology._skeletonize_cy
Sources:
skimage/morphology/_skeletonize_cy.pyx
Extension: skimage.transform._radon_transform
Sources:
skimage/transform/_radon_transform.pyx
Extension: skimage.transform._warps_cy
Sources:
skimage/transform/_warps_cy.pyx
@@ -120,36 +132,18 @@ Library:
Extension: skimage._shared.geometry
Sources:
skimage/_shared/geometry.pyx
Extension: skimage.filter.rank._core16
Extension: skimage.filter.rank.generic_cy
Sources:
skimage/filter/rank/_core16.pyx
Extension: skimage.filter.rank._crank8
skimage/filter/rank/generic_cy.pyx
Extension: skimage.filter.rank.percentile_cy
Sources:
skimage/filter/rank/_crank8.pyx
Extension: skimage.filter.rank._crank16
skimage/filter/rank/percentile_cy.pyx
Extension: skimage.filter.rank.core_cy
Sources:
skimage/filter/rank/_crank16.pyx
Extension: skimage.filter.rank._core8
skimage/filter/rank/core_cy.pyx
Extension: skimage.filter.rank.bilateral_cy
Sources:
skimage/filter/rank/_core8.pyx
Extension: skimage.filter.rank.rank
Sources:
skimage/filter/rank/rank.pyx
Extension: skimage.filter.rank.bilateral_rank
Sources:
skimage/filter/rank/bilateral_rank.pyx
Extension: skimage.filter.rank._crank16_percentiles
Sources:
skimage/filter/rank/_crank16_percentiles.pyx
Extension: skimage.filter.rank.percentile_rank
Sources:
skimage/filter/rank/percentile_rank.pyx
Extension: skimage.filter.rank._crank8_percentiles
Sources:
skimage/filter/rank/_crank8_percentiles.pyx
Extension: skimage.filter.rank._crank16_bilateral
Sources:
skimage/filter/rank/_crank16_bilateral.pyx
skimage/filter/rank/bilateral_cy.pyx
Executable: skivi
Module: skimage.scripts.skivi
+4
View File
@@ -3,6 +3,7 @@ Check that Cython extensions in setup.py files match those in bento.info.
"""
import os
import re
import sys
RE_CYTHON = re.compile("config.add_extension\(\s*['\"]([\S]+)['\"]")
@@ -93,3 +94,6 @@ if __name__ == '__main__':
cy_bento, cy_setup = remove_common_extensions(cy_bento, cy_setup)
print_results(cy_bento, cy_setup)
if cy_setup or cy_bento:
sys.exit(1)
+9 -8
View File
@@ -2,9 +2,10 @@
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
PYTHON ?= python
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
PAPER ?=
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
@@ -36,14 +37,14 @@ clean:
-find ./source/auto_examples/* -type f | grep -v blank | xargs rm -f
api:
@mkdir -p source/api
python tools/build_modref_templates.py
$(PYTHON) tools/build_modref_templates.py
@echo "Build API docs...done."
random_gallery:
@cd source && python random_gallery.py
@cd source && $(PYTHON) random_gallery.py
coveragetable:
@cd source && python coverage_generator.py
@cd source && $(PYTHON) coverage_generator.py
html: api coveragetable random_gallery
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DEST)/html
@@ -120,10 +121,10 @@ doctest:
"results in build/doctest/output.txt."
gh-pages:
python gh-pages.py
$(PYTHON) gh-pages.py
gitwash:
python tools/gitwash/gitwash_dumper.py source scikit-image \
$(PYTHON) tools/gitwash/gitwash_dumper.py source scikit-image \
--project-url=http://scikit-image.org \
--project-ml-url=http://groups.google.com/group/scikit-image \
--repo-name=scikit-image \
+245 -193
View File
@@ -3,11 +3,11 @@
Rank filters
============
Rank filters are non-linear filters using the local greylevels ordering to
Rank filters are non-linear filters using the local gray-level ordering to
compute the filtered value. This ensemble of filters share a common base: the
local grey-level histogram extraction computed on the neighborhood of a pixel
(defined by a 2D structuring element). If the filtered value is taken as the
middle value of the histogram, we get the classical median filter.
local gray-level histogram is computed on the neighborhood of a pixel (defined
by a 2-D structuring element). If the filtered value is taken as the middle
value of the histogram, we get the classical median filter.
Rank filters can be used for several purposes such as:
@@ -26,11 +26,9 @@ Rank filters can be used for several purposes such as:
Some well known filters are specific cases of rank filters [1]_ e.g.
morphological dilation, morphological erosion, median filters.
The different implementation availables in `skimage` are compared.
In this example, we will see how to filter a greylevel image using some of the
linear and non-linear filters availables in skimage. We use the `camera`
image from `skimage.data`.
In this example, we will see how to filter a gray-level image using some of the
linear and non-linear filters available in skimage. We use the `camera` image
from `skimage.data` for all comparisons.
.. [1] Pierre Soille, On morphological operators based on rank filters, Pattern
Recognition 35 (2002) 527-535.
@@ -40,18 +38,19 @@ image from `skimage.data`.
import numpy as np
import matplotlib.pyplot as plt
from skimage import img_as_ubyte
from skimage import data
ima = data.camera()
hist = np.histogram(ima, bins=np.arange(0, 256))
noisy_image = img_as_ubyte(data.camera())
hist = np.histogram(noisy_image, bins=np.arange(0, 256))
plt.figure(figsize=(8, 3))
plt.subplot(1, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray, interpolation='nearest')
plt.imshow(noisy_image, interpolation='nearest')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.plot(hist[1][:-1], hist[0], lw=2)
plt.title('histogram of grey values')
plt.title('Histogram of grey values')
"""
@@ -65,50 +64,56 @@ randomly set to 0. The **median** filter is applied to remove the noise.
.. note::
there are different implementations of median filter :
There are different implementations of median filter:
`skimage.filter.median_filter` and `skimage.filter.rank.median`
"""
noise = np.random.random(ima.shape)
nima = data.camera()
nima[noise > 0.99] = 255
nima[noise < 0.01] = 0
from skimage.filter.rank import median
from skimage.morphology import disk
fig = plt.figure(figsize=[10, 7])
noise = np.random.random(noisy_image.shape)
noisy_image = img_as_ubyte(data.camera())
noisy_image[noise > 0.99] = 255
noisy_image[noise < 0.01] = 0
fig = plt.figure(figsize=(10, 7))
lo = median(nima, disk(1))
hi = median(nima, disk(5))
ext = median(nima, disk(20))
plt.subplot(2, 2, 1)
plt.imshow(nima, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('noised image')
plt.imshow(noisy_image, vmin=0, vmax=255)
plt.title('Noisy image')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(lo, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=1$')
plt.imshow(median(noisy_image, disk(1)), vmin=0, vmax=255)
plt.title('Median $r=1$')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(hi, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=5$')
plt.imshow(median(noisy_image, disk(5)), vmin=0, vmax=255)
plt.title('Median $r=5$')
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(ext, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=20$')
plt.imshow(median(noisy_image, disk(20)), vmin=0, vmax=255)
plt.title('Median $r=20$')
plt.axis('off')
"""
.. image:: PLOT2RST.current_figure
The added noise is efficiently removed, as the image defaults are small (1 pixel
wide), a small filter radius is sufficient. As the radius is increasing, objects
with a bigger size are filtered as well, such as the camera tripod. The median
filter is commonly used for noise removal because borders are preserved.
The added noise is efficiently removed, as the image defaults are small (1
pixel wide), a small filter radius is sufficient. As the radius is increasing,
objects with bigger sizes are filtered as well, such as the camera tripod. The
median filter is often used for noise removal because borders are preserved and
e.g. salt and pepper noise typically does not distort the gray-level.
Image smoothing
================
The example hereunder shows how a local **mean** smoothes the camera man image.
The example hereunder shows how a local **mean** filter smooths the camera man
image.
"""
@@ -116,13 +121,17 @@ from skimage.filter.rank import mean
fig = plt.figure(figsize=[10, 7])
loc_mean = mean(nima, disk(10))
loc_mean = mean(noisy_image, disk(10))
plt.subplot(1, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('original')
plt.imshow(noisy_image, vmin=0, vmax=255)
plt.title('Original')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(loc_mean, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('local mean $r=10$')
plt.imshow(loc_mean, vmin=0, vmax=255)
plt.title('Local mean $r=10$')
plt.axis('off')
"""
@@ -130,35 +139,41 @@ plt.xlabel('local mean $r=10$')
One may be interested in smoothing an image while preserving important borders
(median filters already achieved this), here we use the **bilateral** filter
that restricts the local neighborhood to pixel having a greylevel similar to
that restricts the local neighborhood to pixel having a gray-level similar to
the central one.
.. note::
a different implementation is available for color images in
A different implementation is available for color images in
`skimage.filter.denoise_bilateral`.
"""
from skimage.filter.rank import bilateral_mean
ima = data.camera()
selem = disk(10)
noisy_image = img_as_ubyte(data.camera())
bilat = bilateral_mean(ima.astype(np.uint16), disk(20), s0=10, s1=10)
bilat = bilateral_mean(noisy_image.astype(np.uint16), disk(20), s0=10, s1=10)
# display results
fig = plt.figure(figsize=[10, 7])
plt.subplot(2, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray)
plt.xlabel('original')
plt.imshow(noisy_image, cmap=plt.cm.gray)
plt.title('Original')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(bilat, cmap=plt.cm.gray)
plt.xlabel('bilateral mean')
plt.title('Bilateral mean')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.imshow(noisy_image[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(bilat[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
"""
@@ -175,7 +190,7 @@ We compare here how the global histogram equalization is applied locally.
The equalized image [2]_ has a roughly linear cumulative distribution function
for each pixel neighborhood. The local version [3]_ of the histogram
equalization emphasizes every local greylevel variations.
equalization emphasizes every local gray-level variations.
.. [2] http://en.wikipedia.org/wiki/Histogram_equalization
.. [3] http://en.wikipedia.org/wiki/Adaptive_histogram_equalization
@@ -185,101 +200,112 @@ equalization emphasizes every local greylevel variations.
from skimage import exposure
from skimage.filter import rank
ima = data.camera()
noisy_image = img_as_ubyte(data.camera())
# equalize globally and locally
glob = exposure.equalize(ima) * 255
loc = rank.equalize(ima, disk(20))
glob = exposure.equalize(noisy_image) * 255
loc = rank.equalize(noisy_image, disk(20))
# extract histogram for each image
hist = np.histogram(ima, bins=np.arange(0, 256))
hist = np.histogram(noisy_image, bins=np.arange(0, 256))
glob_hist = np.histogram(glob, bins=np.arange(0, 256))
loc_hist = np.histogram(loc, bins=np.arange(0, 256))
plt.figure(figsize=(10, 10))
plt.subplot(321)
plt.imshow(ima, cmap=plt.cm.gray, interpolation='nearest')
plt.imshow(noisy_image, interpolation='nearest')
plt.axis('off')
plt.subplot(322)
plt.plot(hist[1][:-1], hist[0], lw=2)
plt.title('histogram of grey values')
plt.title('Histogram of gray values')
plt.subplot(323)
plt.imshow(glob, cmap=plt.cm.gray, interpolation='nearest')
plt.imshow(glob, interpolation='nearest')
plt.axis('off')
plt.subplot(324)
plt.plot(glob_hist[1][:-1], glob_hist[0], lw=2)
plt.title('histogram of grey values')
plt.title('Histogram of gray values')
plt.subplot(325)
plt.imshow(loc, cmap=plt.cm.gray, interpolation='nearest')
plt.imshow(loc, interpolation='nearest')
plt.axis('off')
plt.subplot(326)
plt.plot(loc_hist[1][:-1], loc_hist[0], lw=2)
plt.title('histogram of grey values')
plt.title('Histogram of gray values')
"""
.. image:: PLOT2RST.current_figure
another way to maximize the number of greylevels used for an image is to apply
a local autoleveling, i.e. here a pixel greylevel is proportionally remapped
between local minimum and local maximum.
Another way to maximize the number of gray-levels used for an image is to apply
a local auto-leveling, i.e. the gray-value of a pixel is proportionally
remapped between local minimum and local maximum.
The following example shows how local autolevel enhances the camara man picture.
The following example shows how local auto-level enhances the camara man
picture.
"""
from skimage.filter.rank import autolevel
ima = data.camera()
selem = disk(10)
noisy_image = img_as_ubyte(data.camera())
auto = autolevel(ima.astype(np.uint16), disk(20))
auto = autolevel(noisy_image.astype(np.uint16), disk(20))
# display results
fig = plt.figure(figsize=[10, 7])
plt.subplot(1, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray)
plt.xlabel('original')
plt.imshow(noisy_image, cmap=plt.cm.gray)
plt.title('Original')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(auto, cmap=plt.cm.gray)
plt.xlabel('local autolevel')
plt.title('Local autolevel')
plt.axis('off')
"""
.. image:: PLOT2RST.current_figure
This filter is very sensitive to local outlayers, see the little white spot in
the sky left part. This is due to a local maximum which is very high comparing
to the rest of the neighborhood. One can moderate this using the percentile
version of the autolevel filter which uses given percentiles (one inferior,
one superior) in place of local minimum and maximum. The example below
illustrates how the percentile parameters influence the local autolevel result.
This filter is very sensitive to local outliers, see the little white spot in
the left part of the sky. This is due to a local maximum which is very high
comparing to the rest of the neighborhood. One can moderate this using the
percentile version of the auto-level filter which uses given percentiles (one
inferior, one superior) in place of local minimum and maximum. The example
below illustrates how the percentile parameters influence the local auto-level
result.
"""
from skimage.filter.rank import percentile_autolevel
from skimage.filter.rank import autolevel_percentile
image = data.camera()
selem = disk(20)
loc_autolevel = autolevel(image, selem=selem)
loc_perc_autolevel0 = percentile_autolevel(image, selem=selem, p0=.00, p1=1.0)
loc_perc_autolevel1 = percentile_autolevel(image, selem=selem, p0=.01, p1=.99)
loc_perc_autolevel2 = percentile_autolevel(image, selem=selem, p0=.05, p1=.95)
loc_perc_autolevel3 = percentile_autolevel(image, selem=selem, p0=.1, p1=.9)
loc_perc_autolevel0 = autolevel_percentile(image, selem=selem, p0=.00, p1=1.0)
loc_perc_autolevel1 = autolevel_percentile(image, selem=selem, p0=.01, p1=.99)
loc_perc_autolevel2 = autolevel_percentile(image, selem=selem, p0=.05, p1=.95)
loc_perc_autolevel3 = autolevel_percentile(image, selem=selem, p0=.1, p1=.9)
fig, axes = plt.subplots(nrows=3, figsize=(7, 8))
ax0, ax1, ax2 = axes
plt.gray()
ax0.imshow(np.hstack((image, loc_autolevel)))
ax0.set_title('original / autolevel')
ax0.set_title('Original / auto-level')
ax1.imshow(
np.hstack((loc_perc_autolevel0, loc_perc_autolevel1)), vmin=0, vmax=255)
ax1.set_title('percentile autolevel 0%,1%')
ax1.set_title('Percentile auto-level 0%,1%')
ax2.imshow(
np.hstack((loc_perc_autolevel2, loc_perc_autolevel3)), vmin=0, vmax=255)
ax2.set_title('percentile autolevel 5% and 10%')
ax2.set_title('Percentile auto-level 5% and 10%')
for ax in axes:
ax.axis('off')
@@ -289,29 +315,35 @@ for ax in axes:
.. image:: PLOT2RST.current_figure
The morphological contrast enhancement filter replaces the central pixel by the
local maximum if the original pixel value is closest to local maximum, otherwise
by the minimum local.
local maximum if the original pixel value is closest to local maximum,
otherwise by the minimum local.
"""
from skimage.filter.rank import morph_contr_enh
from skimage.filter.rank import enhance_contrast
ima = data.camera()
noisy_image = img_as_ubyte(data.camera())
enh = morph_contr_enh(ima, disk(5))
enh = enhance_contrast(noisy_image, disk(5))
# display results
fig = plt.figure(figsize=[10, 7])
plt.subplot(2, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray)
plt.xlabel('original')
plt.imshow(noisy_image, cmap=plt.cm.gray)
plt.title('Original')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(enh, cmap=plt.cm.gray)
plt.xlabel('local morphlogical contrast enhancement')
plt.title('Local morphological contrast enhancement')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.imshow(noisy_image[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(enh[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
"""
@@ -322,24 +354,30 @@ percentile *p0* and *p1* instead of the local minimum and maximum.
"""
from skimage.filter.rank import percentile_morph_contr_enh
from skimage.filter.rank import enhance_contrast_percentile
ima = data.camera()
noisy_image = img_as_ubyte(data.camera())
penh = percentile_morph_contr_enh(ima, disk(5), p0=.1, p1=.9)
penh = enhance_contrast_percentile(noisy_image, disk(5), p0=.1, p1=.9)
# display results
fig = plt.figure(figsize=[10, 7])
plt.subplot(2, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray)
plt.xlabel('original')
plt.imshow(noisy_image, cmap=plt.cm.gray)
plt.title('Original')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(penh, cmap=plt.cm.gray)
plt.xlabel('local percentile morphlogical\n contrast enhancement')
plt.title('Local percentile morphological\n contrast enhancement')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.imshow(noisy_image[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(penh[200:350, 350:450], cmap=plt.cm.gray)
plt.axis('off')
"""
@@ -348,18 +386,18 @@ plt.imshow(penh[200:350, 350:450], cmap=plt.cm.gray)
Image threshold
===============
The Otsu's threshold [1]_ method can be applied locally using the local
greylevel distribution. In the example below, for each pixel, an "optimal"
threshold is determined by maximizing the variance between two classes of pixels
of the local neighborhood defined by a structuring element.
The Otsu threshold [1]_ method can be applied locally using the local gray-
level distribution. In the example below, for each pixel, an "optimal"
threshold is determined by maximizing the variance between two classes of
pixels of the local neighborhood defined by a structuring element.
The example compares the local threshold with the global threshold
`skimage.filter.threshold_otsu`.
.. note::
Local thresholding is much slower than global one. There exists a function
for global Otsu thresholding: `skimage.filter.threshold_otsu`.
Local is much slower than global thresholding. A function for global Otsu
thresholding can be found in : `skimage.filter.threshold_otsu`.
.. [4] http://en.wikipedia.org/wiki/Otsu's_method
@@ -382,27 +420,35 @@ t_glob_otsu = threshold_otsu(p8)
glob_otsu = p8 >= t_glob_otsu
plt.figure()
plt.subplot(2, 2, 1)
plt.imshow(p8, cmap=plt.cm.gray)
plt.xlabel('original')
plt.title('Original')
plt.colorbar()
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(t_loc_otsu, cmap=plt.cm.gray)
plt.xlabel('local Otsu ($radius=%d$)' % radius)
plt.title('Local Otsu ($r=%d$)' % radius)
plt.colorbar()
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(p8 >= t_loc_otsu, cmap=plt.cm.gray)
plt.xlabel('original>=local Otsu' % t_glob_otsu)
plt.title('Original >= local Otsu' % t_glob_otsu)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(glob_otsu, cmap=plt.cm.gray)
plt.xlabel('global Otsu ($t=%d$)' % t_glob_otsu)
plt.title('Global Otsu ($t=%d$)' % t_glob_otsu)
plt.axis('off')
"""
.. image:: PLOT2RST.current_figure
The following example shows how local Otsu's threshold handles a global level
shift applied to a synthetic image .
The following example shows how local Otsu thresholding handles a global level
shift applied to a synthetic image.
"""
@@ -413,13 +459,18 @@ m = (np.tile(x, (n, 1)) * np.linspace(0.1, 1, n) * 128 + 128).astype(np.uint8)
radius = 10
t = rank.otsu(m, disk(radius))
plt.figure()
plt.subplot(1, 2, 1)
plt.imshow(m)
plt.xlabel('original')
plt.title('Original')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(m >= t, interpolation='nearest')
plt.xlabel('local Otsu ($radius=%d$)' % radius)
plt.title('Local Otsu ($r=%d$)' % radius)
plt.axis('off')
"""
@@ -428,7 +479,7 @@ plt.xlabel('local Otsu ($radius=%d$)' % radius)
Image morphology
================
Local maximum and local minimum are the base operators for greylevel
Local maximum and local minimum are the base operators for gray-level
morphology.
.. note::
@@ -436,33 +487,41 @@ morphology.
`skimage.dilate` and `skimage.erode` are equivalent filters (see below for
comparison).
Here is an example of the classical morphological greylevel filters: opening,
Here is an example of the classical morphological gray-level filters: opening,
closing and morphological gradient.
"""
from skimage.filter.rank import maximum, minimum, gradient
ima = data.camera()
noisy_image = img_as_ubyte(data.camera())
closing = maximum(minimum(ima, disk(5)), disk(5))
opening = minimum(maximum(ima, disk(5)), disk(5))
grad = gradient(ima, disk(5))
closing = maximum(minimum(noisy_image, disk(5)), disk(5))
opening = minimum(maximum(noisy_image, disk(5)), disk(5))
grad = gradient(noisy_image, disk(5))
# display results
fig = plt.figure(figsize=[10, 7])
plt.subplot(2, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray)
plt.xlabel('original')
plt.imshow(noisy_image, cmap=plt.cm.gray)
plt.title('Original')
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(closing, cmap=plt.cm.gray)
plt.xlabel('greylevel closing')
plt.title('Gray-level closing')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(opening, cmap=plt.cm.gray)
plt.xlabel('greylevel opening')
plt.title('Gray-level opening')
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(grad, cmap=plt.cm.gray)
plt.xlabel('morphological gradient')
plt.title('Morphological gradient')
plt.axis('off')
"""
@@ -471,13 +530,14 @@ plt.xlabel('morphological gradient')
Feature extraction
===================
Local histogram can be exploited to compute local entropy, which is related to
Local histograms can be exploited to compute local entropy, which is related to
the local image complexity. Entropy is computed using base 2 logarithm i.e. the
filter returns the minimum number of bits needed to encode local greylevel
filter returns the minimum number of bits needed to encode local gray-level
distribution.
`skimage.rank.entropy` returns local entropy on a given structuring element.
The following example shows this filter applied on 8- and 16- bit images.
`skimage.rank.entropy` returns the local entropy on a given structuring
element. The following example shows applies this filter on 8- and 16-bit
images.
.. note::
@@ -492,47 +552,36 @@ from skimage.morphology import disk
import numpy as np
import matplotlib.pyplot as plt
# defining a 8- and a 16-bit test images
a8 = data.camera()
a16 = data.camera().astype(np.uint16) * 4
image = data.camera()
ent8 = entropy(a8, disk(5)) # pixel value contain 10x the local entropy
ent16 = entropy(a16, disk(5)) # pixel value contain 1000x the local entropy
plt.figure(figsize=(10, 4))
# display results
plt.figure(figsize=(10, 10))
plt.subplot(2, 2, 1)
plt.imshow(a8, cmap=plt.cm.gray)
plt.xlabel('8-bit image')
plt.subplot(1, 2, 1)
plt.imshow(image, cmap=plt.cm.gray)
plt.title('Image')
plt.colorbar()
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(ent8, cmap=plt.cm.jet)
plt.xlabel('entropy*10')
plt.colorbar()
plt.subplot(2, 2, 3)
plt.imshow(a16, cmap=plt.cm.gray)
plt.xlabel('16-bit image')
plt.colorbar()
plt.subplot(2, 2, 4)
plt.imshow(ent16, cmap=plt.cm.jet)
plt.xlabel('entropy*1000')
plt.subplot(1, 2, 2)
plt.imshow(entropy(image, disk(5)), cmap=plt.cm.jet)
plt.title('Entropy')
plt.colorbar()
plt.axis('off')
"""
.. image:: PLOT2RST.current_figure
Implementation
================
==============
The central part of the `skimage.rank` filters is build on a sliding window that
update local greylevel histogram. This approach limits the algorithm complexity
to O(n) where n is the number of image pixels. The complexity is also limited
with respect to the structuring element size.
The central part of the `skimage.rank` filters is build on a sliding window
that updates the local gray-level histogram. This approach limits the algorithm
complexity to O(n) where n is the number of image pixels. The complexity is
also limited with respect to the structuring element size.
In the following we compare the performance of different implementations
available in `skimage`.
"""
@@ -583,10 +632,10 @@ def ndi_med(image, n):
Comparison between
* `rank.maximum`
* `cmorph.dilate`
* `filter.rank.maximum`
* `morphology.dilate`
on increasing structuring element size
on increasing structuring element size:
"""
@@ -603,18 +652,18 @@ for r in e_range:
rec = np.asarray(rec)
plt.figure()
plt.title('increasing element size')
plt.ylabel('time (ms)')
plt.xlabel('element radius')
plt.title('Performance with respect to element size')
plt.ylabel('Time (ms)')
plt.title('Element radius')
plt.plot(e_range, rec)
plt.legend(['crank.maximum', 'cmorph.dilate'])
plt.legend(['filter.rank.maximum', 'morphology.dilate'])
"""
and increasing image size
.. image:: PLOT2RST.current_figure
and increasing image size:
"""
r = 9
@@ -623,7 +672,7 @@ elem = disk(r + 1)
rec = []
s_range = range(100, 1000, 100)
for s in s_range:
a = (np.random.random((s, s)) * 256).astype('uint8')
a = (np.random.random((s, s)) * 256).astype(np.uint8)
(rc, ms_rc) = cr_max(a, elem)
(rcm, ms_rcm) = cm_dil(a, elem)
rec.append((ms_rc, ms_rcm))
@@ -631,11 +680,11 @@ for s in s_range:
rec = np.asarray(rec)
plt.figure()
plt.title('increasing image size')
plt.ylabel('time (ms)')
plt.xlabel('image size')
plt.title('Performance with respect to image size')
plt.ylabel('Time (ms)')
plt.title('Image size')
plt.plot(s_range, rec)
plt.legend(['crank.maximum', 'cmorph.dilate'])
plt.legend(['filter.rank.maximum', 'morphology.dilate'])
"""
@@ -644,11 +693,11 @@ plt.legend(['crank.maximum', 'cmorph.dilate'])
Comparison between:
* `rank.median`
* `ctmf.median_filter`
* `ndimage.percentile`
* `filter.rank.median`
* `filter.median_filter`
* `scipy.ndimage.percentile`
on increasing structuring element size
on increasing structuring element size:
"""
@@ -666,27 +715,29 @@ for r in e_range:
rec = np.asarray(rec)
plt.figure()
plt.title('increasing element size')
plt.title('Performance with respect to element size')
plt.plot(e_range, rec)
plt.legend(['rank.median', 'ctmf.median_filter', 'ndimage.percentile'])
plt.ylabel('time (ms)')
plt.xlabel('element radius')
plt.legend(['filter.rank.median', 'filter.median_filter',
'scipy.ndimage.percentile'])
plt.ylabel('Time (ms)')
plt.title('Element radius')
"""
.. image:: PLOT2RST.current_figure
comparison of outcome of the three methods
Comparison of outcome of the three methods:
"""
plt.figure()
plt.imshow(np.hstack((rc, rctmf, rndi)))
plt.xlabel('rank.median vs ctmf.median_filter vs ndimage.percentile')
plt.title('filter.rank.median vs filtermedian_filter vs scipy.ndimage.percentile')
plt.axis('off')
"""
.. image:: PLOT2RST.current_figure
and increasing image size
and increasing image size:
"""
@@ -696,7 +747,7 @@ elem = disk(r + 1)
rec = []
s_range = [100, 200, 500, 1000]
for s in s_range:
a = (np.random.random((s, s)) * 256).astype('uint8')
a = (np.random.random((s, s)) * 256).astype(np.uint8)
(rc, ms_rc) = cr_med(a, elem)
rctmf, ms_rctmf = ctmf_med(a, r)
rndi, ms_ndi = ndi_med(a, r)
@@ -705,11 +756,12 @@ for s in s_range:
rec = np.asarray(rec)
plt.figure()
plt.title('increasing image size')
plt.title('Performance with respect to image size')
plt.plot(s_range, rec)
plt.legend(['rank.median', 'ctmf.median_filter', 'ndimage.percentile'])
plt.ylabel('time (ms)')
plt.xlabel('image size')
plt.legend(['filter.rank.median', 'filter.median_filter',
'scipy.ndimage.percentile'])
plt.ylabel('Time (ms)')
plt.title('Image size')
"""
.. image:: PLOT2RST.current_figure
+148
View File
@@ -0,0 +1,148 @@
"""
========================================
Circular and Elliptical Hough Transforms
========================================
The Hough transform in its simplest form is a `method to detect
straight lines <http://en.wikipedia.org/wiki/Hough_transform>`__
but it can also be used to detect circles or ellipses.
The algorithm assumes that the edge is detected and it is robust against
noise or missing points.
Circle detection
================
In the following example, the Hough transform is used to detect
coin positions and match their edges. We provide a range of
plausible radii. For each radius, two circles are extracted and
we finally keep the five most prominent candidates.
The result shows that coin positions are well-detected.
Algorithm overview
------------------
Given a black circle on a white background, we first guess its
radius (or a range of radii) to construct a new circle.
This circle is applied on each black pixel of the original picture
and the coordinates of this circle are voting in an accumulator.
From this geometrical construction, the original circle center
position receives the highest score.
Note that the accumulator size is built to be larger than the
original picture in order to detect centers outside the frame.
Its size is extended by two times the larger radius.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, filter, color
from skimage.transform import hough_circle
from skimage.feature import peak_local_max
from skimage.draw import circle_perimeter
from skimage.util import img_as_ubyte
# Load picture and detect edges
image = img_as_ubyte(data.coins()[0:95, 70:370])
edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
# Detect two radii
hough_radii = np.arange(15, 30, 2)
hough_res = hough_circle(edges, hough_radii)
centers = []
accums = []
radii = []
for radius, h in zip(hough_radii, hough_res):
# For each radius, extract two circles
peaks = peak_local_max(h, num_peaks=2)
centers.extend(peaks)
accums.extend(h[peaks[:, 0], peaks[:, 1]])
radii.extend([radius, radius])
# Draw the most prominent 5 circles
image = color.gray2rgb(image)
for idx in np.argsort(accums)[::-1][:5]:
center_x, center_y = centers[idx]
radius = radii[idx]
cx, cy = circle_perimeter(center_y, center_x, radius)
image[cy, cx] = (220, 20, 20)
ax.imshow(image, cmap=plt.cm.gray)
"""
Ellipse detection
=================
In this second example, the aim is to detect the edge of a coffee cup.
Basically, this is a projection of a circle, i.e. an ellipse.
The problem to solve is much more difficult because five parameters have to be
determined, instead of three for circles.
Algorithm overview
------------------
The algorithm takes two different points belonging to the ellipse. It assumes
that it is the main axis. A loop on all the other points determines how much
an ellipse passes to them. A good match corresponds to high accumulator values.
A full description of the algorithm can be found in reference [1]_.
References
----------
.. [1] Xie, Yonghong, and Qiang Ji. "A new efficient ellipse detection
method." Pattern Recognition, 2002. Proceedings. 16th International
Conference on. Vol. 2. IEEE, 2002
"""
import matplotlib.pyplot as plt
from skimage import data, filter, color
from skimage.transform import hough_ellipse
from skimage.draw import ellipse_perimeter
# Load picture, convert to grayscale and detect edges
image_rgb = data.coffee()[0:220, 160:420]
image_gray = color.rgb2gray(image_rgb)
edges = filter.canny(image_gray, sigma=2.0,
low_threshold=0.55, high_threshold=0.8)
# Perform a Hough Transform
# The accuracy corresponds to the bin size of a major axis.
# The value is chosen in order to get a single high accumulator.
# The threshold eliminates low accumulators
result = hough_ellipse(edges, accuracy=20, threshold=250,
min_size=100, max_size=120)
result.sort(order='accumulator')
# Estimated parameters for the ellipse
best = result[-1]
yc = int(best[1])
xc = int(best[2])
a = int(best[3])
b = int(best[4])
orientation = best[5]
# Draw the ellipse on the original image
cy, cx = ellipse_perimeter(yc, xc, a, b, orientation)
image_rgb[cy, cx] = (0, 0, 255)
# Draw the edge (white) and the resulting ellipse (red)
edges = color.gray2rgb(edges)
edges[cy, cx] = (250, 0, 0)
fig2, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, figsize=(10, 6))
ax1.set_title('Original picture')
ax1.imshow(image_rgb)
ax2.set_title('Edge (white) and result (red)')
ax2.imshow(edges)
plt.show()
@@ -1,72 +0,0 @@
"""
========================
Circular Hough Transform
========================
The Hough transform in its simplest form is a `method to detect
straight lines <http://en.wikipedia.org/wiki/Hough_transform>`__
but it can also be used to detect circles.
In the following example, the Hough transform is used to detect
coin positions and match their edges. We provide a range of
plausible radii. For each radius, two circles are extracted and
we finally keep the five most prominent candidates.
The result shows that coin positions are well-detected.
Algorithm overview
------------------
Given a black circle on a white background, we first guess its
radius (or a range of radii) to construct a new circle.
This circle is applied on each black pixel of the original picture
and the coordinates of this circle are voting in an accumulator.
From this geometrical construction, the original circle center
position receives the highest score.
Note that the accumulator size is built to be larger than the
original picture in order to detect centers outside the frame.
Its size is extended by two times the larger radius.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, filter, color
from skimage.transform import hough_circle
from skimage.feature import peak_local_max
from skimage.draw import circle_perimeter
from skimage.util import img_as_ubyte
# Load picture and detect edges
image = img_as_ubyte(data.coins()[0:95, 70:370])
edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
# Detect two radii
hough_radii = np.arange(15, 30, 2)
hough_res = hough_circle(edges, hough_radii)
centers = []
accums = []
radii = []
for radius, h in zip(hough_radii, hough_res):
# For each radius, extract two circles
peaks = peak_local_max(h, num_peaks=2)
centers.extend(peaks)
accums.extend(h[peaks[:, 0], peaks[:, 1]])
radii.extend([radius, radius])
# Draw the most prominent 5 circles
image = color.gray2rgb(image)
for idx in np.argsort(accums)[::-1][:5]:
center_x, center_y = centers[idx]
radius = radii[idx]
cx, cy = circle_perimeter(center_y, center_x, radius)
image[cy, cx] = (220, 20, 20)
ax.imshow(image, cmap=plt.cm.gray)
plt.show()
+18 -3
View File
@@ -27,9 +27,24 @@ image = np.array(
[0, 1, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=float)
chull = convex_hull_image(image)
image[chull] += 1.7
image -= -1.7
original_image = np.copy(image)
chull = convex_hull_image(image)
image[chull] += 1
# image is now:
#[[ 0. 0. 0. 0. 0. 0. 0. 0. 0.]
# [ 0. 0. 0. 0. 2. 0. 0. 0. 0.]
# [ 0. 0. 0. 2. 1. 2. 0. 0. 0.]
# [ 0. 0. 2. 1. 1. 1. 2. 0. 0.]
# [ 0. 2. 1. 1. 1. 1. 1. 2. 0.]
# [ 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
fig = plt.subplots(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.title('Original picture')
plt.imshow(original_image, cmap=plt.cm.gray, interpolation='nearest')
plt.subplot(1, 2, 2)
plt.title('Transformed picture')
plt.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
plt.show()
+13 -26
View File
@@ -3,8 +3,10 @@
Entropy
=======
Image entropy is a quantity which is used to describe the amount of information
coded in an image.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
@@ -13,33 +15,18 @@ from skimage.morphology import disk
from skimage.util import img_as_ubyte
# defining a 8- and a 16-bit test images
a8 = img_as_ubyte(data.camera())
a16 = a8.astype(np.uint16) * 4
image = img_as_ubyte(data.camera())
ent8 = entropy(a8, disk(5)) # pixel value contain 10x the local entropy
ent16 = entropy(a16, disk(5)) # pixel value contain 1000x the local entropy
fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(10, 4))
# display results
plt.figure(figsize=(10, 10))
img0 = ax0.imshow(image, cmap=plt.cm.gray)
ax0.set_title('Image')
ax0.axis('off')
plt.colorbar(img0, ax=ax0)
plt.subplot(2,2,1)
plt.imshow(a8, cmap=plt.cm.gray)
plt.xlabel('8-bit image')
plt.colorbar()
img1 = ax1.imshow(entropy(image, disk(5)), cmap=plt.cm.jet)
ax1.set_title('Entropy')
ax1.axis('off')
plt.colorbar(img1, ax=ax1)
plt.subplot(2,2,2)
plt.imshow(ent8, cmap=plt.cm.jet)
plt.xlabel('entropy*10')
plt.colorbar()
plt.subplot(2,2,3)
plt.imshow(a16, cmap=plt.cm.gray)
plt.xlabel('16-bit image')
plt.colorbar()
plt.subplot(2,2,4)
plt.imshow(ent16, cmap=plt.cm.jet)
plt.xlabel('entropy*1000')
plt.colorbar()
plt.show()
+6 -10
View File
@@ -18,27 +18,23 @@ from skimage.filter import sobel
from skimage.segmentation import slic, join_segmentations
from skimage.morphology import watershed
from skimage.color import label2rgb
from skimage import data
from skimage import data, img_as_float
coins = data.coins()
coins = img_as_float(data.coins())
# make segmentation using edge-detection and watershed
edges = sobel(coins)
markers = np.zeros_like(coins)
foreground, background = 1, 2
markers[coins < 30] = background
markers[coins > 150] = foreground
markers[coins < 30.0 / 255] = background
markers[coins > 150.0 / 255] = foreground
ws = watershed(edges, markers)
seg1 = nd.label(ws == foreground)[0]
# make segmentation using SLIC superpixels
# make the RGB equivalent of `coins`
coins_colour = np.tile(coins[..., np.newaxis], (1, 1, 3))
seg2 = slic(coins_colour, n_segments=30, max_iter=160, sigma=1, ratio=9,
convert2lab=False)
seg2 = slic(coins, n_segments=117, max_iter=160, sigma=1, compactness=0.75,
multichannel=False)
# combine the two
segj = join_segmentations(seg1, seg2)
@@ -1,7 +1,7 @@
"""
===============
Hough transform
===============
r"""
=============================
Straight line Hough transform
=============================
The Hough transform in its simplest form is a `method to detect straight lines
<http://en.wikipedia.org/wiki/Hough_transform>`__.
+163 -26
View File
@@ -4,27 +4,159 @@ Local Binary Pattern for texture classification
===============================================
In this example, we will see how to classify textures based on LBP (Local
Binary Pattern). The histogram of the LBP result is a good measure to classify
textures. For simplicity the histogram distributions are then tested against
each other using the Kullback-Leibler-Divergence.
Binary Pattern). LBP looks at points surrounding a central point and tests
whether the surrounding points are greater than or less than the central point
(i.e. gives a binary result).
Before trying out LBP on an image, it helps to look at a schematic of LBPs.
The below code is just used to plot the schematic.
"""
from __future__ import print_function
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
METHOD = 'uniform'
plt.rcParams['font.size'] = 9
def plot_circle(ax, center, radius, color):
circle = plt.Circle(center, radius, facecolor=color, edgecolor='0.5')
ax.add_patch(circle)
def plot_lbp_model(ax, binary_values):
"""Draw the schematic for a local binary pattern."""
# Geometry spec
theta = np.deg2rad(45)
R = 1
r = 0.15
w = 1.5
gray = '0.5'
# Draw the central pixel.
plot_circle(ax, (0, 0), radius=r, color=gray)
# Draw the surrounding pixels.
for i, facecolor in enumerate(binary_values):
x = R * np.cos(i * theta)
y = R * np.sin(i * theta)
plot_circle(ax, (x, y), radius=r, color=str(facecolor))
# Draw the pixel grid.
for x in np.linspace(-w, w, 4):
ax.axvline(x, color=gray)
ax.axhline(x, color=gray)
# Tweak the layout.
ax.axis('image')
ax.axis('off')
size = w + 0.2
ax.set_xlim(-size, size)
ax.set_ylim(-size, size)
fig, axes = plt.subplots(ncols=5, figsize=(7, 2))
titles = ['flat', 'flat', 'edge', 'corner', 'non-uniform']
binary_patterns = [np.zeros(8),
np.ones(8),
np.hstack([np.ones(4), np.zeros(4)]),
np.hstack([np.zeros(3), np.ones(5)]),
[1, 0, 0, 1, 1, 1, 0, 0]]
for ax, values, name in zip(axes, binary_patterns, titles):
plot_lbp_model(ax, values)
ax.set_title(name)
"""
.. image:: PLOT2RST.current_figure
The figure above shows example results with black (or white) representing
pixels that are less (or more) intense than the central pixel. When surrounding
pixels are all black or all white, then that image region is flat (i.e.
featureless). Groups of continuous black or white pixels are considered
"uniform" patterns that can be interpreted as corners or edges. If pixels
switch back-and-forth between black and white pixels, the pattern is considered
"non-uniform".
When using LBP to detect texture, you measure a collection of LBPs over an
image patch and look at the distribution of these LBPs. Lets apply LBP to
a brick texture.
"""
from skimage.transform import rotate
from skimage.feature import local_binary_pattern
from skimage import data
from skimage.color import label2rgb
# settings for LBP
METHOD = 'uniform'
P = 16
R = 2
matplotlib.rcParams['font.size'] = 9
radius = 3
n_points = 8 * radius
def overlay_labels(image, lbp, labels):
mask = np.logical_or.reduce([lbp == each for each in labels])
return label2rgb(mask, image=image, bg_label=0, alpha=0.5)
def highlight_bars(bars, indexes):
for i in indexes:
bars[i].set_facecolor('r')
image = data.load('brick.png')
lbp = local_binary_pattern(image, n_points, radius, METHOD)
def hist(ax, lbp):
n_bins = lbp.max() + 1
return ax.hist(lbp.ravel(), normed=True, bins=n_bins, range=(0, n_bins),
facecolor='0.5')
# plot histograms of LBP of textures
fig, (ax_img, ax_hist) = plt.subplots(nrows=2, ncols=3, figsize=(9, 6))
plt.gray()
titles = ('edge', 'flat', 'corner')
w = width = radius - 1
edge_labels = range(n_points // 2 - w, n_points // 2 + w + 1)
flat_labels = list(range(0, w + 1)) + list(range(n_points - w, n_points + 2))
i_14 = n_points // 4 # 1/4th of the histogram
i_34 = 3 * (n_points // 4) # 3/4th of the histogram
corner_labels = (list(range(i_14 - w, i_14 + w + 1)) +
list(range(i_34 - w, i_34 + w + 1)))
label_sets = (edge_labels, flat_labels, corner_labels)
for ax, labels in zip(ax_img, label_sets):
ax.imshow(overlay_labels(image, lbp, labels))
for ax, labels, name in zip(ax_hist, label_sets, titles):
counts, _, bars = hist(ax, lbp)
highlight_bars(bars, labels)
ax.set_ylim(ymax=np.max(counts[:-1]))
ax.set_xlim(xmax=n_points + 2)
ax.set_title(name)
ax_hist[0].set_ylabel('Percentage')
for ax in ax_img:
ax.axis('off')
"""
.. image:: PLOT2RST.current_figure
The above plot highlights flat, edge-like, and corner-like regions of the
image.
The histogram of the LBP result is a good measure to classify textures. Here,
we test the histogram distributions against each other using the
Kullback-Leibler-Divergence.
"""
# settings for LBP
radius = 2
n_points = 8 * radius
def kullback_leibler_divergence(p, q):
@@ -37,11 +169,12 @@ def kullback_leibler_divergence(p, q):
def match(refs, img):
best_score = 10
best_name = None
lbp = local_binary_pattern(img, P, R, METHOD)
hist, _ = np.histogram(lbp, normed=True, bins=P + 2, range=(0, P + 2))
lbp = local_binary_pattern(img, n_points, radius, METHOD)
n_bins = lbp.max() + 1
hist, _ = np.histogram(lbp, normed=True, bins=n_bins, range=(0, n_bins))
for name, ref in refs.items():
ref_hist, _ = np.histogram(ref, normed=True, bins=P + 2,
range=(0, P + 2))
ref_hist, _ = np.histogram(ref, normed=True, bins=n_bins,
range=(0, n_bins))
score = kullback_leibler_divergence(hist, ref_hist)
if score < best_score:
best_score = score
@@ -54,19 +187,19 @@ grass = data.load('grass.png')
wall = data.load('rough-wall.png')
refs = {
'brick': local_binary_pattern(brick, P, R, METHOD),
'grass': local_binary_pattern(grass, P, R, METHOD),
'wall': local_binary_pattern(wall, P, R, METHOD)
'brick': local_binary_pattern(brick, n_points, radius, METHOD),
'grass': local_binary_pattern(grass, n_points, radius, METHOD),
'wall': local_binary_pattern(wall, n_points, radius, METHOD)
}
# classify rotated textures
print('Rotated images matched against references using LBP:')
print('original: brick, rotated: 30deg, match result: ', end='')
print(match(refs, rotate(brick, angle=30, resize=False)))
print('original: brick, rotated: 70deg, match result: ', end='')
print(match(refs, rotate(brick, angle=70, resize=False)))
print('original: grass, rotated: 145deg, match result: ', end='')
print(match(refs, rotate(grass, angle=145, resize=False)))
print('original: brick, rotated: 30deg, match result: ',
match(refs, rotate(brick, angle=30, resize=False)))
print('original: brick, rotated: 70deg, match result: ',
match(refs, rotate(brick, angle=70, resize=False)))
print('original: grass, rotated: 145deg, match result: ',
match(refs, rotate(grass, angle=145, resize=False)))
# plot histograms of LBP of textures
fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3,
@@ -75,16 +208,20 @@ plt.gray()
ax1.imshow(brick)
ax1.axis('off')
ax4.hist(refs['brick'].ravel(), normed=True, bins=P + 2, range=(0, P + 2))
hist(ax4, refs['brick'])
ax4.set_ylabel('Percentage')
ax2.imshow(grass)
ax2.axis('off')
ax5.hist(refs['grass'].ravel(), normed=True, bins=P + 2, range=(0, P + 2))
hist(ax5, refs['grass'])
ax5.set_xlabel('Uniform LBP values')
ax3.imshow(wall)
ax3.axis('off')
ax6.hist(refs['wall'].ravel(), normed=True, bins=P + 2, range=(0, P + 2))
hist(ax6, refs['wall'])
"""
.. image:: PLOT2RST.current_figure
"""
plt.show()
+55
View File
@@ -0,0 +1,55 @@
"""
==============
Marching Cubes
==============
Marching cubes is an algorithm to extract a 2D surface mesh from a 3D volume.
This can be conceptualized as a 3D generalization of isolines on topographical
or weather maps. It works by iterating across the volume, looking for regions
which cross the level of interest. If such regions are found, triangulations
are generated and added to an output mesh. The final result is a set of
vertices and a set of triangular faces.
The algorithm requires a data volume and an isosurface value. For example, in
CT imaging Hounsfield units of +700 to +3000 represent bone. So, one potential
input would be a reconstructed CT set of data and the value +700, to extract
a mesh for regions of bone or bone-like density.
This implementation also works correctly on anisotropic datasets, where the
voxel spacing is not equal for every spatial dimension, through use of the
`spacing` kwarg.
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from skimage import measure
from skimage.draw import ellipsoid
# Generate a level set about zero of two identical ellipsoids in 3D
ellip_base = ellipsoid(6, 10, 16, levelset=True)
ellip_double = np.concatenate((ellip_base[:-1, ...],
ellip_base[2:, ...]), axis=0)
# Use marching cubes to obtain the surface mesh of these ellipsoids
verts, faces = measure.marching_cubes(ellip_double, 0)
# Display resulting triangular mesh using Matplotlib. This can also be done
# with mayavi (see skimage.measure.marching_cubes docstring).
fig = plt.figure(figsize=(10, 12))
ax = fig.add_subplot(111, projection='3d')
# Fancy indexing: `verts[faces]` to generate a collection of triangles
mesh = Poly3DCollection(verts[faces])
ax.add_collection3d(mesh)
ax.set_xlabel("x-axis: a = 6 per ellipsoid")
ax.set_ylabel("y-axis: b = 10")
ax.set_zlabel("z-axis: c = 16")
ax.set_xlim(0, 24) # a = 6 (times two for 2nd ellipsoid)
ax.set_ylim(0, 20) # b = 10
ax.set_zlim(0, 32) # c = 16
plt.show()
+172 -30
View File
@@ -3,55 +3,197 @@
Radon transform
===============
The radon transform is a technique widely used in tomography to
reconstruct an object from different projections. A projection is, for
example, the scattering data obtained as the output of a tomographic
scan.
In computed tomography, the tomography reconstruction problem is to obtain
a tomographic slice image from a set of projections [1]_. A projection is formed
by drawing a set of parallel rays through the 2D object of interest, assigning
the integral of the object's contrast along each ray to a single pixel in the
projection. A single projection of a 2D object is one dimensional. To
enable computed tomography reconstruction of the object, several projections
must be acquired, each of them corresponding to a different angle between the
rays with respect to the object. A collection of projections at several angles
is called a sinogram, which is a linear transform of the original image.
For more information see:
The inverse Radon transform is used in computed tomography to reconstruct
a 2D image from the measured projections (the sinogram). A practical, exact
implementation of the inverse Radon transform does not exist, but there are
several good approximate algorithms available.
- http://en.wikipedia.org/wiki/Radon_transform
- http://www.clear.rice.edu/elec431/projects96/DSP/bpanalysis.html
As the inverse Radon transform reconstructs the object from a set of
projections, the (forward) Radon transform can be used to simulate a
tomography experiment.
This script performs the radon transform, and reconstructs the
input image based on the resulting sinogram.
This script performs the Radon transform to simulate a tomography experiment
and reconstructs the input image based on the resulting sinogram formed by
the simulation. Two methods for performing the inverse Radon transform
and reconstructing the original image are compared: The Filtered Back
Projection (FBP) and the Simultaneous Algebraic Reconstruction
Technique (SART).
.. seealso::
- AC Kak, M Slaney, "Principles of Computerized Tomographic Imaging",
http://www.slaney.org/pct/pct-toc.html
- http://en.wikipedia.org/wiki/Radon_transform
The forward transform
=====================
As our original image, we will use the Shepp-Logan phantom. When calculating
the Radon transform, we need to decide how many projection angles we wish
to use. As a rule of thumb, the number of projections should be about the
same as the number of pixels there are across the object (to see why this
is so, consider how many unknown pixel values must be determined in the
reconstruction process and compare this to the number of measurements
provided by the projections), and we follow that rule here. Below is the
original image and its Radon transform, often known as its _sinogram_:
"""
from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread
from skimage import data_dir
from skimage.transform import radon, iradon, rescale
from skimage.transform import radon, rescale
image = imread(data_dir + "/phantom.png", as_grey=True)
image = rescale(image, scale=0.4)
plt.figure(figsize=(8, 8.5))
plt.figure(figsize=(8, 4.5))
plt.subplot(221)
plt.subplot(121)
plt.title("Original")
plt.imshow(image, cmap=plt.cm.Greys_r)
plt.subplot(222)
projections = radon(image, theta=[0, 45, 90])
plt.plot(projections)
plt.title("Projections at\n0, 45 and 90 degrees")
plt.xlabel("Projection axis")
plt.ylabel("Intensity")
projections = radon(image)
plt.subplot(223)
theta = np.linspace(0., 180., max(image.shape), endpoint=True)
sinogram = radon(image, theta=theta, circle=True)
plt.subplot(122)
plt.title("Radon transform\n(Sinogram)")
plt.xlabel("Projection angle (degrees)")
plt.ylabel("Projection axis")
plt.imshow(projections, aspect='auto')
reconstruction = iradon(projections)
plt.subplot(224)
plt.title("Reconstruction\nfrom sinogram")
plt.imshow(reconstruction, cmap=plt.cm.Greys_r)
plt.xlabel("Projection angle (deg)")
plt.ylabel("Projection position (pixels)")
plt.imshow(sinogram, cmap=plt.cm.Greys_r,
extent=(0, 180, 0, sinogram.shape[0]), aspect='auto')
plt.subplots_adjust(hspace=0.4, wspace=0.5)
plt.show()
"""
.. image:: PLOT2RST.current_figure
Reconstruction with the Filtered Back Projection (FBP)
======================================================
The mathematical foundation of the filtered back projection is the Fourier
slice theorem [2]_. It uses Fourier transform of the projection and
interpolation in Fourier space to obtain the 2D Fourier transform of the image,
which is then inverted to form the reconstructed image. The filtered back
projection is among the fastest methods of performing the inverse Radon
transform. The only tunable parameter for the FBP is the filter, which is
applied to the Fourier transformed projections. It may be used to suppress
high frequency noise in the reconstruction. ``skimage`` provides a few
different options for the filter.
"""
from skimage.transform import iradon
reconstruction_fbp = iradon(sinogram, theta=theta, circle=True)
error = reconstruction_fbp - image
print('FBP rms reconstruction error: %.3g' % np.sqrt(np.mean(error**2)))
imkwargs = dict(vmin=-0.2, vmax=0.2)
plt.figure(figsize=(8, 4.5))
plt.subplot(121)
plt.title("Reconstruction\nFiltered back projection")
plt.imshow(reconstruction_fbp, cmap=plt.cm.Greys_r)
plt.subplot(122)
plt.title("Reconstruction error\nFiltered back projection")
plt.imshow(reconstruction_fbp - image, cmap=plt.cm.Greys_r, **imkwargs)
plt.show()
"""
.. image:: PLOT2RST.current_figure
Reconstruction with the Simultaneous Algebraic Reconstruction Technique
=======================================================================
Algebraic reconstruction techniques for tomography are based on a
straightforward idea: for a pixelated image the value of a single ray in a
particular projection is simply a sum of all the pixels the ray passes through
on its way through the object. This is a way of expressing the forward Radon
transform. The inverse Radon transform can then be formulated as a (large) set
of linear equations. As each ray passes through a small fraction of the pixels
in the image, this set of equations is sparse, allowing iterative solvers for
sparse linear systems to tackle the system of equations. One iterative method
has been particularly popular, namely Kaczmarz' method [3]_, which has the
property that the solution will approach a least-squares solution of the
equation set.
The combination of the formulation of the reconstruction problem as a set
of linear equations and an iterative solver makes algebraic techniques
relatively flexible, hence some forms of prior knowledge can be incorporated
with relative ease.
``skimage`` provides one of the more popular variations of the algebraic
reconstruction techniques: the Simultaneous Algebraic Reconstruction Technique
(SART) [1]_ [4]_. It uses Kaczmarz' method [3]_ as the iterative solver. A good
reconstruction is normally obtained in a single iteration, making the method
computationally effective. Running one or more extra iterations will normally
improve the reconstruction of sharp, high frequency features and reduce the
mean squared error at the expense of increased high frequency noise (the user
will need to decide on what number of iterations is best suited to the problem
at hand. The implementation in ``skimage`` allows prior information of the
form of a lower and upper threshold on the reconstructed values to be supplied
to the reconstruction.
"""
from skimage.transform import iradon_sart
reconstruction_sart = iradon_sart(sinogram, theta=theta)
error = reconstruction_sart - image
print('SART (1 iteration) rms reconstruction error: %.3g'
% np.sqrt(np.mean(error**2)))
plt.figure(figsize=(8, 8.5))
plt.subplot(221)
plt.title("Reconstruction\nSART")
plt.imshow(reconstruction_sart, cmap=plt.cm.Greys_r)
plt.subplot(222)
plt.title("Reconstruction error\nSART")
plt.imshow(reconstruction_sart - image, cmap=plt.cm.Greys_r, **imkwargs)
# Run a second iteration of SART by supplying the reconstruction
# from the first iteration as an initial estimate
reconstruction_sart2 = iradon_sart(sinogram, theta=theta,
image=reconstruction_sart)
error = reconstruction_sart2 - image
print('SART (2 iterations) rms reconstruction error: %.3g'
% np.sqrt(np.mean(error**2)))
plt.subplot(223)
plt.title("Reconstruction\nSART, 2 iterations")
plt.imshow(reconstruction_sart2, cmap=plt.cm.Greys_r)
plt.subplot(224)
plt.title("Reconstruction error\nSART, 2 iterations")
plt.imshow(reconstruction_sart2 - image, cmap=plt.cm.Greys_r, **imkwargs)
plt.show()
"""
.. image:: PLOT2RST.current_figure
.. [1] AC Kak, M Slaney, "Principles of Computerized Tomographic Imaging",
IEEE Press 1988. http://www.slaney.org/pct/pct-toc.html
.. [2] Wikipedia, Radon transform,
http://en.wikipedia.org/wiki/Radon_transform#Relationship_with_the_Fourier_transform
.. [3] S Kaczmarz, "Angenaeherte Aufloesung von Systemen linearer
Gleichungen", Bulletin International de l'Academie Polonaise des
Sciences et des Lettres 35 pp 355--357 (1937)
.. [4] AH Andersen, AC Kak, "Simultaneous algebraic reconstruction technique
(SART): a superior implementation of the ART algorithm", Ultrasonic
Imaging 6 pp 81--94 (1984)
"""
+22 -15
View File
@@ -6,9 +6,9 @@ Mean filters
This example compares the following mean filters of the rank filter package:
* **local mean**: all pixels belonging to the structuring element to compute
average gray level
average gray level.
* **percentile mean**: only use values between percentiles p0 and p1
(here 10% and 90%)
(here 10% and 90%).
* **bilateral mean**: only use pixels of the structuring element having a gray
level situated inside g-s0 and g+s1 (here g-500 and g+500)
@@ -23,23 +23,30 @@ import matplotlib.pyplot as plt
from skimage import data
from skimage.morphology import disk
import skimage.filter.rank as rank
from skimage.filter import rank
a16 = (data.coins()).astype(np.uint16) * 16
image = (data.coins()).astype(np.uint16) * 16
selem = disk(20)
f1 = rank.percentile_mean(a16, selem=selem, p0=.1, p1=.9)
f2 = rank.bilateral_mean(a16, selem=selem, s0=500, s1=500)
f3 = rank.mean(a16, selem=selem)
percentile_result = rank.mean_percentile(image, selem=selem, p0=.1, p1=.9)
bilateral_result = rank.mean_bilateral(image, selem=selem, s0=500, s1=500)
normal_result = rank.mean(image, selem=selem)
# display results
fig, axes = plt.subplots(nrows=3, figsize=(15, 10))
fig, axes = plt.subplots(nrows=3, figsize=(8, 10))
ax0, ax1, ax2 = axes
ax0.imshow(np.hstack((a16, f1)))
ax0.set_title('percentile mean')
ax1.imshow(np.hstack((a16, f2)))
ax1.set_title('bilateral mean')
ax2.imshow(np.hstack((a16, f3)))
ax2.set_title('local mean')
ax0.imshow(np.hstack((image, percentile_result)))
ax0.set_title('Percentile mean')
ax0.axis('off')
ax1.imshow(np.hstack((image, bilateral_result)))
ax1.set_title('Bilateral mean')
ax1.axis('off')
ax2.imshow(np.hstack((image, normal_result)))
ax2.set_title('Local mean')
ax2.axis('off')
plt.show()
+1 -1
View File
@@ -21,7 +21,7 @@ y = 0.2 * x + 20
data = np.column_stack([x, y])
# add faulty data
faulty = np.array(30 * [(180, -100)])
faulty = np.array(30 * [(180., -100)])
faulty += 5 * np.random.normal(size=faulty.shape)
data[:faulty.shape[0]] = faulty
+9 -15
View File
@@ -24,29 +24,23 @@ image[rr,cc] = 1
image = rotate(image, angle=15, order=0)
label_img = label(image)
props = regionprops(label_img, [
'BoundingBox',
'Centroid',
'Orientation',
'MajorAxisLength',
'MinorAxisLength'
])
regions = regionprops(label_img)
plt.imshow(image)
for prop in props:
x0 = prop['Centroid'][1]
y0 = prop['Centroid'][0]
x1 = x0 + math.cos(prop['Orientation']) * 0.5 * prop['MajorAxisLength']
y1 = y0 - math.sin(prop['Orientation']) * 0.5 * prop['MajorAxisLength']
x2 = x0 - math.sin(prop['Orientation']) * 0.5 * prop['MinorAxisLength']
y2 = y0 - math.cos(prop['Orientation']) * 0.5 * prop['MinorAxisLength']
for props in regions:
y0, x0 = props.centroid
orientation = props.orientation
x1 = x0 + math.cos(orientation) * 0.5 * props.major_axis_length
y1 = y0 - math.sin(orientation) * 0.5 * props.major_axis_length
x2 = x0 - math.sin(orientation) * 0.5 * props.minor_axis_length
y2 = y0 - math.cos(orientation) * 0.5 * props.minor_axis_length
plt.plot((x0, x1), (y0, y1), '-r', linewidth=2.5)
plt.plot((x0, x2), (y0, y2), '-r', linewidth=2.5)
plt.plot(x0, y0, '.g', markersize=15)
minr, minc, maxr, maxc = prop['BoundingBox']
minr, minc, maxr, maxc = props.bbox
bx = (minc, maxc, maxc, minc, minc)
by = (minr, minr, maxr, maxr, minr)
plt.plot(bx, by, '-b', linewidth=2.5)
+58 -20
View File
@@ -1,29 +1,34 @@
"""
===========
Fill shapes
===========
This example shows how to fill several different shapes:
======
Shapes
======
This example shows how to draw several different shapes:
* line
* Bezier curve
* polygon
* circle
* ellipse
"""
import math
import numpy as np
import matplotlib.pyplot as plt
from skimage.draw import line, polygon, circle, circle_perimeter, \
ellipse, ellipse_perimeter
import numpy as np
import math
from skimage.draw import (line, polygon, circle,
circle_perimeter,
ellipse, ellipse_perimeter,
bezier_curve)
img = np.zeros((500, 500, 3), dtype=np.uint8)
fig, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, figsize=(10, 6))
img = np.zeros((500, 500, 3), dtype=np.double)
# draw line
rr, cc = line(120, 123, 20, 400)
img[rr,cc,0] = 255
img[rr, cc, 0] = 255
# fill polygon
poly = np.array((
@@ -33,28 +38,61 @@ poly = np.array((
(220, 590),
(300, 300),
))
rr, cc = polygon(poly[:,0], poly[:,1], img.shape)
img[rr,cc,1] = 255
rr, cc = polygon(poly[:, 0], poly[:, 1], img.shape)
img[rr, cc, 1] = 1
# fill circle
rr, cc = circle(200, 200, 100, img.shape)
img[rr,cc,:] = (255, 255, 0)
img[rr, cc, :] = (1, 1, 0)
# fill ellipse
rr, cc = ellipse(300, 300, 100, 200, img.shape)
img[rr,cc,2] = 255
img[rr, cc, 2] = 1
# circle
rr, cc = circle_perimeter(120, 400, 15)
img[rr, cc, :] = (255, 0, 0)
img[rr, cc, :] = (1, 0, 0)
# Bezier curve
rr, cc = bezier_curve(70, 100, 10, 10, 150, 100, 1)
img[rr, cc, :] = (1, 0, 0)
# ellipses
rr, cc = ellipse_perimeter(120, 400, 60, 20, orientation=math.pi / 4.)
img[rr, cc, :] = (255, 0, 255)
img[rr, cc, :] = (1, 0, 1)
rr, cc = ellipse_perimeter(120, 400, 60, 20, orientation=-math.pi / 4.)
img[rr, cc, :] = (0, 0, 255)
img[rr, cc, :] = (0, 0, 1)
rr, cc = ellipse_perimeter(120, 400, 60, 20, orientation=math.pi / 2.)
img[rr, cc, :] = (255, 255, 255)
img[rr, cc, :] = (1, 1, 1)
ax1.imshow(img)
ax1.set_title('No anti-aliasing')
ax1.axis('off')
"""
Anti-aliased drawing for:
* line
* circle
"""
from skimage.draw import line_aa, circle_perimeter_aa
img = np.zeros((100, 100), dtype=np.double)
# anti-aliased line
rr, cc, val = line_aa(12, 12, 20, 50)
img[rr, cc] = val
# anti-aliased circle
rr, cc, val = circle_perimeter_aa(60, 40, 30)
img[rr, cc] = val
ax2.imshow(img, cmap=plt.cm.gray, interpolation='nearest')
ax2.set_title('Anti-aliasing')
ax2.axis('off')
plt.imshow(img)
plt.show()
+3
View File
@@ -52,8 +52,11 @@ fig, axes = plt.subplots(ncols=3, figsize=(8, 2.7))
ax0, ax1, ax2 = axes
ax0.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax0.set_title('Overlapping objects')
ax1.imshow(-distance, cmap=plt.cm.jet, interpolation='nearest')
ax1.set_title('Distances')
ax2.imshow(labels, cmap=plt.cm.spectral, interpolation='nearest')
ax2.set_title('Separated objects')
for ax in axes:
ax.axis('off')
+4
View File
@@ -85,6 +85,10 @@ if __name__ == '__main__':
for l in setup_lines:
if l.startswith('VERSION'):
tag = l.split("'")[1]
# Rename to, e.g., 0.9.x
tag = '.'.join(tag.split('.')[:-1] + ['x'])
break
if "dev" in tag:
+10 -6
View File
@@ -1,17 +1,21 @@
function insert_version_links() {
var labels = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3'];
var versions = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3'];
for (i = 0; i < labels.length; i++){
function insert_version_links() {
for (i = 0; i < versions.length; i++){
open_list = '<li>'
if (typeof(DOCUMENTATION_OPTIONS) !== 'undefined') {
if ((DOCUMENTATION_OPTIONS['VERSION'] == labels[i]) ||
if ((DOCUMENTATION_OPTIONS['VERSION'] == versions[i]) ||
(DOCUMENTATION_OPTIONS['VERSION'].match(/dev$/) && (i == 0))) {
open_list = '<li id="current">'
}
}
document.write(open_list);
document.write('<a href="URL">skimage VERSION</a> </li>\n'
.replace('VERSION', labels[i])
.replace('URL', 'http://scikit-image.org/docs/' + labels[i]));
.replace('VERSION', versions[i])
.replace('URL', 'http://scikit-image.org/docs/' + versions[i]));
}
}
function stable_version() {
return versions[1];
}
+12 -3
View File
@@ -1,9 +1,18 @@
Version 0.9
-----------
- No longer wrap ``imread`` output in an ``Image`` class
- Change default value of `sigma` parameter in ``skimage.segmentation.slic``
to 0
- ``hough_circle`` now returns a stack of arrays that are the same size as the
input image. Set the ``full_output`` flag to True for the old behavior.
Version 0.4
-----------
- Switch mask and radius arguments for ``median_filter``
Version 0.3
-----------
- Remove ``as_grey``, ``dtype`` keyword from ImageCollection
- Remove ``dtype`` from imread
- Generalise ImageCollection to accept a load_func
Version 0.4
-----------
- Switch mask and radius arguments for median_filter
+23
View File
@@ -59,4 +59,27 @@ in-place::
python setup.py build_ext -i
Building with bento
-------------------
``scikit-image`` can also be built using `bento
<http://cournape.github.io/Bento/>`__. Bento depends on `WAF
<https://code.google.com/p/waf/>`__ for compilation.
Follow the `Bento installation instructions
<http://cournape.github.io/Bento/html/install.html>`__ and `download the WAF
source <http://code.google.com/p/waf/downloads/list>`__.
Tell Bento where to find WAF by setting the ``WAFDIR`` environment variable::
export WAFDIR=<path/to/waf>
From the ``scikit-image`` source directory::
bentomaker configure
bentomaker build -j # (add -i for in-place build)
bentomaker install # (when not builing in-place)
Depending on file permissions, the install commands may need to be run as sudo.
.. include:: ../../DEPENDS.txt
@@ -79,6 +79,10 @@ dt {
padding-left: 15px;
}
#current {
font-weight: bold;
}
.headerlink {
margin-left: 10px;
color: #ddd;
+58 -38
View File
@@ -1,14 +1,15 @@
import urllib
import json
import copy
import urllib
import dateutil.parser
from collections import OrderedDict
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from matplotlib.transforms import blended_transform_factory
import dateutil.parser
from dateutil.relativedelta import relativedelta
from datetime import datetime, timedelta
cache = '_pr_cache.txt'
@@ -22,16 +23,16 @@ cache = '_pr_cache.txt'
releases = OrderedDict([
#('0.1', u'2009-10-07 13:52:19 +0200'),
#('0.2', u'2009-11-12 14:48:45 +0200'),
('0.3', u'2011-10-10 03:28:47 -0700'),
#('0.3', u'2011-10-10 03:28:47 -0700'),
('0.4', u'2011-12-03 14:31:32 -0800'),
('0.5', u'2012-02-26 21:00:51 -0800'),
('0.6', u'2012-06-24 21:37:05 -0700')])
('0.6', u'2012-06-24 21:37:05 -0700'),
('0.7', u'2012-09-29 18:08:49 -0700'),
('0.8', u'2013-03-04 20:46:09 +0100')])
month_duration = 24
for r in releases:
releases[r] = dateutil.parser.parse(releases[r])
def fetch_PRs(user='scikit-image', repo='scikit-image', state='open'):
params = {'state': state,
@@ -46,12 +47,12 @@ def fetch_PRs(user='scikit-image', repo='scikit-image', state='open'):
'repo': repo,
'params': urllib.urlencode(params)}
fetch_status = 'Fetching page %(page)d (state=%(state)s)' % params + \
' from %(user)s/%(repo)s...' % config
fetch_status = ('Fetching page %(page)d (state=%(state)s)' % params +
' from %(user)s/%(repo)s...' % config)
print(fetch_status)
f = urllib.urlopen(
'https://api.github.com/repos/%(user)s/%(repo)s/pulls?%(params)s' \
'https://api.github.com/repos/%(user)s/%(repo)s/pulls?%(params)s'
% config
)
@@ -67,6 +68,31 @@ def fetch_PRs(user='scikit-image', repo='scikit-image', state='open'):
return data
def seconds_from_epoch(dates):
seconds = [(dt - epoch).total_seconds() for dt in dates]
return seconds
def get_month_bins(dates):
now = datetime.now(tz=dates[0].tzinfo)
this_month = datetime(year=now.year, month=now.month, day=1,
tzinfo=dates[0].tzinfo)
bins = [this_month - relativedelta(months=i)
for i in reversed(range(-1, month_duration))]
return seconds_from_epoch(bins)
def date_formatter(value, _):
dt = epoch + timedelta(seconds=value)
return dt.strftime('%Y/%m')
for r in releases:
releases[r] = dateutil.parser.parse(releases[r])
try:
PRs = json.loads(open(cache, 'r').read())
print('Loaded PRs from cache...')
@@ -87,47 +113,41 @@ dates = [dateutil.parser.parse(pr['created_at']) for pr in PRs]
epoch = datetime(2009, 1, 1, tzinfo=dates[0].tzinfo)
def seconds_from_epoch(dates):
seconds = [(dt - epoch).total_seconds() for dt in dates]
return seconds
dates_f = seconds_from_epoch(dates)
bins = get_month_bins(dates)
def date_formatter(value, _):
dt = epoch + timedelta(seconds=value)
return dt.strftime('%Y/%m')
fig, ax = plt.subplots(figsize=(7, 5))
plt.figure(figsize=(7, 5))
n, bins, _ = ax.hist(dates_f, bins=bins, color='blue', alpha=0.6)
now = datetime.now(tz=dates[0].tzinfo)
this_month = datetime(year=now.year, month=now.month, day=1,
tzinfo=dates[0].tzinfo)
bins = [this_month - relativedelta(months=i) \
for i in reversed(range(-1, month_duration))]
bins = seconds_from_epoch(bins)
plt.hist(dates_f, bins=bins)
ax = plt.gca()
ax.xaxis.set_major_formatter(FuncFormatter(date_formatter))
ax.set_xticks(bins[:-1])
ax.set_xticks(bins[2:-1:3]) # Date label every 3 months.
labels = ax.get_xticklabels()
for l in labels:
l.set_rotation(40)
l.set_size(10)
mixed_transform = blended_transform_factory(ax.transData, ax.transAxes)
for version, date in releases.items():
date = seconds_from_epoch([date])[0]
plt.axvline(date, color='r', label=version)
ax.axvline(date, color='black', linestyle=':', label=version)
ax.text(date, 1, version, color='r', va='bottom', ha='center',
transform=mixed_transform)
plt.title('Pull request activity').set_y(1.05)
plt.xlabel('Date')
plt.ylabel('PRs created')
plt.legend(loc=2, title='Release')
plt.subplots_adjust(top=0.875, bottom=0.225)
ax.set_title('Pull request activity').set_y(1.05)
ax.set_xlabel('Date')
ax.set_ylabel('PRs per month', color='blue')
fig.subplots_adjust(top=0.875, bottom=0.225)
plt.savefig('PRs.png')
cumulative = np.cumsum(n)
cumulative += len(dates) - cumulative[-1]
ax2 = ax.twinx()
ax2.plot(bins[1:], cumulative, color='black', linewidth=2)
ax2.set_ylabel('Total PRs', color='black')
fig.savefig('PRs.png')
plt.show()
+3
View File
@@ -0,0 +1,3 @@
cython>=0.17
matplotlib>=1.0
numpy>=1.6
+2 -5
View File
@@ -21,7 +21,7 @@ VERSION = '0.9dev'
PYTHON_VERSION = (2, 5)
DEPENDENCIES = {
'numpy': (1, 6),
'Cython': (0, 15),
'Cython': (0, 17),
}
@@ -30,10 +30,7 @@ import sys
import re
import setuptools
from numpy.distutils.core import setup
try:
from distutils.command.build_py import build_py_2to3 as build_py
except ImportError:
from distutils.command.build_py import build_py
from distutils.command.build_py import build_py
def configuration(parent_package='', top_path=None):
+53 -30
View File
@@ -38,8 +38,6 @@ util
Utility Functions
-----------------
get_log
Returns the ``skimage`` log. Use this to print debug output.
img_as_float
Convert an image to floating point format, with values in [0, 1].
img_as_uint
@@ -54,6 +52,7 @@ img_as_ubyte
import os.path as _osp
import imp as _imp
import functools as _functools
from skimage._shared.utils import deprecated as _deprecated
pkg_dir = _osp.abspath(_osp.dirname(__file__))
data_dir = _osp.join(pkg_dir, 'data')
@@ -62,6 +61,7 @@ try:
from .version import version as __version__
except ImportError:
__version__ = "unbuilt-dev"
del version
try:
@@ -88,6 +88,56 @@ test_verbose = _functools.partial(test, verbose=True)
test_verbose.__doc__ = test.__doc__
class _Log(Warning):
pass
class _FakeLog(object):
def __init__(self, name):
"""
Parameters
----------
name : str
Name of the log.
repeat : bool
Whether to print repeating messages more than once (False by
default).
"""
self._name = name
import warnings
warnings.simplefilter("always", _Log)
self._warnings = warnings
def _warn(self, msg, wtype):
self._warnings.warn('%s: %s' % (wtype, msg), _Log)
def debug(self, msg):
self._warn(msg, 'DEBUG')
def info(self, msg):
self._warn(msg, 'INFO')
def warning(self, msg):
self._warn(msg, 'WARNING')
warn = warning
def error(self, msg):
self._warn(msg, 'ERROR')
def critical(self, msg):
self._warn(msg, 'CRITICAL')
def addHandler(*args):
pass
def setLevel(*args):
pass
@_deprecated()
def get_log(name=None):
"""Return a console logger.
@@ -105,39 +155,12 @@ def get_log(name=None):
http://docs.python.org/library/logging.html
"""
import logging
if name is None:
name = 'skimage'
else:
name = 'skimage.' + name
log = logging.getLogger(name)
return log
return _FakeLog(name)
def _setup_log():
"""Configure root logger.
"""
import logging
import sys
formatter = logging.Formatter(
'%(name)s: %(levelname)s: %(message)s'
)
try:
handler = logging.StreamHandler(stream=sys.stdout)
except TypeError:
handler = logging.StreamHandler(strm=sys.stdout)
handler.setFormatter(formatter)
log = get_log()
log.addHandler(handler)
log.setLevel(logging.WARNING)
log.propagate = False
_setup_log()
from .util.dtype import *
+423
View File
@@ -0,0 +1,423 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
def iterbytes(buf):
return (ord(byte) for byte in buf)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
del builtins
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
+2 -2
View File
@@ -1,5 +1,5 @@
cimport numpy as cnp
cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat,
Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1)
cdef float integrate(float[:, ::1] sat, Py_ssize_t r0, Py_ssize_t c0,
Py_ssize_t r1, Py_ssize_t c1)
+2 -2
View File
@@ -5,8 +5,8 @@
cimport numpy as cnp
cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat,
Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1):
cdef float integrate(float[:, ::1] sat, Py_ssize_t r0, Py_ssize_t c0,
Py_ssize_t r1, Py_ssize_t c1):
"""
Using a summed area table / integral image, calculate the sum
over a given window.
+23 -14
View File
@@ -1,19 +1,19 @@
import warnings
import functools
import sys
from . import six
__all__ = ['deprecated', 'is_str']
__all__ = ['deprecated', 'get_bound_method_class']
try:
isinstance("", basestring)
def is_str(s):
"""Return True if `s` is a string. Safe for Python 2 and 3."""
return isinstance(s, basestring)
except NameError:
def is_str(s):
"""Return True if `s` is a string. Safe for Python 2 and 3."""
return isinstance(s, str)
class skimage_deprecation(Warning):
"""Create our own deprecation class, since Python >= 2.7
silences deprecations by default.
"""
pass
class deprecated(object):
@@ -46,12 +46,14 @@ class deprecated(object):
@functools.wraps(func)
def wrapped(*args, **kwargs):
if self.behavior == 'warn':
func_code = six.get_function_code(func)
warnings.simplefilter('always', skimage_deprecation)
warnings.warn_explicit(msg,
category=DeprecationWarning,
filename=func.func_code.co_filename,
lineno=func.func_code.co_firstlineno + 1)
category=skimage_deprecation,
filename=func_code.co_filename,
lineno=func_code.co_firstlineno + 1)
elif self.behavior == 'raise':
raise DeprecationWarning(msg)
raise skimage_deprecation(msg)
return func(*args, **kwargs)
# modify doc string to display deprecation warning
@@ -62,3 +64,10 @@ class deprecated(object):
wrapped.__doc__ = doc + '\n\n ' + wrapped.__doc__
return wrapped
def get_bound_method_class(m):
"""Return the class for a bound method.
"""
return m.im_class if sys.version < '3' else m.__self__.__class__
+18 -1
View File
@@ -1,4 +1,5 @@
from .colorconv import (convert_colorspace,
guess_spatial_dimensions,
rgb2hsv,
hsv2rgb,
rgb2xyz,
@@ -14,6 +15,8 @@ from .colorconv import (convert_colorspace,
rgb2lab,
rgb2hed,
hed2rgb,
lab2lch,
lch2lab,
separate_stains,
combine_stains,
rgb_from_hed,
@@ -43,8 +46,15 @@ from .colorconv import (convert_colorspace,
from .colorlabel import color_dict, label2rgb
from .delta_e import (deltaE_cie76,
deltaE_ciede94,
deltaE_ciede2000,
deltaE_cmc,
)
__all__ = ['convert_colorspace',
'guess_spatial_dimensions',
'rgb2hsv',
'hsv2rgb',
'rgb2xyz',
@@ -60,6 +70,8 @@ __all__ = ['convert_colorspace',
'rgb2lab',
'rgb2hed',
'hed2rgb',
'lab2lch',
'lch2lab',
'separate_stains',
'combine_stains',
'rgb_from_hed',
@@ -87,4 +99,9 @@ __all__ = ['convert_colorspace',
'is_rgb',
'is_gray',
'color_dict',
'label2rgb']
'label2rgb',
'deltaE_cie76',
'deltaE_ciede94',
'deltaE_ciede2000',
'deltaE_cmc',
]
+169 -21
View File
@@ -26,10 +26,17 @@ Supported color spaces
Derived from the RGB CIE color space. Chosen such that
``x == y == z == 1/3`` at the whitepoint, and all color matching
functions are greater than zero everywhere.
* LAB CIE : Lightness, a, b
Colorspace derived from XYZ CIE that is intended to be more
perceptually uniform
* LCH CIE : Lightness, Chroma, Hue
Defined in terms of LAB CIE. C and H are the polar representation of
a and b. The polar angle C is defined to be on (0, 2*pi)
:author: Nicolas Pinto (rgb2hsv)
:author: Ralf Gommers (hsv2rgb)
:author: Travis Oliphant (XYZ and RGB CIE functions)
:author: Matt Terry (lab2lch)
:license: modified BSD
@@ -49,6 +56,37 @@ from ..util import dtype
from skimage._shared.utils import deprecated
def guess_spatial_dimensions(image):
"""Make an educated guess about whether an image has a channels dimension.
Parameters
----------
image : ndarray
The input image.
Returns
-------
spatial_dims : int or None
The number of spatial dimensions of `image`. If ambiguous, the value
is `None`.
Raises
------
ValueError
If the image array has less than two or more than four dimensions.
"""
if image.ndim == 2:
return 2
if image.ndim == 3 and image.shape[-1] != 3:
return 3
if image.ndim == 3 and image.shape[-1] == 3:
return None
if image.ndim == 4 and image.shape[-1] == 3:
return 3
else:
raise ValueError("Expected 2D, 3D, or 4D array, got %iD." % image.ndim)
@deprecated()
def is_rgb(image):
"""Test whether the image is RGB or RGBA.
@@ -72,7 +110,7 @@ def is_gray(image):
Input image.
"""
return np.squeeze(image).ndim == 2
return image.ndim in (2, 3) and not is_rgb(image)
def convert_colorspace(arr, fromspace, tospace):
@@ -129,8 +167,9 @@ def _prepare_colorarray(arr):
"""
arr = np.asanyarray(arr)
if arr.ndim != 3 or arr.shape[2] != 3:
msg = "the input array must be have a shape == (.,.,3))"
if arr.ndim not in [3, 4] or arr.shape[-1] != 3:
msg = ("the input array must be have a shape == (.., ..,[ ..,] 3)), " +
"got (" + (", ".join(map(str, arr.shape))) + ")")
raise ValueError(msg)
return dtype.img_as_float(arr)
@@ -413,12 +452,12 @@ def _convert(matrix, arr):
The converted array.
"""
arr = _prepare_colorarray(arr)
arr = np.swapaxes(arr, 0, 2)
arr = np.swapaxes(arr, 0, -1)
oldshape = arr.shape
arr = np.reshape(arr, (3, -1))
out = np.dot(matrix, arr)
out.shape = oldshape
out = np.swapaxes(out, 2, 0)
out = np.swapaxes(out, -1, 0)
return np.ascontiguousarray(out)
@@ -473,17 +512,19 @@ def rgb2xyz(rgb):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Returns
-------
out : ndarray
The image in XYZ format, in a 3-D array of shape (.., .., 3).
The image in XYZ format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3- or 4-D array of shape (.., ..,[ ..,] 3).
Notes
-----
@@ -628,23 +669,24 @@ def gray2rgb(image):
Parameters
----------
image : array_like
Input image of shape ``(M, N)``.
Input image of shape ``(M, N [, P])``.
Returns
-------
rgb : ndarray
RGB image of shape ``(M, N, 3)``.
RGB image of shape ``(M, N, [, P], 3)``.
Raises
------
ValueError
If the input is not 2-dimensional.
If the input is not a 2- or 3-dimensional image.
"""
if np.squeeze(image).ndim == 3 and image.shape[2] in (3, 4):
return image
elif image.ndim == 2 or np.squeeze(image).ndim == 2:
return np.dstack((image, image, image))
elif image.ndim != 1 and np.squeeze(image).ndim in (1, 2, 3):
image = image[..., np.newaxis]
return np.concatenate(3 * (image,), axis=-1)
else:
raise ValueError("Input image expected to be RGB, RGBA or gray.")
@@ -655,17 +697,19 @@ def xyz2lab(xyz):
Parameters
----------
xyz : array_like
The image in XYZ format, in a 3-D array of shape (.., .., 3).
The image in XYZ format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Returns
-------
out : ndarray
The image in CIE-LAB format, in a 3-D array of shape (.., .., 3).
The image in CIE-LAB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Raises
------
ValueError
If `xyz` is not a 3-D array of shape (.., .., 3).
If `xyz` is not a 3-D array of shape (.., ..,[ ..,] 3).
Notes
-----
@@ -695,14 +739,14 @@ def xyz2lab(xyz):
arr[mask] = np.power(arr[mask], 1. / 3.)
arr[~mask] = 7.787 * arr[~mask] + 16. / 116.
x, y, z = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2]
x, y, z = arr[..., 0], arr[..., 1], arr[..., 2]
# Vector scaling
L = (116. * y) - 16.
a = 500.0 * (x - y)
b = 200.0 * (y - z)
return np.dstack([L, a, b])
return np.concatenate([x[..., np.newaxis] for x in [L, a, b]], axis=-1)
def lab2xyz(lab):
@@ -759,17 +803,19 @@ def rgb2lab(rgb):
Parameters
----------
rgb : array_like
The image in RGB format, in a 3-D array of shape (.., .., 3).
The image in RGB format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Returns
-------
out : ndarray
The image in Lab format, in a 3-D array of shape (.., .., 3).
The image in Lab format, in a 3- or 4-D array of shape
(.., ..,[ ..,] 3).
Raises
------
ValueError
If `rgb` is not a 3-D array of shape (.., .., 3).
If `rgb` is not a 3- or 4-D array of shape (.., ..,[ ..,] 3).
Notes
-----
@@ -987,3 +1033,105 @@ def combine_stains(stains, conv_matrix):
logrgb2 = np.dot(-np.reshape(stains, (-1, 3)), conv_matrix)
rgb2 = np.exp(logrgb2)
return rescale_intensity(np.reshape(rgb2 - 2, stains.shape), in_range=(-1, 1))
def lab2lch(lab):
"""CIE-LAB to CIE-LCH color space conversion.
LCH is the cylindrical representation of the LAB (Cartesian) colorspace
Parameters
----------
lab : array_like
The N-D image in CIE-LAB format. The last (`N+1`th) dimension must have
at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color
channels. Subsequent elements are copied.
Returns
-------
out : ndarray
The image in LCH format, in a N-D array with same shape as input `lab`.
Raises
------
ValueError
If `lch` does not have at least 3 color channels (i.e. l, a, b).
Notes
-----
The Hue is expressed as an angle between (0, 2*pi)
Examples
--------
>>> from skimage import data
>>> from skimage.color import rgb2lab, lab2lch
>>> lena = data.lena()
>>> lena_lab = rgb2lab(lena)
>>> lena_lch = lab2lch(lena_lab)
"""
lch = _prepare_lab_array(lab)
a, b = lch[..., 1], lch[..., 2]
lch[..., 1], lch[..., 2] = _cart2polar_2pi(a, b)
return lch
def _cart2polar_2pi(x, y):
"""convert cartesian coordiantes to polar (uses non-standard theta range!)
NON-STANDARD RANGE! Maps to (0, 2*pi) rather than usual (-pi, +pi)
"""
r, t = np.hypot(x, y), np.arctan2(y, x)
t += np.where(t < 0., 2 * np.pi, 0)
return r, t
def lch2lab(lch):
"""CIE-LCH to CIE-LAB color space conversion.
LCH is the cylindrical representation of the LAB (Cartesian) colorspace
Parameters
----------
lch : array_like
The N-D image in CIE-LCH format. The last (`N+1`th) dimension must have
at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color
channels. Subsequent elements are copied.
Returns
-------
out : ndarray
The image in LAB format, with same shape as input `lch`.
Raises
------
ValueError
If `lch` does not have at least 3 color channels (i.e. l, c, h).
Examples
--------
>>> from skimage import data
>>> from skimage.color import rgb2lab, lch2lab
>>> lena = data.lena()
>>> lena_lab = rgb2lab(lena)
>>> lena_lch = lab2lch(lena_lab)
>>> lena_lab2 = lch2lab(lena_lch)
"""
lch = _prepare_lab_array(lch)
c, h = lch[..., 1], lch[..., 2]
lch[..., 1], lch[..., 2] = c * np.cos(h), c * np.sin(h)
return lch
def _prepare_lab_array(arr):
"""Ensure input for lab2lch, lch2lab are well-posed.
Arrays must be in floating point and have at least 3 elements in
last dimension. Return a new array.
"""
arr = np.asarray(arr)
shape = arr.shape
if shape[-1] < 3:
raise ValueError('Input array has less than 3 color channels')
return dtype.img_as_float(arr, force_copy=True)
+57 -18
View File
@@ -4,7 +4,8 @@ import itertools
import numpy as np
from skimage import img_as_float
from skimage._shared.utils import is_str
from skimage._shared import six
from skimage._shared.six.moves import zip
from .colorconv import rgb2gray, gray2rgb
from . import rgb_colors
@@ -30,10 +31,34 @@ def _rgb_vector(color):
color : str or array
Color name in `color_dict` or RGB float values between [0, 1].
"""
if is_str(color):
if isinstance(color, six.string_types):
color = color_dict[color]
# slice to handle RGBA colors
return np.array(color[:3]).reshape(1, 3)
# Slice to handle RGBA colors.
return np.array(color[:3])
def _match_label_with_color(label, colors, bg_label, bg_color):
"""Return `unique_labels` and `color_cycle` for label array and color list.
Colors are cycled for normal labels, but the background color should only
be used for the background.
"""
# Temporarily set background color; it will be removed later.
if bg_color is None:
bg_color = (0, 0, 0)
bg_color = _rgb_vector([bg_color])
unique_labels = list(set(label.flat))
# Ensure that the background label is in front to match call to `chain`.
if bg_label in unique_labels:
unique_labels.remove(bg_label)
unique_labels.insert(0, bg_label)
# Modify labels and color cycle so background color is used only once.
color_cycle = itertools.cycle(colors)
color_cycle = itertools.chain(bg_color, color_cycle)
return unique_labels, color_cycle
def label2rgb(label, image=None, colors=None, alpha=0.3,
@@ -65,7 +90,7 @@ def label2rgb(label, image=None, colors=None, alpha=0.3,
colors = [_rgb_vector(c) for c in colors]
if image is None:
colorized = np.zeros(label.shape + (3,), dtype=np.float64)
image = np.zeros(label.shape + (3,), dtype=np.float64)
# Opacity doesn't make sense if no image exists.
alpha = 1
else:
@@ -76,20 +101,34 @@ def label2rgb(label, image=None, colors=None, alpha=0.3,
warnings.warn("Negative intensities in `image` are not supported")
image = img_as_float(rgb2gray(image))
colorized = gray2rgb(image) * image_alpha + (1 - image_alpha)
image = gray2rgb(image) * image_alpha + (1 - image_alpha)
labels = list(set(label.flat))
color_cycle = itertools.cycle(colors)
# Ensure that all labels are non-negative so we can index into
# `label_to_color` correctly.
offset = min(label.min(), bg_label)
if offset != 0:
label = label - offset # Make sure you don't modify the input array.
bg_label -= offset
if bg_label in labels:
labels.remove(bg_label)
if bg_color is not None:
labels.insert(0, bg_label)
bg_color = _rgb_vector(bg_color)
color_cycle = itertools.chain(bg_color, color_cycle)
new_type = np.min_scalar_type(label.max())
if new_type == np.bool:
new_type = np.uint8
label = label.astype(new_type)
for c, i in itertools.izip(color_cycle, labels):
mask = (label == i)
colorized[mask] = c * alpha + colorized[mask] * (1 - alpha)
unique_labels, color_cycle = _match_label_with_color(label, colors,
bg_label, bg_color)
return colorized
if len(unique_labels) == 0:
return image
dense_labels = range(max(unique_labels) + 1)
label_to_color = np.array([c for i, c in zip(dense_labels, color_cycle)])
result = label_to_color[label] * alpha + image * (1 - alpha)
# Remove background label if its color was not specified.
remove_background = bg_label in unique_labels and bg_color is None
if remove_background:
result[label == bg_label] = image[label == bg_label]
return result
+339
View File
@@ -0,0 +1,339 @@
"""
Functions for calculating the "distance" between colors.
Implicit in these definitions of "distance" is the notion of "Just Noticeable
Distance" (JND). This represents the distance between colors where a human can
perceive different colors. Humans are more sensitive to certain colors than
others, which different deltaE metrics correct for with varying degrees of
sophistication.
The literature often mentions 1 as the minimum distance for visual
differentiation, but more recent studies (Mahy 1994) peg JND at 2.3
The delta-E notation comes from the German word for "Sensation" (Empfindung).
Reference
---------
http://en.wikipedia.org/wiki/Color_difference
"""
from __future__ import division
import numpy as np
from skimage.color.colorconv import lab2lch, _cart2polar_2pi
def deltaE_cie76(lab1, lab2):
"""Euclidean distance between two points in Lab color space
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
Returns
-------
dE : array_like
distance between colors `lab1` and `lab2`
References
----------
.. [1] http://en.wikipedia.org/wiki/Color_difference
.. [2] A. R. Robertson, "The CIE 1976 color-difference formulae,"
Color Res. Appl. 2, 7-11 (1977).
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
L1, a1, b1 = np.rollaxis(lab1, -1)[:3]
L2, a2, b2 = np.rollaxis(lab2, -1)[:3]
return np.sqrt((L2 - L1) ** 2 + (a2 - a1) ** 2 + (b2 - b1) ** 2)
def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015):
"""Color difference according to CIEDE 94 standard
Accommodates perceptual non-uniformities through the use of application
specific scale factors (`kH`, `kC`, `kL`, `k1`, and `k2`).
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
kH : float, optional
Hue scale
kC : float, optional
Chroma scale
kL : float, optional
Lightness scale
k1 : float, optional
first scale parameter
k2 : float, optional
second scale parameter
Returns
-------
dE : array_like
color difference between `lab1` and `lab2`
Notes
-----
deltaE_ciede94 is not symmetric with respect to lab1 and lab2. CIEDE94
defines the scales for the lightness, hue, and chroma in terms of the first
color. Consequently, the first color should be regarded as the "reference"
color.
`kL`, `k1`, `k2` depend on the application and default to the values
suggested for graphic arts
========== ============== ==========
Parameter Graphic Arts Textiles
========== ============== ==========
`kL` 1.000 2.000
`k1` 0.045 0.048
`k2` 0.015 0.014
========== ============== ==========
References
----------
.. [1] http://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
"""
L1, C1 = np.rollaxis(lab2lch(lab1), -1)[:2]
L2, C2 = np.rollaxis(lab2lch(lab2), -1)[:2]
dL = L1 - L2
dC = C1 - C2
dH2 = get_dH2(lab1, lab2)
SL = 1
SC = 1 + k1 * C1
SH = 1 + k2 * C1
dE2 = (dL / (kL * SL)) ** 2
dE2 += (dC / (kC * SC)) ** 2
dE2 += dH2 / (kH * SH) ** 2
return np.sqrt(dE2)
def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1):
"""Color difference as given by the CIEDE 2000 standard.
CIEDE 2000 is a major revision of CIDE94. The perceptual calibration is
largely based on experience with automotive paint on smooth surfaces.
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
kL : float (range), optional
lightness scale factor, 1 for "acceptably close"; 2 for "imperceptible"
see deltaE_cmc
kC : float (range), optional
chroma scale factor, usually 1
kH : float (range), optional
hue scale factor, usually 1
Returns
-------
deltaE : array_like
The distance between `lab1` and `lab2`
Notes
-----
CIEDE 2000 assumes parametric weighting factors for the lightness, chroma,
and hue (`kL`, `kC`, `kH` respectively). These default to 1.
References
----------
.. [1] http://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
(doi:10.1364/AO.33.008069)
.. [3] M. Melgosa, J. Quesada, and E. Hita, "Uniformity of some recent
color metrics tested with an accurate color-difference tolerance
dataset," Appl. Opt. 33, 8069-8077 (1994).
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
unroll = False
if lab1.ndim == 1 and lab2.ndim == 1:
unroll = True
if lab1.ndim == 1:
lab1 = lab1[None, :]
if lab2.ndim == 1:
lab2 = lab2[None, :]
L1, a1, b1 = np.rollaxis(lab1, -1)[:3]
L2, a2, b2 = np.rollaxis(lab2, -1)[:3]
# distort `a` based on average chroma
# then convert to lch coordines from distorted `a`
# all subsequence calculations are in the new coordiantes
# (often denoted "prime" in the literature)
Cbar = 0.5 * (np.hypot(a1, b1) + np.hypot(a2, b2))
c7 = Cbar ** 7
G = 0.5 * (1 - np.sqrt(c7 / (c7 + 25 ** 7)))
scale = 1 + G
C1, h1 = _cart2polar_2pi(a1 * scale, b1)
C2, h2 = _cart2polar_2pi(a2 * scale, b2)
# recall that c, h are polar coordiantes. c==r, h==theta
# cide2000 has four terms to delta_e:
# 1) Luminance term
# 2) Hue term
# 3) Chroma term
# 4) hue Rotation term
# lightness term
Lbar = 0.5 * (L1 + L2)
tmp = (Lbar - 50) ** 2
SL = 1 + 0.015 * tmp / np.sqrt(20 + tmp)
L_term = (L2 - L1) / (kL * SL)
# chroma term
Cbar = 0.5 * (C1 + C2) # new coordiantes
SC = 1 + 0.045 * Cbar
C_term = (C2 - C1) / (kC * SC)
# hue term
h_diff = h2 - h1
h_sum = h1 + h2
CC = C1 * C2
dH = h_diff.copy()
dH[h_diff > np.pi] -= 2 * np.pi
dH[h_diff < -np.pi] += 2 * np.pi
dH[CC == 0.] = 0. # if r == 0, dtheta == 0
dH_term = 2 * np.sqrt(CC) * np.sin(dH / 2)
Hbar = h_sum.copy()
mask = np.logical_and(CC != 0., np.abs(h_diff) > np.pi)
Hbar[mask * (h_sum < 2 * np.pi)] += 2 * np.pi
Hbar[mask * (h_sum >= 2 * np.pi)] -= 2 * np.pi
Hbar[CC == 0.] *= 2
Hbar *= 0.5
T = (1 -
0.17 * np.cos(Hbar - np.deg2rad(30)) +
0.24 * np.cos(2 * Hbar) +
0.32 * np.cos(3 * Hbar + np.deg2rad(6)) -
0.20 * np.cos(4 * Hbar - np.deg2rad(63))
)
SH = 1 + 0.015 * Cbar * T
H_term = dH_term / (kH * SH)
# hue rotation
c7 = Cbar ** 7
Rc = 2 * np.sqrt(c7 / (c7 + 25 ** 7))
dtheta = np.deg2rad(30) * np.exp(-((np.rad2deg(Hbar) - 275) / 25) ** 2)
R_term = -np.sin(2 * dtheta) * Rc * C_term * H_term
# put it all together
dE2 = L_term ** 2
dE2 += C_term ** 2
dE2 += H_term ** 2
dE2 += R_term
ans = np.sqrt(dE2)
if unroll:
ans = ans[0]
return ans
def deltaE_cmc(lab1, lab2, kL=1, kC=1):
"""Color difference from the CMC l:c standard.
This color difference was developed by the Colour Measurement Committee
(CMC) of the Society of Dyers and Colourists (United Kingdom). It is
intended for use in the textile industry.
The scale factors `kL`, `kC` set the weight given to differences in
lightness and chroma relative to differences in hue. The usual values are
``kL=2``, ``kC=1`` for "acceptability" and ``kL=1``, ``kC=1`` for
"imperceptibility". Colors with ``dE > 1`` are "different" for the given
scale factors.
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
Returns
-------
dE : array_like
distance between colors `lab1` and `lab2`
Notes
-----
deltaE_cmc the defines the scales for the lightness, hue, and chroma
in terms of the first color. Consequently
``deltaE_cmc(lab1, lab2) != deltaE_cmc(lab2, lab1)``
References
----------
.. [1] http://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
.. [3] F. J. J. Clarke, R. McDonald, and B. Rigg, "Modification to the
JPC79 colour-difference formula," J. Soc. Dyers Colour. 100, 128-132
(1984).
"""
L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3]
L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3]
dC = C1 - C2
dL = L1 - L2
dH2 = get_dH2(lab1, lab2)
T = np.where(np.logical_and(np.rad2deg(h1) >= 164, np.rad2deg(h1) <= 345),
0.56 + 0.2 * np.abs(np.cos(h1 + np.deg2rad(168))),
0.36 + 0.4 * np.abs(np.cos(h1 + np.deg2rad(35)))
)
c1_4 = C1 ** 4
F = np.sqrt(c1_4 / (c1_4 + 1900))
SL = np.where(L1 < 16, 0.511, 0.040975 * L1 / (1. + 0.01765 * L1))
SC = 0.638 + 0.0638 * C1 / (1. + 0.0131 * C1)
SH = SC * (F * T + 1 - F)
dE2 = (dL / (kL * SL)) ** 2
dE2 += (dC / (kC * SC)) ** 2
dE2 += dH2 / (SH ** 2)
return np.sqrt(dE2)
def get_dH2(lab1, lab2):
"""squared hue difference term occurring in deltaE_cmc and deltaE_ciede94
Despite its name, "dH" is not a simple difference of hue values. We avoid
working directly with the hue value, since differencing angles is
troublesome. The hue term is usually written as:
c1 = sqrt(a1**2 + b1**2)
c2 = sqrt(a2**2 + b2**2)
term = (a1-a2)**2 + (b1-b2)**2 - (c1-c2)**2
dH = sqrt(term)
However, this has poor roundoff properties when a or b is dominant.
Instead, ab is a vector with elements a and b. The same dH term can be
re-written as:
|ab1-ab2|**2 - (|ab1| - |ab2|)**2
and then simplified to:
2*|ab1|*|ab2| - 2*dot(ab1, ab2)
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
a1, b1 = np.rollaxis(lab1, -1)[1:3]
a2, b2 = np.rollaxis(lab2, -1)[1:3]
# magnitude of (a, b) is the chroma
C1 = np.hypot(a1, b1)
C2 = np.hypot(a2, b2)
term = (C1 * C2) - (a1 * a2 + b1 * b2)
return 2*term
@@ -0,0 +1,38 @@
# input, intermediate, and output values for CIEDE2000 dE function
# data taken from "The CIEDE2000 Color-Difference Formula: Implementation Notes, ..." http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
# tab delimited data
# pair 1 L1 a1 b1 ap1 cp1 hp1 hbar1 G T SL SC SH RT dE 2 L2 a2 b2 ap2 cp2 hp2
1 1 50.0000 2.6772 -79.7751 2.6774 79.8200 271.9222 270.9611 0.0001 0.6907 1.0000 4.6578 1.8421 -1.7042 2.0425 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
2 1 50.0000 3.1571 -77.2803 3.1573 77.3448 272.3395 271.1698 0.0001 0.6843 1.0000 4.6021 1.8216 -1.7070 2.8615 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
3 1 50.0000 2.8361 -74.0200 2.8363 74.0743 272.1944 271.0972 0.0001 0.6865 1.0000 4.5285 1.8074 -1.7060 3.4412 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
4 1 50.0000 -1.3802 -84.2814 -1.3803 84.2927 269.0618 269.5309 0.0001 0.7357 1.0000 4.7584 1.9217 -1.6809 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
5 1 50.0000 -1.1848 -84.8006 -1.1849 84.8089 269.1995 269.5997 0.0001 0.7335 1.0000 4.7700 1.9218 -1.6822 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
6 1 50.0000 -0.9009 -85.5211 -0.9009 85.5258 269.3964 269.6982 0.0001 0.7303 1.0000 4.7862 1.9217 -1.6840 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000
7 1 50.0000 0.0000 0.0000 0.0000 0.0000 0.0000 126.8697 0.5000 1.2200 1.0000 1.0562 1.0229 0.0000 2.3669 2 50.0000 -1.0000 2.0000 -1.5000 2.5000 126.8697
8 1 50.0000 -1.0000 2.0000 -1.5000 2.5000 126.8697 126.8697 0.5000 1.2200 1.0000 1.0562 1.0229 0.0000 2.3669 2 50.0000 0.0000 0.0000 0.0000 0.0000 0.0000
9 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 269.9854 0.4998 0.7212 1.0000 1.1681 1.0404 -0.0022 7.1792 2 50.0000 -2.4900 0.0009 -3.7346 3.7346 179.9862
10 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 269.9847 0.4998 0.7212 1.0000 1.1681 1.0404 -0.0022 7.1792 2 50.0000 -2.4900 0.0010 -3.7346 3.7346 179.9847
11 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 89.9839 0.4998 0.6175 1.0000 1.1681 1.0346 0.0000 7.2195 2 50.0000 -2.4900 0.0011 -3.7346 3.7346 179.9831
12 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 89.9831 0.4998 0.6175 1.0000 1.1681 1.0346 0.0000 7.2195 2 50.0000 -2.4900 0.0012 -3.7346 3.7346 179.9816
13 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 180.0328 0.4998 0.9779 1.0000 1.1121 1.0365 0.0000 4.8045 2 50.0000 0.0009 -2.4900 0.0013 2.4900 270.0311
14 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 180.0345 0.4998 0.9779 1.0000 1.1121 1.0365 0.0000 4.8045 2 50.0000 0.0010 -2.4900 0.0015 2.4900 270.0345
15 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 0.0362 0.4998 1.3197 1.0000 1.1121 1.0493 0.0000 4.7461 2 50.0000 0.0011 -2.4900 0.0016 2.4900 270.0380
16 1 50.0000 2.5000 0.0000 3.7496 3.7496 0.0000 315.0000 0.4998 0.8454 1.0000 1.1406 1.0396 -0.0001 4.3065 2 50.0000 0.0000 -2.5000 0.0000 2.5000 270.0000
17 1 50.0000 2.5000 0.0000 3.4569 3.4569 0.0000 346.2470 0.3827 1.4453 1.1608 1.9547 1.4599 -0.0003 27.1492 2 73.0000 25.0000 -18.0000 34.5687 38.9743 332.4939
18 1 50.0000 2.5000 0.0000 3.4954 3.4954 0.0000 51.7766 0.3981 0.6447 1.0640 1.7498 1.1612 0.0000 22.8977 2 61.0000 -5.0000 29.0000 -6.9907 29.8307 103.5532
19 1 50.0000 2.5000 0.0000 3.5514 3.5514 0.0000 272.2362 0.4206 0.6521 1.0251 1.9455 1.2055 -0.8219 31.9030 2 56.0000 -27.0000 -3.0000 -38.3556 38.4728 184.4723
20 1 50.0000 2.5000 0.0000 3.5244 3.5244 0.0000 11.9548 0.4098 1.1031 1.0400 1.9120 1.3353 0.0000 19.4535 2 58.0000 24.0000 15.0000 33.8342 37.0102 23.9095
21 1 50.0000 2.5000 0.0000 3.7494 3.7494 0.0000 3.5056 0.4997 1.2616 1.0000 1.1923 1.0808 0.0000 1.0000 2 50.0000 3.1736 0.5854 4.7596 4.7954 7.0113
22 1 50.0000 2.5000 0.0000 3.7493 3.7493 0.0000 0.0000 0.4997 1.3202 1.0000 1.1956 1.0861 0.0000 1.0000 2 50.0000 3.2972 0.0000 4.9450 4.9450 0.0000
23 1 50.0000 2.5000 0.0000 3.7497 3.7497 0.0000 5.8190 0.4999 1.2197 1.0000 1.1486 1.0604 0.0000 1.0000 2 50.0000 1.8634 0.5757 2.7949 2.8536 11.6380
24 1 50.0000 2.5000 0.0000 3.7493 3.7493 0.0000 1.9603 0.4997 1.2883 1.0000 1.1946 1.0836 0.0000 1.0000 2 50.0000 3.2592 0.3350 4.8879 4.8994 3.9206
25 1 60.2574 -34.0099 36.2677 -34.0678 49.7590 133.2085 132.0835 0.0017 1.3010 1.1427 3.2946 1.9951 0.0000 1.2644 2 60.4626 -34.1751 39.4387 -34.2333 52.2238 130.9584
26 1 63.0109 -31.0961 -5.8663 -32.6194 33.1427 190.1951 188.8221 0.0490 0.9402 1.1831 2.4549 1.4560 0.0000 1.2630 2 62.8187 -29.7946 -4.0864 -31.2542 31.5202 187.4490
27 1 61.2901 3.7196 -5.3901 5.5668 7.7487 315.9240 310.0313 0.4966 0.6952 1.1586 1.3092 1.0717 -0.0032 1.8731 2 61.4292 2.2480 -4.9620 3.3644 5.9950 304.1385
28 1 35.0831 -44.1164 3.7933 -44.3939 44.5557 175.1161 176.4290 0.0063 1.0168 1.2148 2.9105 1.6476 0.0000 1.8645 2 35.0232 -40.0716 1.5901 -40.3237 40.3550 177.7418
29 1 22.7233 20.0904 -46.6940 20.1424 50.8532 293.3339 291.3809 0.0026 0.3636 1.4014 3.1597 1.2617 -1.2537 2.0373 2 23.0331 14.9730 -42.5619 15.0118 45.1317 289.4279
30 1 36.4612 47.8580 18.3852 47.9197 51.3256 20.9901 21.8781 0.0013 0.9239 1.1943 3.3888 1.7357 0.0000 1.4146 2 36.2715 50.5065 21.2231 50.5716 54.8444 22.7660
31 1 90.8027 -2.0831 1.4410 -3.1245 3.4408 155.2410 167.1011 0.4999 1.1546 1.6110 1.1329 1.0511 0.0000 1.4441 2 91.1528 -1.6435 0.0447 -2.4651 2.4655 178.9612
32 1 90.9257 -0.5406 -0.9208 -0.8109 1.2270 228.6315 218.4363 0.5000 1.3916 1.5930 1.0620 1.0288 0.0000 1.5381 2 88.6381 -0.8985 -0.7239 -1.3477 1.5298 208.2412
33 1 6.7747 -0.2908 -2.4247 -0.4362 2.4636 259.8025 263.0049 0.4999 0.9556 1.6517 1.1057 1.0337 -0.0004 0.6377 2 5.8714 -0.0985 -2.2286 -0.1477 2.2335 266.2073
34 1 2.0776 0.0795 -1.1350 0.1192 1.1412 275.9978 268.0910 0.5000 0.7826 1.7246 1.0383 1.0100 0.0000 0.9082 2 0.9033 -0.0636 -0.5514 -0.0954 0.5596 260.18421
+53 -1
View File
@@ -33,7 +33,9 @@ from skimage.color import (rgb2hsv, hsv2rgb,
rgb2grey, gray2rgb,
xyz2lab, lab2xyz,
lab2rgb, rgb2lab,
is_rgb, is_gray
is_rgb, is_gray,
lab2lch, lch2lab,
guess_spatial_dimensions
)
from skimage import data_dir, data
@@ -41,6 +43,19 @@ from skimage import data_dir, data
import colorsys
def test_guess_spatial_dimensions():
im1 = np.zeros((5, 5))
im2 = np.zeros((5, 5, 5))
im3 = np.zeros((5, 5, 3))
im4 = np.zeros((5, 5, 5, 3))
im5 = np.zeros((5,))
assert_equal(guess_spatial_dimensions(im1), 2)
assert_equal(guess_spatial_dimensions(im2), 3)
assert_equal(guess_spatial_dimensions(im3), None)
assert_equal(guess_spatial_dimensions(im4), 3)
assert_raises(ValueError, guess_spatial_dimensions, im5)
class TestColorconv(TestCase):
img_rgb = imread(os.path.join(data_dir, 'color.png'))
@@ -235,6 +250,43 @@ class TestColorconv(TestCase):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(lab2rgb(rgb2lab(img_rgb)), img_rgb)
def test_lab_lch_roundtrip(self):
rgb = img_as_float(self.img_rgb)
lab = rgb2lab(rgb)
lab2 = lch2lab(lab2lch(lab))
assert_array_almost_equal(lab2, lab)
def test_rgb_lch_roundtrip(self):
rgb = img_as_float(self.img_rgb)
lab = rgb2lab(rgb)
lch = lab2lch(lab)
lab2 = lch2lab(lch)
rgb2 = lab2rgb(lab2)
assert_array_almost_equal(rgb, rgb2)
def test_lab_lch_0d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch2 = lab2lch(lab0[None, None, :])
assert_array_almost_equal(lch0, lch2[0, 0, :])
def test_lab_lch_1d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch1 = lab2lch(lab0[None, :])
assert_array_almost_equal(lch0, lch1[0, :])
def test_lab_lch_3d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch3 = lab2lch(lab0[None, None, None, :])
assert_array_almost_equal(lch0, lch3[0, 0, 0, :])
def _get_lab0(self):
rgb = img_as_float(self.img_rgb[:1, :1, :])
return rgb2lab(rgb)[0, 0, :]
def test_gray2rgb():
x = np.array([0, 0.5, 1])
assert_raises(ValueError, gray2rgb, x)
+22 -1
View File
@@ -3,7 +3,8 @@ import itertools
import numpy as np
from numpy import testing
from skimage.color.colorlabel import label2rgb
from numpy.testing import assert_array_almost_equal as assert_close
from numpy.testing import (assert_array_almost_equal as assert_close,
assert_array_equal)
def test_shape_mismatch():
@@ -69,6 +70,26 @@ def test_bg_and_color_cycle():
assert_close(pixel, color)
def test_label_consistency():
"""Assert that the same labels map to the same colors."""
label_1 = np.arange(5).reshape(1, -1)
label_2 = np.array([2, 4])
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1)]
# Set alphas just in case the defaults change
rgb_1 = label2rgb(label_1, colors=colors)
rgb_2 = label2rgb(label_2, colors=colors)
for label_id in label_2.flat:
assert_close(rgb_1[label_1 == label_id], rgb_2[label_2 == label_id])
def test_leave_labels_alone():
labels = np.array([-1, 0, 1])
labels_saved = labels.copy()
label2rgb(labels)
label2rgb(labels, bg_label=1)
assert_array_equal(labels, labels_saved)
if __name__ == '__main__':
testing.run_module_suite()
+167
View File
@@ -0,0 +1,167 @@
"""Test for correctness of color distance functions"""
from os.path import abspath, dirname, join as pjoin
import numpy as np
from numpy.testing import assert_allclose
from skimage.color import (deltaE_cie76,
deltaE_ciede94,
deltaE_ciede2000,
deltaE_cmc)
def test_ciede2000_dE():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_ciede2000(lab1, lab2)
assert_allclose(dE2, data['dE'], rtol=1.e-4)
def load_ciede2000_data():
dtype = [('pair', int),
('1', int),
('L1', float),
('a1', float),
('b1', float),
('a1_prime', float),
('C1_prime', float),
('h1_prime', float),
('hbar_prime', float),
('G', float),
('T', float),
('SL', float),
('SC', float),
('SH', float),
('RT', float),
('dE', float),
('2', int),
('L2', float),
('a2', float),
('b2', float),
('a2_prime', float),
('C2_prime', float),
('h2_prime', float),
]
# note: ciede_test_data.txt contains several intermediate quantities
path = pjoin(dirname(abspath(__file__)), 'ciede2000_test_data.txt')
return np.loadtxt(path, dtype=dtype)
def test_cie76():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_cie76(lab1, lab2)
oracle = np.array([
4.00106328, 6.31415011, 9.1776999, 2.06270077, 2.36957073,
2.91529271, 2.23606798, 2.23606798, 4.98000036, 4.9800004,
4.98000044, 4.98000049, 4.98000036, 4.9800004, 4.98000044,
3.53553391, 36.86800781, 31.91002977, 30.25309901, 27.40894015,
0.89242934, 0.7972, 0.8583065, 0.82982507, 3.1819238,
2.21334297, 1.53890382, 4.60630929, 6.58467989, 3.88641412,
1.50514845, 2.3237848, 0.94413208, 1.31910843
])
assert_allclose(dE2, oracle, rtol=1.e-8)
def test_ciede94():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_ciede94(lab1, lab2)
oracle = np.array([
1.39503887, 1.93410055, 2.45433566, 0.68449187, 0.6695627,
0.69194527, 2.23606798, 2.03163832, 4.80069441, 4.80069445,
4.80069449, 4.80069453, 4.80069441, 4.80069445, 4.80069449,
3.40774352, 34.6891632, 29.44137328, 27.91408781, 24.93766082,
0.82213163, 0.71658427, 0.8048753, 0.75284394, 1.39099471,
1.24808929, 1.29795787, 1.82045088, 2.55613309, 1.42491303,
1.41945261, 2.3225685, 0.93853308, 1.30654464
])
assert_allclose(dE2, oracle, rtol=1.e-8)
def test_cmc():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_cmc(lab1, lab2)
oracle = np.array([
1.73873611, 2.49660844, 3.30494501, 0.85735576, 0.88332927,
0.97822692, 3.50480874, 2.87930032, 6.5783807, 6.57838075,
6.5783808, 6.57838086, 6.67492321, 6.67492326, 6.67492331,
4.66852997, 42.10875485, 39.45889064, 38.36005919, 33.93663807,
1.14400168, 1.00600419, 1.11302547, 1.05335328, 1.42822951,
1.2548143, 1.76838061, 2.02583367, 3.08695508, 1.74893533,
1.90095165, 1.70258148, 1.80317207, 2.44934417
])
assert_allclose(dE2, oracle, rtol=1.e-8)
def test_single_color_cie76():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_cie76(lab1, lab2)
def test_single_color_ciede94():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_ciede94(lab1, lab2)
def test_single_color_ciede2000():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_ciede2000(lab1, lab2)
def test_single_color_cmc():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_cmc(lab1, lab2)
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()
+17 -1
View File
@@ -23,7 +23,8 @@ __all__ = ['load',
'horse',
'clock',
'immunohistochemistry',
'chelsea']
'chelsea',
'coffee']
def load(f):
@@ -184,3 +185,18 @@ def chelsea():
"""
return load("chelsea.png")
def coffee():
"""Coffee cup.
This photograph is courtesy of Pikolo Espresso Bar.
It contains several elliptical shapes as well as varying texture (smooth
porcelain to course wood grain).
Notes
-----
No copyright restrictions. CC0 by the photographer (Rachel Michetti).
"""
return load("coffee.png")
Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

+5
View File
@@ -44,6 +44,11 @@ def test_chelsea():
data.chelsea()
def test_coffee():
""" Test that "coffee" image can be loaded. """
data.coffee()
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()
+9 -2
View File
@@ -1,11 +1,18 @@
from .draw import circle, ellipse, set_color
from ._draw import line, polygon, ellipse_perimeter, circle_perimeter, \
bezier_segment
from .draw3d import ellipsoid, ellipsoid_stats
from ._draw import (line, line_aa, polygon, ellipse_perimeter,
circle_perimeter, circle_perimeter_aa,
_bezier_segment, bezier_curve)
__all__ = ['line',
'line_aa',
'bezier_curve',
'polygon',
'ellipse',
'ellipse_perimeter',
'ellipsoid',
'ellipsoid_stats',
'circle',
'circle_perimeter',
'circle_perimeter_aa',
'set_color']
+339 -31
View File
@@ -6,7 +6,7 @@ import math
import numpy as np
cimport numpy as cnp
from libc.math cimport sqrt, sin, cos, floor
from libc.math cimport sqrt, sin, cos, floor, ceil
from skimage._shared.geometry cimport point_in_polygon
@@ -27,6 +27,10 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
Notes
-----
Anti-aliased line generator is available with `line_aa`.
Examples
--------
>>> from skimage.draw import line
@@ -47,8 +51,6 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
"""
cdef cnp.ndarray[cnp.intp_t, ndim=1, mode="c"] rr, cc
cdef char steep = 0
cdef Py_ssize_t dx = abs(x2 - x)
cdef Py_ssize_t dy = abs(y2 - y)
@@ -69,8 +71,8 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
sx, sy = sy, sx
d = (2 * dy) - dx
rr = np.zeros(int(dx) + 1, dtype=np.intp)
cc = np.zeros(int(dx) + 1, dtype=np.intp)
cdef Py_ssize_t[:] rr = np.zeros(int(dx) + 1, dtype=np.intp)
cdef Py_ssize_t[:] cc = np.zeros(int(dx) + 1, dtype=np.intp)
for i in range(dx):
if steep:
@@ -88,7 +90,100 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
rr[dx] = y2
cc[dx] = x2
return rr, cc
return np.asarray(rr), np.asarray(cc)
def line_aa(Py_ssize_t y1, Py_ssize_t x1, Py_ssize_t y2, Py_ssize_t x2):
"""Generate anti-aliased line pixel coordinates.
Parameters
----------
y1, x1 : int
Starting position (row, column).
y2, x2 : int
End position (row, column).
Returns
-------
rr, cc, val : (N,) ndarray (int, int, float)
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
``img[rr, cc] = val``.
References
----------
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
http://members.chello.at/easyfilter/Bresenham.pdf
Examples
--------
>>> from skimage.draw import line_aa
>>> img = np.zeros((10, 10), dtype=np.uint8)
>>> rr, cc, val = line_aa(1, 1, 8, 8)
>>> img[rr, cc] = val * 255
>>> img
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 255, 56, 0, 0, 0, 0, 0, 0, 0],
[ 0, 56, 255, 56, 0, 0, 0, 0, 0, 0],
[ 0, 0, 56, 255, 56, 0, 0, 0, 0, 0],
[ 0, 0, 0, 56, 255, 56, 0, 0, 0, 0],
[ 0, 0, 0, 0, 56, 255, 56, 0, 0, 0],
[ 0, 0, 0, 0, 0, 56, 255, 56, 0, 0],
[ 0, 0, 0, 0, 0, 0, 56, 255, 56, 0],
[ 0, 0, 0, 0, 0, 0, 0, 56, 255, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
"""
cdef list rr = list()
cdef list cc = list()
cdef list val = list()
cdef int dx = abs(x1 - x2)
cdef int dy = abs(y1 - y2)
cdef int err = dx - dy
cdef int x, y, e, ed, sign_x, sign_y
if x1 < x2:
sign_x = 1
else:
sign_x = -1
if y1 < y2:
sign_y = 1
else:
sign_y = -1
if dx + dy == 0:
ed = 1
else:
ed = <int>(sqrt(dx*dx + dy*dy))
x, y = x1, y1
while True:
cc.append(x)
rr.append(y)
val.append(1. * abs(err - dx + dy) / <float>(ed))
e = err
if 2 * e >= -dx:
if x == x2:
break
if e + dy < ed:
cc.append(x)
rr.append(y + sign_y)
val.append(1. * abs(e + dy) / <float>(ed))
err -= dy
x += sign_x
if 2 * e <= dy:
if y == y2:
break
if dx - e < ed:
cc.append(x)
rr.append(y)
val.append(abs(dx - e) / <float>(ed))
err += dx
y += sign_y
return (np.array(rr, dtype=np.intp),
np.array(cc, dtype=np.intp),
1. - np.array(val, dtype=np.float))
def polygon(y, x, shape=None):
@@ -136,9 +231,9 @@ def polygon(y, x, shape=None):
cdef Py_ssize_t nr_verts = x.shape[0]
cdef Py_ssize_t minr = int(max(0, y.min()))
cdef Py_ssize_t maxr = int(math.ceil(y.max()))
cdef Py_ssize_t maxr = int(ceil(y.max()))
cdef Py_ssize_t minc = int(max(0, x.min()))
cdef Py_ssize_t maxc = int(math.ceil(x.max()))
cdef Py_ssize_t maxc = int(ceil(x.max()))
# make sure output coordinates do not exceed image size
if shape is not None:
@@ -149,8 +244,8 @@ def polygon(y, x, shape=None):
# make contigous arrays for r, c coordinates
cdef cnp.ndarray contiguous_rdata, contiguous_cdata
contiguous_rdata = np.ascontiguousarray(y, 'double')
contiguous_cdata = np.ascontiguousarray(x, 'double')
contiguous_rdata = np.ascontiguousarray(y, dtype=np.double)
contiguous_cdata = np.ascontiguousarray(x, dtype=np.double)
cdef cnp.double_t* rptr = <cnp.double_t*>contiguous_rdata.data
cdef cnp.double_t* cptr = <cnp.double_t*>contiguous_cdata.data
@@ -184,6 +279,7 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
Returns
-------
rr, cc : (N,) ndarray of int
Bresenham and Andres' method:
Indices of pixels that belong to the circle perimeter.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
@@ -194,13 +290,14 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
circles create a disc whereas Bresenham can make holes. There
is also less distortions when Andres circles are rotated.
Bresenham method is also known as midpoint circle algorithm.
Anti-aliased circle generator is available with `circle_perimeter_aa`.
References
----------
.. [1] J.E. Bresenham, "Algorithm for computer control of a digital
plotter", 4 (1965) 25-30.
.. [2] E. Andres, "Discrete circles, rings and spheres",
18 (1994) 695-706.
plotter", IBM Systems journal, 4 (1965) 25-30.
.. [2] E. Andres, "Discrete circles, rings and spheres", Computers &
Graphics, 18 (1994) 695-706.
Examples
--------
@@ -228,6 +325,10 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
cdef Py_ssize_t x = 0
cdef Py_ssize_t y = radius
cdef Py_ssize_t d = 0
cdef double dceil = 0
cdef double dceil_prev = 0
cdef char cmethod
if method == 'bresenham':
d = 3 - 2 * radius
@@ -260,8 +361,84 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
d = d + 2 * (y - x - 1)
y = y - 1
x = x + 1
return (np.array(rr, dtype=np.intp) + cy,
np.array(cc, dtype=np.intp) + cx)
return np.array(rr, dtype=np.intp) + cy, np.array(cc, dtype=np.intp) + cx
def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius):
"""Generate anti-aliased circle perimeter coordinates.
Parameters
----------
cy, cx : int
Centre coordinate of circle.
radius: int
Radius of circle.
Returns
-------
rr, cc, val : (N,) ndarray (int, int, float)
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
``img[rr, cc] = val``.
Notes
-----
Wu's method draws anti-aliased circle. This implementation doesn't use
lookup table optimization.
References
----------
.. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH
Computer Graphics, 25 (1991) 143-152.
Examples
--------
>>> from skimage.draw import circle_perimeter_aa
>>> img = np.zeros((10, 10), dtype=np.uint8)
>>> rr, cc, val = circle_perimeter_aa(4, 4, 3)
>>> img[rr, cc] = val * 255
>>> img
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
[ 0, 255, 0, 0, 0, 0, 0, 255, 0, 0],
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
"""
cdef Py_ssize_t x = 0
cdef Py_ssize_t y = radius
cdef Py_ssize_t d = 0
cdef double dceil = 0
cdef double dceil_prev = 0
cdef list rr = [y, x, y, x, -y, -x, -y, -x]
cdef list cc = [x, y, -x, -y, x, y, -x, -y]
cdef list val = [1] * 8
while y > x + 1:
x += 1
dceil = sqrt(radius**2 - x**2)
dceil = ceil(dceil) - dceil
if dceil < dceil_prev:
y -= 1
rr.extend([y, y - 1, x, x, y, y - 1, x, x])
cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y])
rr.extend([-y, 1 - y, -x, -x, -y, 1 - y, -x, -x])
cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y])
val.extend([1 - dceil, dceil] * 8)
dceil_prev = dceil
return (np.array(rr, dtype=np.intp) + cy,
np.array(cc, dtype=np.intp) + cx,
np.array(val, dtype=np.float))
def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
@@ -272,9 +449,9 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
----------
cy, cx : int
Centre coordinate of ellipse.
yradius, xradius: int
yradius, xradius : int
Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``.
orientation: double, optional (default 0)
orientation : double, optional (default 0)
Major axis orientation in clockwise direction as radians.
Returns
@@ -384,38 +561,38 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
iyd = int(floor(ya * w + 0.5))
# Draw the 4 quadrants
rr, cc = bezier_segment(iy0 + iyd, ix0, iy0, ix0, iy0, ix0 + ixd, 1-w)
rr, cc = _bezier_segment(iy0 + iyd, ix0, iy0, ix0, iy0, ix0 + ixd, 1-w)
py.extend(rr)
px.extend(cc)
rr, cc = bezier_segment(iy0 + iyd, ix0, iy1, ix0, iy1, ix1 - ixd, w)
rr, cc = _bezier_segment(iy0 + iyd, ix0, iy1, ix0, iy1, ix1 - ixd, w)
py.extend(rr)
px.extend(cc)
rr, cc = bezier_segment(iy1 - iyd, ix1, iy1, ix1, iy1, ix1 - ixd, 1-w)
rr, cc = _bezier_segment(iy1 - iyd, ix1, iy1, ix1, iy1, ix1 - ixd, 1-w)
py.extend(rr)
px.extend(cc)
rr, cc = bezier_segment(iy1 - iyd, ix1, iy0, ix1, iy0, ix0 + ixd, w)
rr, cc = _bezier_segment(iy1 - iyd, ix1, iy0, ix1, iy0, ix0 + ixd, w)
py.extend(rr)
px.extend(cc)
return np.array(py, dtype=np.intp), np.array(px, dtype=np.intp)
def bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
Py_ssize_t y1, Py_ssize_t x1,
Py_ssize_t y2, Py_ssize_t x2,
double weight):
def _bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
Py_ssize_t y1, Py_ssize_t x1,
Py_ssize_t y2, Py_ssize_t x2,
double weight):
"""Generate Bezier segment coordinates.
Parameters
----------
y0, x0 : int
Coordinates of the first point
Coordinates of the first control point.
y1, x1 : int
Coordinates of the middle point
Coordinates of the middle control point.
y2, x2 : int
Coordinates of the last point
Coordinates of the last control point.
weight : double
Middle point weight, it describes the line tension.
Middle control point weight, it describes the line tension.
Returns
-------
@@ -427,7 +604,7 @@ def bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
Notes
-----
The algorithm is the rational quadratic algorithm presented in
reference [1].
reference [1]_.
References
----------
@@ -494,8 +671,8 @@ def bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
sy = floor((y0 + 2 * weight * y1 + y2) * xy * 0.5 + 0.5)
dx = floor((weight * x1 + x0) * xy + 0.5)
dy = floor((y1 * weight + y0) * xy + 0.5)
return bezier_segment(y0, x0, <Py_ssize_t>(dy), <Py_ssize_t>(dx),
<Py_ssize_t>(sy), <Py_ssize_t>(sx), cur)
return _bezier_segment(y0, x0, <Py_ssize_t>(dy), <Py_ssize_t>(dx),
<Py_ssize_t>(sy), <Py_ssize_t>(sx), cur)
err = dx + dy - xy
while dy <= xy and dx >= xy:
@@ -528,6 +705,137 @@ def bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
return np.array(py, dtype=np.intp), np.array(px, dtype=np.intp)
def bezier_curve(Py_ssize_t y0, Py_ssize_t x0,
Py_ssize_t y1, Py_ssize_t x1,
Py_ssize_t y2, Py_ssize_t x2,
double weight):
"""Generate Bezier curve coordinates.
Parameters
----------
y0, x0 : int
Coordinates of the first control point.
y1, x1 : int
Coordinates of the middle control point.
y2, x2 : int
Coordinates of the last control point.
weight : double
Middle control point weight, it describes the line tension.
Returns
-------
rr, cc : (N,) ndarray of int
Indices of pixels that belong to the Bezier curve.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
Notes
-----
The algorithm is the rational quadratic algorithm presented in
reference [1]_.
References
----------
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
http://members.chello.at/easyfilter/Bresenham.pdf
Examples
--------
>>> import numpy as np
>>> from skimage.draw import bezier_curve
>>> img = np.zeros((10, 10), dtype=np.uint8)
>>> rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
>>> img[rr, cc] = 1
>>> img
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
"""
# Pixels
cdef list px = list()
cdef list py = list()
cdef int x, y
cdef double xx, yy, ww, t, q
x = x0 - 2 * x1 + x2
y = y0 - 2 * y1 + y2
xx = x0 - x1
yy = y0 - y1
if xx * (x2 - x1) > 0:
if yy * (y2 - y1):
if abs(xx * y) > abs(yy * x):
x0 = x2
x2 = <Py_ssize_t>(xx + x1)
y0 = y2
y2 = <Py_ssize_t>(yy + y1)
if (x0 == x2) or (weight == 1.):
t = <double>(x0 - x1) / x
else:
q = sqrt(4. * weight * weight * (x0 - x1) * (x2 - x1) + (x2 - x0) * floor(x2 - x0))
if (x1 < x0):
q = -q
t = (2. * weight * (x0 - x1) - x0 + x2 + q) / (2. * (1. - weight) * (x2 - x0))
q = 1. / (2. * t * (1. - t) * (weight - 1.) + 1.0)
xx = (t * t * (x0 - 2. * weight * x1 + x2) + 2. * t * (weight * x1 - x0) + x0) * q
yy = (t * t * (y0 - 2. * weight * y1 + y2) + 2. * t * (weight * y1 - y0) + y0) * q
ww = t * (weight - 1.) + 1.
ww *= ww * q
weight = ((1. - t) * (weight - 1.) + 1.) * sqrt(q)
x = <int>(xx + 0.5)
y = <int>(yy + 0.5)
yy = (xx - x0) * (y1 - y0) / (x1 - x0) + y0
rr, cc = _bezier_segment(y0, x0, <int>(yy + 0.5), x, y, x, ww)
px.extend(rr)
py.extend(cc)
yy = (xx - x2) * (y1 - y2) / (x1 - x2) + y2
y1 = <int>(yy + 0.5)
x0 = x1 = x
y0 = y
if (y0 - y1) * floor(y2 - y1) > 0:
if (y0 == y2) or (weight == 1):
t = (y0 - y1) / (y0 - 2. * y1 + y2)
else:
q = sqrt(4. * weight * weight * (y0 - y1) * (y2 - y1) + (y2 - y0) * floor(y2 - y0))
if y1 < y0:
q = -q
t = (2. * weight * (y0 - y1) - y0 + y2 + q) / (2. * (1. - weight) * (y2 - y0))
q = 1. / (2. * t * (1. - t) * (weight - 1.) + 1.)
xx = (t * t * (x0 - 2. * weight * x1 + x2) + 2. * t * (weight * x1 - x0) + x0) * q
yy = (t * t * (y0 - 2. * weight * y1 + y2) + 2. * t * (weight * y1 - y0) + y0) * q
ww = t * (weight - 1.) + 1.
ww *= ww * q
weight = ((1. - t) * (weight - 1.) + 1.) * sqrt(q)
x = <int>(xx + 0.5)
y = <int>(yy + 0.5)
xx = (x1 - x0) * (yy - y0) / (y1 - y0) + x0
rr, cc = _bezier_segment(y0, x0, y, <int>(xx + 0.5), y, x, ww)
px.extend(rr)
py.extend(cc)
xx = (x1 - x2) * (yy - y2) / (y1 - y2) + x2
x1 = <int>(xx + 0.5)
x0 = x
y0 = y1 = y
rr, cc = _bezier_segment(y0, x0, y1, x1, y2, x2, weight * weight)
px.extend(rr)
py.extend(cc)
return np.array(px, dtype=np.intp), np.array(py, dtype=np.intp)
def set_color(img, coords, color):
"""Set pixel color in the image at the given coordinates.
+1 -1
View File
@@ -52,7 +52,7 @@ def ellipse(cy, cx, yradius, xradius, shape=None):
dc = 1 / float(xradius)
r, c = np.ogrid[-1:1:dr, -1:1:dc]
rr, cc = np.nonzero(r ** 2 + c ** 2 < 1)
rr, cc = np.nonzero(r ** 2 + c ** 2 < 1)
rr.flags.writeable = True
cc.flags.writeable = True
+115
View File
@@ -0,0 +1,115 @@
# coding: utf-8
import numpy as np
from scipy.special import (ellipkinc as ellip_F, ellipeinc as ellip_E)
def ellipsoid(a, b, c, spacing=(1., 1., 1.), levelset=False):
"""
Generates ellipsoid with semimajor axes aligned with grid dimensions
on grid with specified `spacing`.
Parameters
----------
a : float
Length of semimajor axis aligned with x-axis.
b : float
Length of semimajor axis aligned with y-axis.
c : float
Length of semimajor axis aligned with z-axis.
spacing : tuple of floats, length 3
Spacing in (x, y, z) spatial dimensions.
levelset : bool
If True, returns the level set for this ellipsoid (signed level
set about zero, with positive denoting interior) as np.float64.
False returns a binarized version of said level set.
Returns
-------
ellip : (N, M, P) array
Ellipsoid centered in a correctly sized array for given `spacing`.
Boolean dtype unless `levelset=True`, in which case a float array is
returned with the level set above 0.0 representing the ellipsoid.
"""
if (a <= 0) or (b <= 0) or (c <= 0):
raise ValueError('Parameters a, b, and c must all be > 0')
offset = np.r_[1, 1, 1] * np.r_[spacing]
# Calculate limits, and ensure output volume is odd & symmetric
low = np.ceil((- np.r_[a, b, c] - offset))
high = np.floor((np.r_[a, b, c] + offset + 1))
for dim in range(3):
if (high[dim] - low[dim]) % 2 == 0:
low[dim] -= 1
num = np.arange(low[dim], high[dim], spacing[dim])
if 0 not in num:
low[dim] -= np.max(num[num < 0])
# Generate (anisotropic) spatial grid
x, y, z = np.mgrid[low[0]:high[0]:spacing[0],
low[1]:high[1]:spacing[1],
low[2]:high[2]:spacing[2]]
if not levelset:
arr = ((x / float(a)) ** 2 +
(y / float(b)) ** 2 +
(z / float(c)) ** 2) <= 1
else:
arr = ((x / float(a)) ** 2 +
(y / float(b)) ** 2 +
(z / float(c)) ** 2) - 1
return arr
def ellipsoid_stats(a, b, c):
"""
Calculates analytical surface area and volume for ellipsoid with
semimajor axes aligned with grid dimensions of specified `spacing`.
Parameters
----------
a : float
Length of semimajor axis aligned with x-axis.
b : float
Length of semimajor axis aligned with y-axis.
c : float
Length of semimajor axis aligned with z-axis.
Returns
-------
vol : float
Calculated volume of ellipsoid.
surf : float
Calculated surface area of ellipsoid.
"""
if (a <= 0) or (b <= 0) or (c <= 0):
raise ValueError('Parameters a, b, and c must all be > 0')
# Calculate volume & surface area
# Surface calculation requires a >= b >= c and a != c.
abc = [a, b, c]
abc.sort(reverse=True)
a = abc[0]
b = abc[1]
c = abc[2]
# Volume
vol = 4 / 3. * np.pi * a * b * c
# Analytical ellipsoid surface area
phi = np.arcsin((1. - (c ** 2 / (a ** 2.))) ** 0.5)
d = float((a ** 2 - c ** 2) ** 0.5)
m = (a ** 2 * (b ** 2 - c ** 2) /
float(b ** 2 * (a ** 2 - c ** 2)))
F = ellip_F(phi, m)
E = ellip_E(phi, m)
surf = 2 * np.pi * (c ** 2 +
b * c ** 2 / d * F +
b * d * E)
return vol, surf
+174 -7
View File
@@ -1,8 +1,11 @@
from numpy.testing import assert_array_equal
from numpy.testing import assert_array_equal, assert_equal
import numpy as np
from skimage.draw import line, polygon, circle, circle_perimeter, \
ellipse, ellipse_perimeter, bezier_segment
from skimage.draw import (line, line_aa, polygon,
circle, circle_perimeter, circle_perimeter_aa,
ellipse, ellipse_perimeter,
_bezier_segment, bezier_curve,
)
def test_line_horizontal():
@@ -52,6 +55,43 @@ def test_line_diag():
assert_array_equal(img, img_)
def test_line_aa_horizontal():
img = np.zeros((10, 10))
rr, cc, val = line_aa(0, 0, 0, 9)
img[rr, cc] = val
img_ = np.zeros((10, 10))
img_[0, :] = 1
assert_array_equal(img, img_)
def test_line_aa_vertical():
img = np.zeros((10, 10))
rr, cc, val = line_aa(0, 0, 9, 0)
img[rr, cc] = val
img_ = np.zeros((10, 10))
img_[:, 0] = 1
assert_array_equal(img, img_)
def test_line_aa_diagonal():
img = np.zeros((10, 10))
rr, cc, val = line_aa(0, 0, 9, 6)
img[rr, cc] = 1
# Check that each pixel belonging to line,
# also belongs to line_aa
r, c = line(0, 0, 9, 6)
for x, y in zip(r, c):
assert_equal(img[r, c], 1)
def test_polygon_rectangle():
img = np.zeros((10, 10), 'uint8')
poly = np.array(((1, 1), (4, 1), (4, 4), (1, 4), (1, 1)))
@@ -215,6 +255,38 @@ def test_circle_perimeter_andres():
assert_array_equal(img, img_)
def test_circle_perimeter_aa():
img = np.zeros((15, 15), 'uint8')
rr, cc, val = circle_perimeter_aa(7, 7, 0)
img[rr, cc] = 1
assert(np.sum(img) == 1)
assert(img[7][7] == 1)
img = np.zeros((17, 17), 'uint8')
rr, cc, val = circle_perimeter_aa(8, 8, 7)
img[rr, cc] = val * 255
img_ = np.array(
[[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 82, 180, 236, 255, 236, 180, 82, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 189, 172, 74, 18, 0, 18, 74, 172, 189, 0, 0, 0, 0],
[ 0, 0, 0, 229, 25, 0, 0, 0, 0, 0, 0, 0, 25, 229, 0, 0, 0],
[ 0, 0, 189, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 189, 0, 0],
[ 0, 82, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 82, 0],
[ 0, 180, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 180, 0],
[ 0, 236, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 236, 0],
[ 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0],
[ 0, 236, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 236, 0],
[ 0, 180, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 180, 0],
[ 0, 82, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 82, 0],
[ 0, 0, 189, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 189, 0, 0],
[ 0, 0, 0, 229, 25, 0, 0, 0, 0, 0, 0, 0, 25, 229, 0, 0, 0],
[ 0, 0, 0, 0, 189, 172, 74, 18, 0, 18, 74, 172, 189, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 82, 180, 236, 255, 236, 180, 82, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
)
assert_array_equal(img, img_)
def test_ellipse():
img = np.zeros((15, 15), 'uint8')
@@ -360,18 +432,21 @@ def test_bezier_segment_straight():
y1 = 50
x2 = 150
y2 = 150
rr, cc = bezier_segment(x0, y0, x1, y1, x2, y2, 0)
image [rr, cc] = 1
rr, cc = _bezier_segment(x0, y0, x1, y1, x2, y2, 0)
image[rr, cc] = 1
image2 = np.zeros((200, 200), dtype=int)
rr, cc = line(x0, y0, x2, y2)
image2 [rr, cc] = 1
image2[rr, cc] = 1
assert_array_equal(image, image2)
def test_bezier_segment_curved():
img = np.zeros((25, 25), 'uint8')
rr, cc = bezier_segment(20, 20, 20, 2, 2, 2, 1)
x1, y1 = 20, 20
x2, y2 = 20, 2
x3, y3 = 2, 2
rr, cc = _bezier_segment(x1, y1, x2, y2, x3, y3, 1)
img[rr, cc] = 1
img_ = np.array(
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
@@ -400,9 +475,101 @@ def test_bezier_segment_curved():
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
)
assert_equal(img[x1, y1], 1)
assert_equal(img[x3, y3], 1)
assert_array_equal(img, img_)
def test_bezier_curve_straight():
image = np.zeros((200, 200), dtype=int)
x0 = 50
y0 = 50
x1 = 150
y1 = 50
x2 = 150
y2 = 150
rr, cc = bezier_curve(x0, y0, x1, y1, x2, y2, 0)
image [rr, cc] = 1
image2 = np.zeros((200, 200), dtype=int)
rr, cc = line(x0, y0, x2, y2)
image2 [rr, cc] = 1
assert_array_equal(image, image2)
def test_bezier_curved_weight_eq_1():
img = np.zeros((23, 8), 'uint8')
x1, y1 = (1, 1)
x2, y2 = (11, 11)
x3, y3 = (21, 1)
rr, cc = bezier_curve(x1, y1, x2, y2, x3, y3, 1)
img[rr, cc] = 1
assert_equal(img[x1, y1], 1)
assert_equal(img[x3, y3], 1)
img_ = np.array(
[[0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]
)
assert_equal(img, img_)
def test_bezier_curved_weight_neq_1():
img = np.zeros((23, 10), 'uint8')
x1, y1 = (1, 1)
x2, y2 = (11, 11)
x3, y3 = (21, 1)
rr, cc = bezier_curve(x1, y1, x2, y2, x3, y3, 2)
img[rr, cc] = 1
assert_equal(img[x1, y1], 1)
assert_equal(img[x3, y3], 1)
img_ = np.array(
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
)
assert_equal(img, img_)
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()
+104
View File
@@ -0,0 +1,104 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_allclose
from skimage.draw import ellipsoid, ellipsoid_stats
def test_ellipsoid_bool():
test = ellipsoid(2, 2, 2)[1:-1, 1:-1, 1:-1]
test_anisotropic = ellipsoid(2, 2, 4, spacing=(1., 1., 2.))
test_anisotropic = test_anisotropic[1:-1, 1:-1, 1:-1]
expected = np.array([[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]])
assert_array_equal(test, expected.astype(bool))
assert_array_equal(test_anisotropic, expected.astype(bool))
def test_ellipsoid_levelset():
test = ellipsoid(2, 2, 2, levelset=True)[1:-1, 1:-1, 1:-1]
test_anisotropic = ellipsoid(2, 2, 4, spacing=(1., 1., 2.),
levelset=True)
test_anisotropic = test_anisotropic[1:-1, 1:-1, 1:-1]
expected = np.array([[[ 2. , 1.25, 1. , 1.25, 2. ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 1. , 0.25, 0. , 0.25, 1. ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 2. , 1.25, 1. , 1.25, 2. ]],
[[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25]],
[[ 1. , 0.25, 0. , 0.25, 1. ],
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
[ 0. , -0.75, -1. , -0.75, 0. ],
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
[ 1. , 0.25, 0. , 0.25, 1. ]],
[[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25]],
[[ 2. , 1.25, 1. , 1.25, 2. ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 1. , 0.25, 0. , 0.25, 1. ],
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
[ 2. , 1.25, 1. , 1.25, 2. ]]])
assert_allclose(test, expected)
assert_allclose(test_anisotropic, expected)
def test_ellipsoid_stats():
# Test comparison values generated by Wolfram Alpha
vol, surf = ellipsoid_stats(6, 10, 16)
assert(round(1280 * np.pi, 4) == round(vol, 4))
assert(1383.28 == round(surf, 2))
# Test when a <= b <= c does not hold
vol, surf = ellipsoid_stats(16, 6, 10)
assert(round(1280 * np.pi, 4) == round(vol, 4))
assert(1383.28 == round(surf, 2))
# Larger test to ensure reliability over broad range
vol, surf = ellipsoid_stats(17, 27, 169)
assert(round(103428 * np.pi, 4) == round(vol, 4))
assert(37426.3 == round(surf, 1))
if __name__ == "__main__":
np.testing.run_module_suite()
+1 -1
View File
@@ -287,7 +287,7 @@ def adjust_log(image, gain=1, inv=False):
inv : float
If True, it performs inverse logarithmic correction,
else correction will be logarithmic. Defaults to False.
Returns
-------
out : ndarray
+3 -2
View File
@@ -2,8 +2,9 @@ from ._daisy import daisy
from ._hog import hog
from .texture import greycomatrix, greycoprops, local_binary_pattern
from .peak import peak_local_max
from .corner import (corner_kitchen_rosenfeld, corner_harris, corner_shi_tomasi,
corner_foerstner, corner_subpix, corner_peaks)
from .corner import (corner_kitchen_rosenfeld, corner_harris,
corner_shi_tomasi, corner_foerstner, corner_subpix,
corner_peaks)
from .corner_cy import corner_moravec
from .template import match_template
+228
View File
@@ -0,0 +1,228 @@
import numpy as np
from scipy.ndimage.filters import gaussian_filter
from ..util import img_as_float
from .util import _mask_border_keypoints, pairwise_hamming_distance
from ._brief_cy import _brief_loop
def brief(image, keypoints, descriptor_size=256, mode='normal', patch_size=49,
sample_seed=1, variance=2):
"""**Experimental function**.
Extract BRIEF Descriptor about given keypoints for a given image.
Parameters
----------
image : 2D ndarray
Input image.
keypoints : (P, 2) ndarray
Array of keypoint locations in the format (row, col).
descriptor_size : int
Size of BRIEF descriptor about each keypoint. Sizes 128, 256 and 512
preferred by the authors. Default is 256.
mode : string
Probability distribution for sampling location of decision pixel-pairs
around keypoints. Default is 'normal' otherwise uniform.
patch_size : int
Length of the two dimensional square patch sampling region around
the keypoints. Default is 49.
sample_seed : int
Seed for sampling the decision pixel-pairs. From a square window with
length patch_size, pixel pairs are sampled using the `mode` parameter
to build the descriptors using intensity comparison. The value of
`sample_seed` should be the same for the images to be matched while
building the descriptors. Default is 1.
variance : float
Variance of the Gaussian Low Pass filter applied on the image to
alleviate noise sensitivity. Default is 2.
Returns
-------
descriptors : (Q, `descriptor_size`) ndarray of dtype bool
2D ndarray of binary descriptors of size `descriptor_size` about Q
keypoints after filtering out border keypoints with value at an index
(i, j) either being True or False representing the outcome
of Intensity comparison about ith keypoint on jth decision pixel-pair.
keypoints : (Q, 2) ndarray
Location i.e. (row, col) of keypoints after removing out those that
are near border.
References
----------
.. [1] Michael Calonder, Vincent Lepetit, Christoph Strecha and Pascal Fua
"BRIEF : Binary robust independent elementary features",
http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf
Examples
--------
>>> import numpy as np
>>> from skimage.feature.corner import corner_peaks, corner_harris
>>> from skimage.feature import pairwise_hamming_distance, brief, match_keypoints_brief
>>> square1 = np.zeros([8, 8], dtype=np.int32)
>>> square1[2:6, 2:6] = 1
>>> square1
array([[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
>>> keypoints1
array([[2, 2],
[2, 5],
[5, 2],
[5, 5]])
>>> descriptors1, keypoints1 = brief(square1, keypoints1, patch_size=5)
>>> keypoints1
array([[2, 2],
[2, 5],
[5, 2],
[5, 5]])
>>> square2 = np.zeros([9, 9], dtype=np.int32)
>>> square2[2:7, 2:7] = 1
>>> square2
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
>>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
>>> keypoints2
array([[2, 2],
[2, 6],
[6, 2],
[6, 6]])
>>> descriptors2, keypoints2 = brief(square2, keypoints2, patch_size=5)
>>> keypoints2
array([[2, 2],
[2, 6],
[6, 2],
[6, 6]])
>>> pairwise_hamming_distance(descriptors1, descriptors2)
array([[ 0.03125 , 0.3203125, 0.3671875, 0.6171875],
[ 0.3203125, 0.03125 , 0.640625 , 0.375 ],
[ 0.375 , 0.6328125, 0.0390625, 0.328125 ],
[ 0.625 , 0.3671875, 0.34375 , 0.0234375]])
>>> match_keypoints_brief(keypoints1, descriptors1, keypoints2, descriptors2)
array([[[ 2, 2],
[ 2, 2]],
[[ 2, 5],
[ 2, 6]],
[[ 5, 2],
[ 6, 2]],
[[ 5, 5],
[ 6, 6]]])
"""
np.random.seed(sample_seed)
image = np.squeeze(image)
if image.ndim != 2:
raise ValueError("Only 2-D gray-scale images supported.")
image = img_as_float(image)
# Gaussian Low pass filtering to alleviate noise
# sensitivity
image = gaussian_filter(image, variance)
image = np.ascontiguousarray(image)
keypoints = np.array(keypoints + 0.5, dtype=np.intp, order='C')
# Removing keypoints that are within (patch_size / 2) distance from the
# image border
keypoints = keypoints[_mask_border_keypoints(image, keypoints, patch_size // 2)]
keypoints = np.ascontiguousarray(keypoints)
descriptors = np.zeros((keypoints.shape[0], descriptor_size), dtype=bool,
order='C')
# Sampling pairs of decision pixels in patch_size x patch_size window
if mode == 'normal':
samples = (patch_size / 5.0) * np.random.randn(descriptor_size * 8)
samples = np.array(samples, dtype=np.int32)
samples = samples[(samples < (patch_size // 2))
& (samples > - (patch_size - 2) // 2)]
pos1 = samples[:descriptor_size * 2]
pos1 = pos1.reshape(descriptor_size, 2)
pos2 = samples[descriptor_size * 2:descriptor_size * 4]
pos2 = pos2.reshape(descriptor_size, 2)
else:
samples = np.random.randint(-(patch_size - 2) // 2,
(patch_size // 2) + 1,
(descriptor_size * 2, 2))
pos1, pos2 = np.split(samples, 2)
pos1 = np.ascontiguousarray(pos1)
pos2 = np.ascontiguousarray(pos2)
_brief_loop(image, descriptors.view(np.uint8), keypoints, pos1, pos2)
return descriptors, keypoints
def match_keypoints_brief(keypoints1, descriptors1, keypoints2,
descriptors2, threshold=0.15):
"""**Experimental function**.
Match keypoints described using BRIEF descriptors in one image to
those in second image.
Parameters
----------
keypoints1 : (M, 2) ndarray
M Keypoints from the first image described using skimage.feature.brief
descriptors1 : (M, P) ndarray
BRIEF descriptors of size P about M keypoints in the first image.
keypoints2 : (N, 2) ndarray
N Keypoints from the second image described using skimage.feature.brief
descriptors2 : (N, P) ndarray
BRIEF descriptors of size P about N keypoints in the second image.
threshold : float in range [0, 1]
Maximum allowable hamming distance between descriptors of two keypoints
in separate images to be regarded as a match. Default is 0.15.
Returns
-------
match_keypoints_brief : (Q, 2, 2) ndarray
Location of Q matched keypoint pairs from two images.
"""
if (keypoints1.shape[0] != descriptors1.shape[0]
or keypoints2.shape[0] != descriptors2.shape[0]):
raise ValueError("The number of keypoints and number of described "
"keypoints do not match. Make the optional parameter "
"return_keypoints True to get described keypoints.")
if descriptors1.shape[1] != descriptors2.shape[1]:
raise ValueError("Descriptor sizes for matching keypoints in both "
"the images should be equal.")
# Get hamming distances between keeypoints1 and keypoints2
distance = pairwise_hamming_distance(descriptors1, descriptors2)
temp = distance > threshold
row_check = np.any(~temp, axis=1)
matched_keypoints2 = keypoints2[np.argmin(distance, axis=1)]
matched_keypoint_pairs = np.zeros((np.sum(row_check), 2, 2), dtype=np.intp)
matched_keypoint_pairs[:, 0, :] = keypoints1[row_check]
matched_keypoint_pairs[:, 1, :] = matched_keypoints2[row_check]
return matched_keypoint_pairs
+24
View File
@@ -0,0 +1,24 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
def _brief_loop(double[:, ::1] image, char[:, ::1] descriptors,
Py_ssize_t[:, ::1] keypoints,
int[:, ::1] pos0, int[:, ::1] pos1):
cdef Py_ssize_t k, d, kr, kc, pr0, pr1, pc0, pc1
for p in range(pos0.shape[0]):
pr0 = pos0[p, 0]
pc0 = pos0[p, 1]
pr1 = pos1[p, 0]
pc1 = pos1[p, 1]
for k in range(keypoints.shape[0]):
kr = keypoints[k, 0]
kc = keypoints[k, 1]
if image[kr + pr0, kc + pc0] < image[kr + pr1, kc + pc1]:
descriptors[k, p] = True
+2 -2
View File
@@ -117,9 +117,9 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
#create new integral image for this orientation
# isolate orientations in this range
temp_ori = np.where(orientation < 180 / orientations * (i + 1),
temp_ori = np.where(orientation < 180.0 / orientations * (i + 1),
orientation, -1)
temp_ori = np.where(orientation >= 180 / orientations * i,
temp_ori = np.where(orientation >= 180.0 / orientations * i,
temp_ori, -1)
# select magnitudes for those orientations
cond2 = temp_ori > -1
+4 -4
View File
@@ -50,9 +50,9 @@ from skimage.transform import integral
def match_template(cnp.ndarray[float, ndim=2, mode="c"] image,
cnp.ndarray[float, ndim=2, mode="c"] template):
cdef cnp.ndarray[float, ndim=2, mode="c"] corr
cdef cnp.ndarray[float, ndim=2, mode="c"] image_sat
cdef cnp.ndarray[float, ndim=2, mode="c"] image_sqr_sat
cdef float[:, ::1] corr
cdef float[:, ::1] image_sat
cdef float[:, ::1] image_sqr_sat
cdef float template_mean = np.mean(template)
cdef float template_ssd
cdef float inv_area
@@ -94,4 +94,4 @@ def match_template(cnp.ndarray[float, ndim=2, mode="c"] image,
den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd)
corr[r, c] /= den
return corr
return np.asarray(corr)
+96 -37
View File
@@ -8,15 +8,9 @@ from libc.math cimport sin, cos, abs
from skimage._shared.interpolation cimport bilinear_interpolation
def _glcm_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] image,
cnp.ndarray[dtype=cnp.float64_t, ndim=1,
negative_indices=False, mode='c'] distances,
cnp.ndarray[dtype=cnp.float64_t, ndim=1,
negative_indices=False, mode='c'] angles,
int levels,
cnp.ndarray[dtype=cnp.uint32_t, ndim=4,
negative_indices=False, mode='c'] out):
def _glcm_loop(cnp.uint8_t[:, ::1] image, double[:] distances,
double[:] angles, Py_ssize_t levels,
cnp.uint32_t[:, :, :, ::1] out):
"""Perform co-occurrence matrix accumulation.
Parameters
@@ -81,7 +75,7 @@ cdef inline int _bit_rotate_right(int value, int length):
return (value >> 1) | ((value & 1) << (length - 1))
def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
def _local_binary_pattern(double[:, ::1] image,
int P, float R, char method='D'):
"""Gray scale and rotation invariant LBP (Local Binary Patterns).
@@ -92,16 +86,17 @@ def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
image : (N, M) double array
Graylevel image.
P : int
Number of circularly symmetric neighbour set points (quantization of the
angular space).
Number of circularly symmetric neighbour set points (quantization of
the angular space).
R : float
Radius of circle (spatial resolution of the operator).
method : {'D', 'R', 'U', 'V'}
method : {'D', 'R', 'U', 'N', 'V'}
Method to determine the pattern.
* 'D': 'default'
* 'R': 'ror'
* 'U': 'uniform'
* 'N': 'nri_uniform'
* 'V': 'var'
Returns
@@ -111,30 +106,35 @@ def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
"""
# texture weights
cdef cnp.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32)
cdef int[:] weights = 2 ** np.arange(P, dtype=np.int32)
# local position of texture elements
rp = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.double) / P)
cp = R * np.cos(2 * np.pi * np.arange(P, dtype=np.double) / P)
cdef cnp.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5)
rr = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.double) / P)
cc = R * np.cos(2 * np.pi * np.arange(P, dtype=np.double) / P)
cdef double[:] rp = np.round(rr, 5)
cdef double[:] cp = np.round(cc, 5)
# pre allocate arrays for computation
cdef cnp.ndarray[double, ndim=1] texture = np.zeros(P, np.double)
cdef cnp.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8)
cdef cnp.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32)
# pre-allocate arrays for computation
cdef double[:] texture = np.zeros(P, dtype=np.double)
cdef char[:] signed_texture = np.zeros(P, dtype=np.int8)
cdef int[:] rotation_chain = np.zeros(P, dtype=np.int32)
output_shape = (image.shape[0], image.shape[1])
cdef cnp.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double)
cdef double[:, ::1] output = np.zeros(output_shape, dtype=np.double)
cdef Py_ssize_t rows = image.shape[0]
cdef Py_ssize_t cols = image.shape[1]
cdef double lbp
cdef Py_ssize_t r, c, changes, i
cdef Py_ssize_t rot_index, n_ones
cdef cnp.int8_t first_zero, first_one
for r in range(image.shape[0]):
for c in range(image.shape[1]):
for i in range(P):
texture[i] = bilinear_interpolation(<double*>image.data,
rows, cols, r + coords[i, 0], c + coords[i, 1], 'C', 0)
texture[i] = bilinear_interpolation(&image[0, 0], rows, cols,
r + rp[i], c + cp[i],
'C', 0)
# signed / thresholded texture
for i in range(P):
if texture[i] - image[r, c] >= 0:
@@ -145,24 +145,83 @@ def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
lbp = 0
# if method == 'uniform' or method == 'var':
if method == 'U' or method == 'V':
if method == 'U' or method == 'N' or method == 'V':
# determine number of 0 - 1 changes
changes = 0
for i in range(P - 1):
changes += abs(signed_texture[i] - signed_texture[i + 1])
if method == 'N':
# Uniform local binary patterns are defined as patterns
# with at most 2 value changes (from 0 to 1 or from 1 to
# 0). Uniform patterns can be caraterized by their number
# `n_ones` of 1. The possible values for `n_ones` range
# from 0 to P.
# Here is an example for P = 4:
# n_ones=0: 0000
# n_ones=1: 0001, 1000, 0100, 0010
# n_ones=2: 0011, 1001, 1100, 0110
# n_ones=3: 0111, 1011, 1101, 1110
# n_ones=4: 1111
#
# For a pattern of size P there are 2 constant patterns
# corresponding to n_ones=0 and n_ones=P. For each other
# value of `n_ones` , i.e n_ones=[1..P-1], there are P
# possible patterns which are related to each other through
# circular permutations. The total number of uniform
# patterns is thus (2 + P * (P - 1)).
# Given any pattern (uniform or not) we must be able to
# associate a unique code:
# 1. Constant patterns patterns (with n_ones=0 and
# n_ones=P) and non uniform patterns are given fixed
# code values.
# 2. Other uniform patterns are indexed considering the
# value of n_ones, and an index called 'rot_index'
# reprenting the number of circular right shifts
# required to obtain the pattern starting from a
# reference position (corresponding to all zeros stacked
# on the right). This number of rotations (or circular
# right shifts) 'rot_index' is efficiently computed by
# considering the positions of the first 1 and the first
# 0 found in the pattern.
if changes <= 2:
for i in range(P):
lbp += signed_texture[i]
else:
lbp = P + 1
if method == 'V':
var = np.var(texture)
if var != 0:
lbp /= var
if changes <= 2:
# We have a uniform pattern
n_ones = 0 # determies the number of ones
first_one = -1 # position was the first one
first_zero = -1 # position of the first zero
for i in range(P):
if signed_texture[i]:
n_ones += 1
if first_one == -1:
first_one = i
else:
if first_zero == -1:
first_zero = i
if n_ones == 0:
lbp = 0
elif n_ones == P:
lbp = P * (P - 1) + 1
else:
if first_one == 0:
rot_index = n_ones - first_zero
else:
rot_index = P - first_one
lbp = 1 + (n_ones - 1) * P + rot_index
else: # changes > 2
lbp = P * (P - 1) + 2
else: # method != 'N'
if changes <= 2:
for i in range(P):
lbp += signed_texture[i]
else:
lbp = np.nan
lbp = P + 1
if method == 'V':
var = np.var(texture)
if var != 0:
lbp /= var
else:
lbp = np.nan
else:
# method == 'default'
for i in range(P):
@@ -181,4 +240,4 @@ def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
output[r, c] = lbp
return output
return np.asarray(output)
+234
View File
@@ -0,0 +1,234 @@
import numpy as np
from scipy.ndimage.filters import maximum_filter, minimum_filter, convolve
from skimage.transform import integral_image
from skimage.feature.corner import _compute_auto_correlation
from skimage.util import img_as_float
from skimage.morphology import octagon, star
from skimage.feature.util import _mask_border_keypoints
from skimage.feature.censure_cy import _censure_dob_loop
# The paper(Reference [1]) mentions the sizes of the Octagon shaped filter
# kernel for the first seven scales only. The sizes of the later scales
# have been extrapolated based on the following statement in the paper.
# "These octagons scale linearly and were experimentally chosen to correspond
# to the seven DOBs described in the previous section."
OCTAGON_OUTER_SHAPE = [(5, 2), (5, 3), (7, 3), (9, 4), (9, 7), (13, 7),
(15, 10), (15, 11), (15, 12), (17, 13), (17, 14)]
OCTAGON_INNER_SHAPE = [(3, 0), (3, 1), (3, 2), (5, 2), (5, 3), (5, 4), (5, 5),
(7, 5), (7, 6), (9, 6), (9, 7)]
# The sizes for the STAR shaped filter kernel for different scales have been
# taken from the OpenCV implementation.
STAR_SHAPE = [1, 2, 3, 4, 6, 8, 11, 12, 16, 22, 23, 32, 45, 46, 64, 90, 128]
STAR_FILTER_SHAPE = [(1, 0), (3, 1), (4, 2), (5, 3), (7, 4), (8, 5),
(9, 6), (11, 8), (13, 10), (14, 11), (15, 12), (16, 14)]
def _filter_image(image, min_scale, max_scale, mode):
response = np.zeros((image.shape[0], image.shape[1],
max_scale - min_scale + 1), dtype=np.double)
if mode == 'dob':
# make response[:, :, i] contiguous memory block
item_size = response.itemsize
response.strides = (item_size * response.shape[0], item_size,
item_size * response.shape[0] * response.shape[1])
integral_img = integral_image(image)
for i in range(max_scale - min_scale + 1):
n = min_scale + i
# Constant multipliers for the outer region and the inner region
# of the bi-level filters with the constraint of keeping the
# DC bias 0.
inner_weight = (1.0 / (2 * n + 1)**2)
outer_weight = (1.0 / (12 * n**2 + 4 * n))
_censure_dob_loop(n, integral_img, response[:, :, i],
inner_weight, outer_weight)
# NOTE : For the Octagon shaped filter, we implemented and evaluated the
# slanted integral image based image filtering but the performance was
# more or less equal to image filtering using
# scipy.ndimage.filters.convolve(). Hence we have decided to use the
# later for a much cleaner implementation.
elif mode == 'octagon':
# TODO : Decide the shapes of Octagon filters for scales > 7
for i in range(max_scale - min_scale + 1):
mo, no = OCTAGON_OUTER_SHAPE[min_scale + i - 1]
mi, ni = OCTAGON_INNER_SHAPE[min_scale + i - 1]
response[:, :, i] = convolve(image,
_octagon_filter_kernel(mo, no, mi, ni))
elif mode == 'star':
for i in range(max_scale - min_scale + 1):
m = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][0]]
n = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][1]]
response[:, :, i] = convolve(image, _star_filter_kernel(m, n))
return response
def _octagon_filter_kernel(mo, no, mi, ni):
outer = (mo + 2 * no)**2 - 2 * no * (no + 1)
inner = (mi + 2 * ni)**2 - 2 * ni * (ni + 1)
outer_weight = 1.0 / (outer - inner)
inner_weight = 1.0 / inner
c = ((mo + 2 * no) - (mi + 2 * ni)) // 2
outer_oct = octagon(mo, no)
inner_oct = np.zeros((mo + 2 * no, mo + 2 * no))
inner_oct[c: -c, c: -c] = octagon(mi, ni)
bfilter = (outer_weight * outer_oct -
(outer_weight + inner_weight) * inner_oct)
return bfilter
def _star_filter_kernel(m, n):
c = m + m // 2 - n - n // 2
outer_star = star(m)
inner_star = np.zeros_like(outer_star)
inner_star[c: -c, c: -c] = star(n)
outer_weight = 1.0 / (np.sum(outer_star - inner_star))
inner_weight = 1.0 / np.sum(inner_star)
bfilter = (outer_weight * outer_star -
(outer_weight + inner_weight) * inner_star)
return bfilter
def _suppress_lines(feature_mask, image, sigma, line_threshold):
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
feature_mask[(Axx + Ayy) * (Axx + Ayy)
> line_threshold * (Axx * Ayy - Axy * Axy)] = False
def keypoints_censure(image, min_scale=1, max_scale=7, mode='DoB',
non_max_threshold=0.15, line_threshold=10):
"""**Experimental function**.
Extracts CenSurE keypoints along with the corresponding scale using
either Difference of Boxes, Octagon or STAR bi-level filter.
Parameters
----------
image : 2D ndarray
Input image.
min_scale : int
Minimum scale to extract keypoints from.
max_scale : int
Maximum scale to extract keypoints from. The keypoints will be
extracted from all the scales except the first and the last i.e.
from the scales in the range [min_scale + 1, max_scale - 1].
mode : {'DoB', 'Octagon', 'STAR'}
Type of bi-level filter used to get the scales of the input image.
Possible values are 'DoB', 'Octagon' and 'STAR'. The three modes
represent the shape of the bi-level filters i.e. box(square), octagon
and star respectively. For instance, a bi-level octagon filter consists
of a smaller inner octagon and a larger outer octagon with the filter
weights being uniformly negative in both the inner octagon while
uniformly positive in the difference region. Use STAR and Octagon for
better features and DoB for better performance.
non_max_threshold : float
Threshold value used to suppress maximas and minimas with a weak
magnitude response obtained after Non-Maximal Suppression.
line_threshold : float
Threshold for rejecting interest points which have ratio of principal
curvatures greater than this value.
Returns
-------
keypoints : (N, 2) array
Location of the extracted keypoints in the ``(row, col)`` format.
scales : (N, 1) array
The corresponding scale of the N extracted keypoints.
References
----------
.. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas
"CenSurE: Center Surround Extremas for Realtime Feature
Detection and Matching",
http://link.springer.com/content/pdf/10.1007%2F978-3-540-88693-8_8.pdf
.. [2] Adam Schmidt, Marek Kraft, Michal Fularz and Zuzanna Domagala
"Comparative Assessment of Point Feature Detectors and
Descriptors in the Context of Robot Navigation"
http://www.jamris.org/01_2013/saveas.php?QUEST=JAMRIS_No01_2013_P_11-20.pdf
"""
# (1) First we generate the required scales on the input grayscale image
# using a bi-level filter and stack them up in `filter_response`.
# (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on the
# filter_response to suppress points that are neither minima or maxima in
# 3 x 3 x 3 neighbourhood. We obtain a boolean ndarray `feature_mask`
# containing all the minimas and maximas in `filter_response` as True.
# (3) Then we suppress all the points in the `feature_mask` for which the
# corresponding point in the image at a particular scale has the ratio of
# principal curvatures greater than `line_threshold`.
# (4) Finally, we remove the border keypoints and return the keypoints
# along with its corresponding scale.
image = np.squeeze(image)
if image.ndim != 2:
raise ValueError("Only 2-D gray-scale images supported.")
mode = mode.lower()
if mode not in ('dob', 'octagon', 'star'):
raise ValueError('Mode must be one of "DoB", "Octagon", "STAR".')
if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2:
raise ValueError('The scales must be >= 1 and the number of scales '
'should be >= 3.')
image = img_as_float(image)
image = np.ascontiguousarray(image)
# Generating all the scales
filter_response = _filter_image(image, min_scale, max_scale, mode)
# Suppressing points that are neither minima or maxima in their 3 x 3 x 3
# neighbourhood to zero
minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response
maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response
feature_mask = minimas | maximas
feature_mask[filter_response < non_max_threshold] = False
for i in range(1, max_scale - min_scale):
# sigma = (window_size - 1) / 6.0, so the window covers > 99% of the
# kernel's distribution
# window_size = 7 + 2 * (min_scale - 1 + i)
# Hence sigma = 1 + (min_scale - 1 + i)/ 3.0
_suppress_lines(feature_mask[:, :, i], image,
(1 + (min_scale + i - 1) / 3.0), line_threshold)
rows, cols, scales = np.nonzero(feature_mask[..., 1:max_scale - min_scale])
keypoints = np.column_stack([rows, cols])
scales = scales + min_scale + 1
if mode == 'dob':
return keypoints, scales
cumulative_mask = np.zeros(keypoints.shape[0], dtype=np.bool)
if mode == 'octagon':
for i in range(min_scale + 1, max_scale):
c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 \
+ OCTAGON_OUTER_SHAPE[i - 1][1]
cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \
& (scales == i)
elif mode == 'star':
for i in range(min_scale + 1, max_scale):
c = STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] \
+ STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2
cumulative_mask |= _mask_border_keypoints(image, keypoints, c) \
& (scales == i)
return keypoints[cumulative_mask], scales[cumulative_mask]
+72
View File
@@ -0,0 +1,72 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
def _censure_dob_loop(Py_ssize_t n,
double[:, ::1] integral_img,
double[:, ::1] filtered_image,
double inner_weight, double outer_weight):
# This function calculates the value in the DoB filtered image using
# integral images. If r = right. l = left, u = up, d = down, the sum of
# pixel values in the rectangle formed by (u, l), (u, r), (d, r), (d, l)
# is calculated as I(d, r) + I(u - 1, l - 1) - I(u - 1, r) - I(d, l - 1).
cdef Py_ssize_t i, j
cdef double inner, outer
cdef Py_ssize_t n2 = 2 * n
cdef double total_weight = inner_weight + outer_weight
# top-left pixel
inner = (integral_img[n2 + n, n2 + n]
+ integral_img[n2 - n - 1, n2 - n - 1]
- integral_img[n2 + n, n2 - n - 1]
- integral_img[n2 - n - 1, n2 + n])
outer = integral_img[2 * n2, 2 * n2]
filtered_image[n2, n2] = (outer_weight * outer
- total_weight * inner)
# left column
for i in range(n2 + 1, integral_img.shape[0] - n2):
inner = (integral_img[i + n, n2 + n]
+ integral_img[i - n - 1, n2 - n - 1]
- integral_img[i + n, n2 - n - 1]
- integral_img[i - n - 1, n2 + n])
outer = (integral_img[i + n2, 2 * n2]
- integral_img[i - n2 - 1, 2 * n2])
filtered_image[i, n2] = (outer_weight * outer
- total_weight * inner)
# top row
for j in range(n2 + 1, integral_img.shape[1] - n2):
inner = (integral_img[n2 + n, j + n]
+ integral_img[n2 - n - 1, j - n - 1]
- integral_img[n2 + n, j - n - 1]
- integral_img[n2 - n - 1, j + n])
outer = (integral_img[2 * n2, j + n2]
- integral_img[2 * n2, j - n2 - 1])
filtered_image[n2, j] = (outer_weight * outer
- total_weight * inner)
# remaining block
for i in range(n2 + 1, integral_img.shape[0] - n2):
for j in range(n2 + 1, integral_img.shape[1] - n2):
inner = (integral_img[i + n, j + n]
+ integral_img[i - n - 1, j - n - 1]
- integral_img[i + n, j - n - 1]
- integral_img[i - n - 1, j + n])
outer = (integral_img[i + n2, j + n2]
+ integral_img[i - n2 - 1, j - n2 - 1]
- integral_img[i + n2, j - n2 - 1]
- integral_img[i - n2 - 1, j + n2])
filtered_image[i, j] = (outer_weight * outer
- total_weight * inner)
+1 -2
View File
@@ -277,8 +277,7 @@ def corner_foerstner(image, sigma=1):
References
----------
.. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\
foerstner87.fast.pdf
.. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/foerstner87.fast.pdf
.. [2] http://en.wikipedia.org/wiki/Corner_detection
Examples
+6 -15
View File
@@ -59,16 +59,8 @@ def corner_moravec(image, Py_ssize_t window_size=1):
cdef Py_ssize_t rows = image.shape[0]
cdef Py_ssize_t cols = image.shape[1]
cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode='c'] cimage, out
if image.ndim == 3:
cimage = rgb2grey(image)
cimage = np.ascontiguousarray(img_as_float(image))
out = np.zeros(image.shape, dtype=np.double)
cdef double* image_data = <double*>cimage.data
cdef double* out_data = <double*>out.data
cdef double[:, ::1] cimage = np.ascontiguousarray(img_as_float(image))
cdef double[:, ::1] out = np.zeros(image.shape, dtype=np.double)
cdef double msum, min_msum
cdef Py_ssize_t r, c, br, bc, mr, mc, a, b
@@ -81,11 +73,10 @@ def corner_moravec(image, Py_ssize_t window_size=1):
msum = 0
for mr in range(- window_size, window_size + 1):
for mc in range(- window_size, window_size + 1):
a = (r + mr) * cols + c + mc
b = (br + mr) * cols + bc + mc
msum += (image_data[a] - image_data[b]) ** 2
msum += (cimage[r + mr, c + mc]
- cimage[br + mr, bc + mc]) ** 2
min_msum = min(msum, min_msum)
out_data[r * cols + c] = min_msum
out[r, c] = min_msum
return out
return np.asarray(out)
+6
View File
@@ -13,11 +13,17 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('tests')
cython(['corner_cy.pyx'], working_path=base_path)
cython(['censure_cy.pyx'], working_path=base_path)
cython(['_brief_cy.pyx'], working_path=base_path)
cython(['_texture.pyx'], working_path=base_path)
cython(['_template.pyx'], working_path=base_path)
config.add_extension('corner_cy', sources=['corner_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('censure_cy', sources=['censure_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_brief_cy', sources=['_brief_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_texture', sources=['_texture.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('_template', sources=['_template.c'],
+83
View File
@@ -0,0 +1,83 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage import data
from skimage import transform as tf
from skimage.color import rgb2gray
from skimage.feature import (brief, match_keypoints_brief, corner_peaks,
corner_harris)
def test_brief_color_image_unsupported_error():
"""Brief descriptors can be evaluated on gray-scale images only."""
img = np.zeros((20, 20, 3))
keypoints = [[7, 5], [11, 13]]
assert_raises(ValueError, brief, img, keypoints)
def test_match_keypoints_brief_lena_translation():
"""Test matched keypoints between lena image and its translated version."""
img = data.lena()
img = rgb2gray(img)
img.shape
tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(15, 20))
translated_img = tf.warp(img, tform)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512)
keypoints2 = corner_peaks(corner_harris(translated_img), min_distance=5)
descriptors2, keypoints2 = brief(translated_img, keypoints2,
descriptor_size=512)
matched_keypoints = match_keypoints_brief(keypoints1, descriptors1,
keypoints2, descriptors2,
threshold=0.10)
assert_array_equal(matched_keypoints[:, 0, :], matched_keypoints[:, 1, :] +
[20, 15])
def test_match_keypoints_brief_lena_rotation():
"""Verify matched keypoints result between lena image and its rotated
version with the expected keypoint pairs."""
img = data.lena()
img = rgb2gray(img)
img.shape
tform = tf.SimilarityTransform(scale=1, rotation=0.10, translation=(0, 0))
rotated_img = tf.warp(img, tform)
keypoints1 = corner_peaks(corner_harris(img), min_distance=5)
descriptors1, keypoints1 = brief(img, keypoints1, descriptor_size=512)
keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5)
descriptors2, keypoints2 = brief(rotated_img, keypoints2,
descriptor_size=512)
matched_keypoints = match_keypoints_brief(keypoints1, descriptors1,
keypoints2, descriptors2,
threshold=0.07)
expected = np.array([[[263, 272],
[234, 298]],
[[271, 120],
[258, 146]],
[[323, 164],
[305, 195]],
[[414, 70],
[405, 111]],
[[435, 181],
[415, 223]],
[[454, 176],
[435, 221]]])
assert_array_equal(matched_keypoints, expected)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+89
View File
@@ -0,0 +1,89 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_raises
from skimage.data import moon
from skimage.feature import keypoints_censure
def test_keypoints_censure_color_image_unsupported_error():
"""Censure keypoints can be extracted from gray-scale images only."""
img = np.zeros((20, 20, 3))
assert_raises(ValueError, keypoints_censure, img)
def test_keypoints_censure_mode_validity_error():
"""Mode argument in keypoints_censure can be either DoB, Octagon or
STAR."""
img = np.zeros((20, 20))
assert_raises(ValueError, keypoints_censure, img, mode='dummy')
def test_keypoints_censure_scale_range_error():
"""Difference between the the max_scale and min_scale parameters in
keypoints_censure should be greater than or equal to two."""
img = np.zeros((20, 20))
assert_raises(ValueError, keypoints_censure, img, min_scale=1, max_scale=2)
def test_keypoints_censure_moon_image_dob():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for DoB filter."""
img = moon()
actual_kp_dob, actual_scale = keypoints_censure(img, 1, 7, 'DoB', 0.15)
expected_kp_dob = np.array([[ 21, 497],
[ 36, 46],
[119, 350],
[185, 177],
[287, 250],
[357, 239],
[463, 116],
[464, 132],
[467, 260]])
expected_scale = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2])
assert_array_equal(expected_kp_dob, actual_kp_dob)
assert_array_equal(expected_scale, actual_scale)
def test_keypoints_censure_moon_image_octagon():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for Octagon filter."""
img = moon()
actual_kp_octagon, actual_scale = keypoints_censure(img, 1, 7, 'Octagon',
0.15)
expected_kp_octagon = np.array([[ 21, 496],
[ 35, 46],
[287, 250],
[356, 239],
[463, 116]])
expected_scale = np.array([3, 4, 2, 2, 2])
assert_array_equal(expected_kp_octagon, actual_kp_octagon)
assert_array_equal(expected_scale, actual_scale)
def test_keypoints_censure_moon_image_star():
"""Verify the actual Censure keypoints and their corresponding scale with
the expected values for STAR filter."""
img = moon()
actual_kp_star, actual_scale = keypoints_censure(img, 1, 7, 'STAR', 0.15)
expected_kp_star = np.array([[ 21, 497],
[ 36, 46],
[117, 356],
[185, 177],
[260, 227],
[287, 250],
[357, 239],
[451, 281],
[463, 116],
[467, 260]])
expected_scale = np.array([3, 3, 6, 2, 3, 2, 3, 5, 2, 2])
assert_array_equal(expected_kp_star, actual_kp_star)
assert_array_equal(expected_scale, actual_scale)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+11
View File
@@ -199,5 +199,16 @@ class TestLBP():
np.testing.assert_array_almost_equal(lbp, ref)
def test_nri_uniform(self):
lbp = local_binary_pattern(self.image, 8, 1, 'nri_uniform')
ref = np.array([[ 0, 54, 0, 57, 12, 57],
[34, 0, 58, 58, 3, 22],
[58, 57, 15, 50, 0, 47],
[10, 3, 40, 42, 35, 0],
[57, 7, 57, 58, 0, 56],
[ 9, 58, 0, 57, 7, 14]])
np.testing.assert_array_almost_equal(lbp, ref)
if __name__ == '__main__':
np.testing.run_module_suite()
+32
View File
@@ -0,0 +1,32 @@
import numpy as np
from numpy.testing import assert_array_equal
from skimage.feature.util import pairwise_hamming_distance
def test_pairwise_hamming_distance_range():
"""Values of all the pairwise hamming distances should be in the range
[0, 1]."""
a = np.random.random_sample((10, 50)) > 0.5
b = np.random.random_sample((20, 50)) > 0.5
dist = pairwise_hamming_distance(a, b)
assert np.all((0 <= dist) & (dist <= 1))
def test_pairwise_hamming_distance_value():
"""The result of pairwise_hamming_distance of two fixed sets of boolean
vectors should be same as expected."""
np.random.seed(10)
a = np.random.random_sample((4, 100)) > 0.5
np.random.seed(20)
b = np.random.random_sample((3, 100)) > 0.5
result = pairwise_hamming_distance(a, b)
expected = np.array([[0.5 , 0.49, 0.44],
[0.44, 0.53, 0.52],
[0.4 , 0.55, 0.5 ],
[0.47, 0.48, 0.57]])
assert_array_equal(result, expected)
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+8 -1
View File
@@ -248,6 +248,8 @@ def local_binary_pattern(image, P, R, method='default'):
* 'uniform': improved rotation invariance with uniform patterns and
finer quantization of the angular space which is gray scale and
rotation invariant.
* 'nri_uniform': non rotation-invariant uniform patterns variant
which is only gray scale invariant [2].
* 'var': rotation invariant variance measures of the contrast of local
image texture which is rotation but not gray scale invariant.
@@ -263,14 +265,19 @@ def local_binary_pattern(image, P, R, method='default'):
Timo Ojala, Matti Pietikainen, Topi Maenpaa.
http://www.rafbis.it/biplab15/images/stories/docenti/Danielriccio/\
Articoliriferimento/LBP.pdf, 2002.
.. [2] Face recognition with local binary patterns.
Timo Ahonen, Abdenour Hadid, Matti Pietikainen,
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.214.6851,
2004.
"""
methods = {
'default': ord('D'),
'ror': ord('R'),
'uniform': ord('U'),
'nri_uniform': ord('N'),
'var': ord('V')
}
image = np.array(image, dtype='double', copy=True)
image = np.ascontiguousarray(image, dtype=np.double)
output = _local_binary_pattern(image, P, R, methods[method.lower()])
return output
+38
View File
@@ -0,0 +1,38 @@
def _mask_border_keypoints(image, keypoints, dist):
"""Removes keypoints that are within dist pixels from the image border."""
width = image.shape[0]
height = image.shape[1]
keypoints_filtering_mask = ((dist - 1 < keypoints[:, 0]) &
(keypoints[:, 0] < width - dist + 1) &
(dist - 1 < keypoints[:, 1]) &
(keypoints[:, 1] < height - dist + 1))
return keypoints_filtering_mask
def pairwise_hamming_distance(array1, array2):
"""**Experimental function**.
Calculate hamming dissimilarity measure between two sets of
vectors.
Parameters
----------
array1 : (P1, D) array
P1 vectors of size D.
array2 : (P2, D) array
P2 vectors of size D.
Returns
-------
distance : (P1, P2) array of dtype float
2D ndarray with value at an index (i, j) representing the hamming
distance in the range [0, 1] between ith vector in array1 and jth
vector in array2.
"""
distance = (array1[:, None] != array2[None]).mean(axis=2)
return distance
+3 -1
View File
@@ -1,8 +1,9 @@
from .lpi_filter import inverse, wiener, LPIFilter2D
from .ctmf import median_filter
from ._gaussian import gaussian_filter
from ._canny import canny
from .edges import (sobel, hsobel, vsobel, scharr, hscharr, vscharr, prewitt,
hprewitt, vprewitt, roberts , roberts_positive_diagonal,
hprewitt, vprewitt, roberts, roberts_positive_diagonal,
roberts_negative_diagonal)
from ._denoise import denoise_tv_chambolle, tv_denoise
from ._denoise_cy import denoise_bilateral, denoise_tv_bregman
@@ -16,6 +17,7 @@ __all__ = ['inverse',
'wiener',
'LPIFilter2D',
'median_filter',
'gaussian_filter',
'canny',
'sobel',
'hsobel',
+29 -27
View File
@@ -1,4 +1,5 @@
'''canny.py - Canny Edge detector
"""
canny.py - Canny Edge detector
Reference: Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
Pattern Analysis and Machine Intelligence, 8:679-714, 1986
@@ -9,13 +10,13 @@ Copyright (c) 2003-2009 Massachusetts Institute of Technology
Copyright (c) 2009-2011 Broad Institute
All rights reserved.
Original author: Lee Kamentsky
'''
"""
import numpy as np
import scipy.ndimage as ndi
from scipy.ndimage import (gaussian_filter,
generate_binary_structure, binary_erosion, label)
from skimage import dtype_limits
def smooth_with_function_and_mask(image, function, mask):
@@ -24,13 +25,11 @@ def smooth_with_function_and_mask(image, function, mask):
Parameters
----------
image : array
The image to smooth
Image you want to smooth.
function : callable
A function that takes an image and returns a smoothed image
A function that does image smoothing.
mask : array
Mask with 1's for significant pixels, 0 for masked pixels
Mask with 1's for significant pixels, 0's for masked pixels.
Notes
------
@@ -50,31 +49,28 @@ def smooth_with_function_and_mask(image, function, mask):
return output_image
def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
'''Edge filter an image using the Canny algorithm.
def canny(image, sigma=1., low_threshold=None, high_threshold=None, mask=None):
"""Edge filter an image using the Canny algorithm.
Parameters
-----------
image : array_like, dtype=float
The greyscale input image to detect edges on; should be normalized to
0.0 to 1.0.
image : 2D array
Greyscale input image to detect edges on; can be of any dtype.
sigma : float
The standard deviation of the Gaussian filter
Standard deviation of the Gaussian filter.
low_threshold : float
The lower bound for hysterisis thresholding (linking edges)
Lower bound for hysteresis thresholding (linking edges).
If None, low_threshold is set to 10% of dtype's max.
high_threshold : float
The upper bound for hysterisis thresholding (linking edges)
Upper bound for hysteresis thresholding (linking edges).
If None, high_threshold is set to 20% of dtype's max.
mask : array, dtype=bool, optional
An optional mask to limit the application of Canny to a certain area.
Mask to limit the application of Canny to a certain area.
Returns
-------
output : array (image)
The binary edge map.
output : 2D array (image)
The binary edge map.
See also
--------
@@ -107,7 +103,7 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
Pattern Analysis and Machine Intelligence, 8:679-714, 1986
William Green' Canny tutorial
William Green's Canny tutorial
http://dasl.mem.drexel.edu/alumni/bGreen/www.pages.drexel.edu/_weg22/can_tut.html
Examples
@@ -116,12 +112,12 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
>>> # Generate noisy image of a square
>>> im = np.zeros((256, 256))
>>> im[64:-64, 64:-64] = 1
>>> im += 0.2*np.random.random(im.shape)
>>> im += 0.2 * np.random.random(im.shape)
>>> # First trial with the Canny filter, with the default smoothing
>>> edges1 = filter.canny(im)
>>> # Increase the smoothing for better results
>>> edges2 = filter.canny(im, sigma=3)
'''
"""
#
# The steps involved:
@@ -154,7 +150,13 @@ def canny(image, sigma=1., low_threshold=.1, high_threshold=.2, mask=None):
#
if image.ndim != 2:
raise TypeError("The input 'image' must be a two dimensional array.")
raise TypeError("The input 'image' must be a two-dimensional array.")
if low_threshold is None:
low_threshold = 0.1 * dtype_limits(image)[1]
if high_threshold is None:
high_threshold = 0.2 * dtype_limits(image)[1]
if mask is None:
mask = np.ones(image.shape, dtype=bool)
+7 -14
View File
@@ -731,13 +731,8 @@ cdef int c_median_filter(Py_ssize_t rows,
return 0
def median_filter(cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] data,
cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] mask,
cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] output,
int radius,
def median_filter(cnp.uint8_t[:, ::1] data, cnp.uint8_t[:, ::1] mask,
cnp.uint8_t[:, ::1] output, int radius,
cnp.int32_t percent):
"""Median filter with octagon shape and masking.
@@ -773,12 +768,10 @@ def median_filter(cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
raise ValueError('Data shape (%d, %d) is not output shape (%d, %d)' %
(data.shape[0], data.shape[1],
output.shape[0], output.shape[1]))
if c_median_filter(<cnp.int32_t>data.shape[0],
<cnp.int32_t>data.shape[1],
<cnp.int32_t>data.strides[0],
<cnp.int32_t>data.strides[1],
if c_median_filter(data.shape[0], data.shape[1],
data.strides[0], data.strides[1],
radius, percent,
<cnp.uint8_t*>data.data,
<cnp.uint8_t*>mask.data,
<cnp.uint8_t*>output.data):
&data[0, 0],
&mask[0, 0],
&output[0, 0]):
raise MemoryError('Failed to allocate scratchpad memory')
+5 -5
View File
@@ -37,7 +37,7 @@ def _denoise_tv_chambolle_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 = denoise_tv(mask, weight=100)
>>> res = denoise_tv_chambolle(mask, weight=100)
"""
@@ -127,7 +127,7 @@ def _denoise_tv_chambolle_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
>>> from skimage import color, data
>>> lena = color.rgb2gray(data.lena())
>>> lena += 0.5 * lena.std() * np.random.randn(*lena.shape)
>>> denoised_lena = denoise_tv(lena, weight=60)
>>> denoised_lena = denoise_tv_chambolle(lena, weight=60)
"""
@@ -227,7 +227,7 @@ def denoise_tv_chambolle(im, weight=50, eps=2.e-4, n_iter_max=200,
>>> from skimage import color, data
>>> lena = color.rgb2gray(data.lena())
>>> lena += 0.5 * lena.std() * np.random.randn(*lena.shape)
>>> denoised_lena = denoise_tv(lena, weight=60)
>>> denoised_lena = denoise_tv_chambolle(lena, weight=60)
3D example on synthetic data:
@@ -235,7 +235,7 @@ def denoise_tv_chambolle(im, weight=50, 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 = denoise_tv(mask, weight=100)
>>> res = denoise_tv_chambolle(mask, weight=100)
"""
@@ -250,7 +250,7 @@ def denoise_tv_chambolle(im, weight=50, eps=2.e-4, n_iter_max=200,
out = np.zeros_like(im)
for c in range(im.shape[2]):
out[..., c] = _denoise_tv_chambolle_2d(im[..., c], weight, eps,
n_iter_max)
n_iter_max)
else:
out = _denoise_tv_chambolle_3d(im, weight, eps, n_iter_max)
else:
+54 -42
View File
@@ -98,7 +98,7 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
"""
image = np.atleast_3d(img_as_float(image))
# if image.max() is 0, then dist_scale can have an unverified value
# and color_lut[<int>(dist * dist_scale)] may cause a segmentation fault
# so we verify we have a positive image and that the max is not 0.0.
@@ -112,12 +112,9 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
Py_ssize_t window_ext = (win_size - 1) / 2
double max_value
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] cimage
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] out
double* image_data
double* out_data
double[:, :, ::1] cimage
double[:, :, ::1] out
double* color_lut
double* range_lut
@@ -136,15 +133,13 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
csigma_range = sigma_range
max_value = image.max()
if max_value == 0.0:
raise ValueError("The maximum value found in the image was 0.")
cimage = np.ascontiguousarray(image)
out = np.zeros((rows, cols, dims), dtype=np.double)
image_data = <double*>cimage.data
out_data = <double*>out.data
color_lut = _compute_color_lut(bins, csigma_range, max_value)
range_lut = _compute_range_lut(win_size, sigma_spatial)
dist_scale = bins / dims / max_value
@@ -159,11 +154,10 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
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]
centres[d] = cimage[r, c, d]
for wr in range(-window_ext, window_ext + 1):
rr = wr + r
kr = wr + window_ext
@@ -175,7 +169,7 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
# distance between centre stack and current position
dist = 0
for d in range(dims):
value = get_pixel3d(image_data, rows, cols, dims,
value = get_pixel3d(&cimage[0, 0, 0], rows, cols, dims,
rr, cc, d, cmode, cval)
values[d] = value
dist += (centres[d] - value)**2
@@ -189,7 +183,7 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
total_values[d] += values[d] * weight
total_weight += weight
for d in range(dims):
out_data[pixel_addr + d] = total_values[d] / total_weight
out[r, c, d] = total_values[d] / total_weight
free(color_lut)
free(range_lut)
@@ -197,10 +191,11 @@ def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
free(centres)
free(total_values)
return np.squeeze(out)
return np.squeeze(np.asarray(out))
def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3,
char isotropic=True):
"""Perform total-variation denoising using split-Bregman optimization.
Total-variation denoising (also know as total-variation regularization)
@@ -222,8 +217,10 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
SUM((u(n) - u(n-1))**2) < eps
max_iter: int, optional
max_iter : int, optional
Maximal number of iterations used for the optimization.
isotropic : boolean, optional
Switch between isotropic and anisotropic TV denoising.
Returns
-------
@@ -239,6 +236,7 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
.. [3] Pascal Getreuer, "RudinOsherFatemi Total Variation Denoising
using Split Bregman" in Image Processing On Line on 20120519,
http://www.ipol.im/pub/art/2012/g-tvd/article_lr.pdf
.. [4] http://www.math.ucsb.edu/~cgarcia/UGProjects/BregmanAlgorithms_JacquelineBush.pdf
"""
@@ -254,21 +252,17 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
Py_ssize_t total = rows * cols * dims
shape_ext = (rows2, cols2, dims)
shape_ext = (rows2, cols2, dims)
u = np.zeros(shape_ext, dtype=np.double)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] cimage = \
np.ascontiguousarray(image)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] u = \
np.zeros(shape_ext, dtype=np.double)
cdef:
double[:, :, ::1] cimage = np.ascontiguousarray(image)
double[:, :, ::1] cu = u
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] dx = \
np.zeros(shape_ext, dtype=np.double)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] dy = \
np.zeros(shape_ext, dtype=np.double)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] bx = \
np.zeros(shape_ext, dtype=np.double)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] by = \
np.zeros(shape_ext, dtype=np.double)
double[:, :, ::1] dx = np.zeros(shape_ext, dtype=np.double)
double[:, :, ::1] dy = np.zeros(shape_ext, dtype=np.double)
double[:, :, ::1] bx = np.zeros(shape_ext, dtype=np.double)
double[:, :, ::1] by = np.zeros(shape_ext, dtype=np.double)
double ux, uy, uprev, unew, bxx, byy, dxx, dyy, s
int i = 0
@@ -292,19 +286,19 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
for r in range(1, rows + 1):
for c in range(1, cols + 1):
uprev = u[r, c, k]
uprev = cu[r, c, k]
# forward derivatives
ux = u[r, c + 1, k] - uprev
uy = u[r + 1, c, k] - uprev
ux = cu[r, c + 1, k] - uprev
uy = cu[r + 1, c, k] - uprev
# Gauss-Seidel method
unew = (
lam * (
+ u[r + 1, c, k]
+ u[r - 1, c, k]
+ u[r, c + 1, k]
+ u[r, c - 1, k]
+ cu[r + 1, c, k]
+ cu[r - 1, c, k]
+ cu[r, c + 1, k]
+ cu[r, c - 1, k]
+ dx[r, c - 1, k]
- dx[r, c, k]
@@ -317,7 +311,7 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
+ by[r, c, k]
) + weight * cimage[r - 1, c - 1, k]
) / norm
u[r, c, k] = unew
cu[r, c, k] = unew
# update root mean square error
rmse += (unew - uprev)**2
@@ -325,9 +319,27 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
bxx = bx[r, c, k]
byy = by[r, c, k]
s = sqrt((ux + bxx)**2 + (uy + byy)**2)
dxx = s * lam * (ux + bxx) / (s * lam + 1)
dyy = s * lam * (uy + byy) / (s * lam + 1)
# d_subproblem after reference [4]
if isotropic:
s = sqrt((ux + bxx)**2 + (uy + byy)**2)
dxx = s * lam * (ux + bxx) / (s * lam + 1)
dyy = s * lam * (uy + byy) / (s * lam + 1)
else:
s = ux + bxx
if s > 1 / lam:
dxx = s - 1/lam
elif s < -1 / lam:
dxx = s + 1 / lam
else:
dxx = 0
s = uy + byy
if s > 1 / lam:
dyy = s - 1 / lam
elif s < -1 / lam:
dyy = s + 1 / lam
else:
dyy = 0
dx[r, c, k] = dxx
dy[r, c, k] = dyy
@@ -338,4 +350,4 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
rmse = sqrt(rmse / total)
i += 1
return np.squeeze(u[1:-1, 1:-1])
return np.squeeze(np.asarray(u[1:-1, 1:-1]))
+105
View File
@@ -0,0 +1,105 @@
import collections as coll
import numpy as np
from scipy import ndimage
import warnings
from ..util import img_as_float
from ..color import guess_spatial_dimensions
__all__ = ['gaussian_filter']
def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0,
multichannel=None):
"""
Multi-dimensional Gaussian filter
Parameters
----------
image : array-like
input image (grayscale or color) to filter.
sigma : scalar or sequence of scalars
standard deviation for Gaussian kernel. The standard
deviations of the Gaussian filter are given for each axis as a
sequence, or as a single number, in which case it is equal for
all axes.
output : array, optional
The ``output`` parameter passes an array in which to store the
filter output.
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
The `mode` parameter determines how the array borders are
handled, where `cval` is the value when mode is equal to
'constant'. Default is 'nearest'.
cval : scalar, optional
Value to fill past edges of input if `mode` is 'constant'. Default
is 0.0
multichannel : bool, optional (default: None)
Whether the last axis of the image is to be interpreted as multiple
channels. If True, each channel is filtered separately (channels are
not mixed together). Only 3 channels are supported. If `None`,
the function will attempt to guess this, and raise a warning if
ambiguous, when the array has shape (M, N, 3).
Returns
-------
filtered_image : ndarray
the filtered array
Notes
-----
This function is a wrapper around :func:`scipy.ndimage.gaussian_filter`.
Integer arrays are converted to float.
The multi-dimensional filter is implemented as a sequence of
one-dimensional convolution filters. The intermediate arrays are
stored in the same data type as the output. Therefore, for output
types with a limited precision, the results may be imprecise
because intermediate results may be stored with insufficient
precision.
Examples
--------
>>> a = np.zeros((3, 3))
>>> a[1, 1] = 1
>>> a
array([[ 0., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 0.]])
>>> gaussian_filter(a, sigma=0.4) # mild smoothing
array([[ 0.00163116, 0.03712502, 0.00163116],
[ 0.03712502, 0.84496158, 0.03712502],
[ 0.00163116, 0.03712502, 0.00163116]])
>>> gaussian_filter(a, sigma=1) # more smooting
array([[ 0.05855018, 0.09653293, 0.05855018],
[ 0.09653293, 0.15915589, 0.09653293],
[ 0.05855018, 0.09653293, 0.05855018]])
>>> # Several modes are possible for handling boundaries
>>> gaussian_filter(a, sigma=1, mode='reflect')
array([[ 0.08767308, 0.12075024, 0.08767308],
[ 0.12075024, 0.16630671, 0.12075024],
[ 0.08767308, 0.12075024, 0.08767308]])
>>> # For RGB images, each is filtered separately
>>> from skimage.data import lena
>>> image = lena()
>>> filtered_lena = gaussian_filter(image, sigma=1, multichannel=True)
"""
spatial_dims = guess_spatial_dimensions(image)
if spatial_dims is None and multichannel is None:
msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB" +
" by default. Use `multichannel=False` to interpret as " +
" 3D image with last dimension of length 3.")
warnings.warn(RuntimeWarning(msg))
multichannel = True
if multichannel:
# do not filter across channels
if not isinstance(sigma, coll.Iterable):
sigma = [sigma] * (image.ndim - 1)
if len(sigma) != image.ndim:
sigma = np.concatenate((np.asarray(sigma), [0]))
image = img_as_float(image)
return ndimage.gaussian_filter(image, sigma, mode=mode, cval=cval)
+6 -6
View File
@@ -8,7 +8,7 @@ Copyright (c) 2009-2011 Broad Institute
All rights reserved.
Original author: Lee Kamentstky
"""
import numpy
import numpy as np
def rank_order(image):
@@ -47,14 +47,14 @@ def rank_order(image):
(array([0, 1, 2, 1], dtype=uint32), array([-1. , 2.5, 3.1]))
"""
flat_image = image.ravel()
sort_order = flat_image.argsort().astype(numpy.uint32)
sort_order = flat_image.argsort().astype(np.uint32)
flat_image = flat_image[sort_order]
sort_rank = numpy.zeros_like(sort_order)
sort_rank = np.zeros_like(sort_order)
is_different = flat_image[:-1] != flat_image[1:]
numpy.cumsum(is_different, out=sort_rank[1:])
original_values = numpy.zeros((sort_rank[-1] + 1,), image.dtype)
np.cumsum(is_different, out=sort_rank[1:])
original_values = np.zeros((sort_rank[-1] + 1,), image.dtype)
original_values[0] = flat_image[0]
original_values[1:] = flat_image[1:][is_different]
int_image = numpy.zeros_like(sort_order)
int_image = np.zeros_like(sort_order)
int_image[sort_order] = sort_rank
return (int_image.reshape(image.shape), original_values)
+3 -3
View File
@@ -31,8 +31,8 @@ HPREWITT_WEIGHTS = np.array([[ 1, 1, 1],
[-1,-1,-1]]) / 3.0
VPREWITT_WEIGHTS = HPREWITT_WEIGHTS.T
ROBERTS_PD_WEIGHTS = np.array([[ 1, 0],
[ 0, -1]], dtype=np.double)
ROBERTS_PD_WEIGHTS = np.array([[1, 0],
[0, -1]], dtype=np.double)
ROBERTS_ND_WEIGHTS = np.array([[0, 1],
[-1, 0]], dtype=np.double)
@@ -346,7 +346,7 @@ def roberts(image, mask=None):
"""Find the edge magnitude using Roberts' cross operator.
Parameters
----------
----------
image : 2-D array
Image to process.
mask : 2-D array, optional
+4 -3
View File
@@ -66,9 +66,10 @@ class LPIFilter2D(object):
--------
Gaussian filter:
>>> def filt_func(r, c):
... return np.exp(-np.hypot(r, c)/1)
Use a 1-D gaussian in each direction without normalization
coefficients.
>>> def filt_func(r, c, sigma = 1):
... return np.exp(-np.hypot(r, c)/sigma)
>>> filter = LPIFilter2D(filt_func)
"""
+6 -6
View File
@@ -23,10 +23,10 @@ size. This implementation gives better results for large structuring elements.
The local histogram is updated at each pixel as the structuring element window
moves by, i.e. only those pixels entering and leaving the structuring element
update the local histogram. The histogram size is 8-bit (256 bins) for 8-bit
images and 2 to 12-bit (up to 4096 bins) for 16-bit images depending on the
maximum value of the image. Pixel values higher than 4095 raise a ValueError.
images and 2 to 16-bit for 16-bit images depending on the maximum value of the
image.
The filter is applied up to the image border, the neighboorhood used is adjusted
accordingly. The user may provide a mask image (same size as input image) where
non zero values are the part of the image participating in the histogram
computation. By default the entire image is filtered.
The filter is applied up to the image border, the neighboorhood used is
adjusted accordingly. The user may provide a mask image (same size as input
image) where non zero values are the part of the image participating in the
histogram computation. By default the entire image is filtered.
+45 -12
View File
@@ -1,36 +1,69 @@
from .rank import (autolevel, bottomhat, equalize, gradient, maximum, mean,
meansubtraction, median, minimum, modal, morph_contr_enh,
pop, threshold, tophat, noise_filter, entropy, otsu)
from .percentile_rank import (percentile_autolevel, percentile_gradient,
percentile_mean, percentile_mean_subtraction,
percentile_morph_contr_enh, percentile,
percentile_pop, percentile_threshold)
from .bilateral_rank import bilateral_mean, bilateral_pop
from .generic import (autolevel, bottomhat, equalize, gradient, maximum, mean,
subtract_mean, median, minimum, modal, enhance_contrast,
pop, threshold, tophat, noise_filter, entropy, otsu)
from .percentile import (autolevel_percentile, gradient_percentile,
mean_percentile, subtract_mean_percentile,
enhance_contrast_percentile, percentile,
pop_percentile, threshold_percentile)
from .bilateral import mean_bilateral, pop_bilateral
from skimage._shared.utils import deprecated
percentile_autolevel = deprecated('autolevel_percentile')(autolevel_percentile)
percentile_gradient = deprecated('gradient_percentile')(gradient_percentile)
percentile_mean = deprecated('mean_percentile')(mean_percentile)
bilateral_mean = deprecated('mean_bilateral')(mean_bilateral)
meansubtraction = deprecated('subtract_mean')(subtract_mean)
percentile_mean_subtraction = deprecated('subtract_mean_percentile')\
(subtract_mean_percentile)
morph_contr_enh = deprecated('enhance_contrast')(enhance_contrast)
percentile_morph_contr_enh = deprecated('enhance_contrast_percentile')\
(enhance_contrast_percentile)
percentile_pop = deprecated('pop_percentile')(pop_percentile)
bilateral_pop = deprecated('pop_bilateral')(pop_bilateral)
percentile_threshold = deprecated('threshold_percentile')(threshold_percentile)
__all__ = ['autolevel',
'autolevel_percentile',
'bottomhat',
'equalize',
'gradient',
'gradient_percentile',
'maximum',
'mean',
'meansubtraction',
'mean_percentile',
'mean_bilateral',
'subtract_mean',
'subtract_mean_percentile',
'median',
'minimum',
'modal',
'morph_contr_enh',
'enhance_contrast',
'enhance_contrast_percentile',
'pop',
'pop_percentile',
'pop_bilateral',
'threshold',
'threshold_percentile',
'tophat',
'noise_filter',
'entropy',
'otsu',
'otsu'
'percentile',
# Deprecated
'percentile_autolevel',
'percentile_gradient',
'percentile_mean',
'percentile_mean_subtraction',
'percentile_morph_contr_enh',
'percentile',
'percentile_pop',
'percentile_threshold',
'bilateral_mean',
-20
View File
@@ -1,20 +0,0 @@
cimport numpy as cnp
ctypedef cnp.uint16_t dtype_t
cdef int int_max(int a, int b)
cdef int int_min(int a, int b)
# 16-bit core kernel receives extra information about data bitdepth
cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t,
Py_ssize_t, Py_ssize_t, Py_ssize_t, float,
float, Py_ssize_t, Py_ssize_t),
cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask,
cnp.ndarray[dtype_t, ndim=2] out,
char shift_x, char shift_y, Py_ssize_t bitdepth,
float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *
-255
View File
@@ -1,255 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as cnp
from libc.stdlib cimport malloc, free
from _core8 cimport is_in_mask
cdef inline int int_max(int a, int b):
return a if a >= b else b
cdef inline int int_min(int a, int b):
return a if a <= b else b
cdef inline void histogram_increment(Py_ssize_t * histo, float * pop,
dtype_t value):
histo[value] += 1
pop[0] += 1
cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop,
dtype_t value):
histo[value] -= 1
pop[0] -= 1
cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t,
Py_ssize_t, Py_ssize_t, Py_ssize_t, float,
float, Py_ssize_t, Py_ssize_t),
cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask,
cnp.ndarray[dtype_t, ndim=2] out,
char shift_x, char shift_y, Py_ssize_t bitdepth,
float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *:
"""Compute histogram for each pixel neighborhood, apply kernel function and
use kernel function return value for output image.
"""
cdef Py_ssize_t rows = image.shape[0]
cdef Py_ssize_t cols = image.shape[1]
cdef Py_ssize_t srows = selem.shape[0]
cdef Py_ssize_t scols = selem.shape[1]
cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y
cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x
# check that structuring element center is inside the element bounding box
assert centre_r >= 0
assert centre_c >= 0
assert centre_r < srows
assert centre_c < scols
assert bitdepth in range(2, 13)
maxbin_list = [0, 0, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
midbin_list = [0, 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]
# set maxbin and midbin
cdef Py_ssize_t maxbin = maxbin_list[bitdepth]
cdef Py_ssize_t midbin = midbin_list[bitdepth]
assert (image < maxbin).all()
# define pointers to the data
cdef dtype_t * out_data = <dtype_t * >out.data
cdef dtype_t * image_data = <dtype_t * >image.data
cdef cnp.uint8_t * mask_data = <cnp.uint8_t * >mask.data
# define local variable types
cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row
# number of pixels actually inside the neighborhood (float)
cdef float pop
# allocate memory with malloc
cdef Py_ssize_t max_se = srows * scols
# number of element in each attack border
cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w
# the current local histogram distribution
cdef Py_ssize_t * histo = <Py_ssize_t * >malloc(maxbin * sizeof(Py_ssize_t))
# these lists contain the relative pixel row and column for each of the 4
# attack borders east, west, north and south e.g. se_e_r lists the rows of
# the east structuring element border
cdef Py_ssize_t * se_e_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_e_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_w_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_w_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_n_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_n_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_s_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_s_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
# build attack and release borders
# by using difference along axis
t = np.hstack((selem, np.zeros((selem.shape[0], 1))))
t_e = np.diff(t, axis=1) < 0
t = np.hstack((np.zeros((selem.shape[0], 1)), selem))
t_w = np.diff(t, axis=1) > 0
t = np.vstack((selem, np.zeros((1, selem.shape[1]))))
t_s = np.diff(t, axis=0) < 0
t = np.vstack((np.zeros((1, selem.shape[1])), selem))
t_n = np.diff(t, axis=0) > 0
num_se_n = num_se_s = num_se_e = num_se_w = 0
for r in range(srows):
for c in range(scols):
if t_e[r, c]:
se_e_r[num_se_e] = r - centre_r
se_e_c[num_se_e] = c - centre_c
num_se_e += 1
if t_w[r, c]:
se_w_r[num_se_w] = r - centre_r
se_w_c[num_se_w] = c - centre_c
num_se_w += 1
if t_n[r, c]:
se_n_r[num_se_n] = r - centre_r
se_n_c[num_se_n] = c - centre_c
num_se_n += 1
if t_s[r, c]:
se_s_r[num_se_s] = r - centre_r
se_s_c[num_se_s] = c - centre_c
num_se_s += 1
# initial population and histogram
for i in range(maxbin):
histo[i] = 0
pop = 0
for r in range(srows):
for c in range(scols):
rr = r - centre_r
cc = c - centre_c
if selem[r, c]:
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
r = 0
c = 0
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# main loop
r = 0
for even_row in range(0, rows, 2):
# ---> west to east
for c in range(1, cols):
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s] - 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(
histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# ---> east to west
for c in range(cols - 2, -1, -1):
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s] + 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(
histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# release memory allocated by malloc
free(se_e_r)
free(se_e_c)
free(se_w_r)
free(se_w_c)
free(se_n_r)
free(se_n_c)
free(se_s_r)
free(se_s_c)
free(histo)
-25
View File
@@ -1,25 +0,0 @@
cimport numpy as cnp
ctypedef cnp.uint8_t dtype_t
cdef dtype_t uint8_max(dtype_t a, dtype_t b)
cdef dtype_t uint8_min(dtype_t a, dtype_t b)
cdef dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c,
dtype_t * mask)
# 8-bit core kernel receives extra information about data inferior and superior
# percentiles
cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
float, Py_ssize_t, Py_ssize_t),
cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask,
cnp.ndarray[dtype_t, ndim=2] out,
char shift_x, char shift_y, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1) except *
-422
View File
@@ -1,422 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from libc.math cimport log
from skimage.filter.rank._core16 cimport _core16
# -----------------------------------------------------------------
# kernels uint16 take extra parameter for defining the bitdepth
# -----------------------------------------------------------------
ctypedef cnp.uint16_t dtype_t
cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, delta
if pop:
for i in range(maxbin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(maxbin):
if histo[i]:
imin = i
break
delta = imax - imin
if delta > 0:
return <dtype_t>(1. * (maxbin - 1) * (g - imin) / delta)
else:
return <dtype_t>(imax - imin)
cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(maxbin):
if histo[i]:
break
return <dtype_t>(g - i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float sum = 0.
if pop:
for i in range(maxbin):
sum += histo[i]
if i >= g:
break
return <dtype_t>(((maxbin - 1) * sum) / pop)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(maxbin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(maxbin):
if histo[i]:
imin = i
break
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(maxbin - 1, -1, -1):
if histo[i]:
return <dtype_t>(i)
return <dtype_t>(0)
cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(maxbin):
mean += histo[i] * i
return <dtype_t>(mean / pop)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_meansubtraction(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(maxbin):
mean += histo[i] * i
return <dtype_t>((g - mean / pop) / 2. + (midbin - 1))
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float sum = pop / 2.0
if pop:
for i in range(maxbin):
if histo[i]:
sum -= histo[i]
if sum < 0:
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(maxbin):
if histo[i]:
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t hmax = 0, imax = 0
if pop:
for i in range(maxbin):
if histo[i] > hmax:
hmax = histo[i]
imax = i
return <dtype_t>(imax)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(maxbin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(maxbin):
if histo[i]:
imin = i
break
if imax - g < g - imin:
return <dtype_t>(imax)
else:
return <dtype_t>(imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
return <dtype_t>(pop)
cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(maxbin):
mean += histo[i] * i
return <dtype_t>(g > (mean / pop))
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(maxbin - 1, -1, -1):
if histo[i]:
break
return <dtype_t>(i - g)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float e, p
if pop:
e = 0.
for i in range(maxbin):
p = histo[i] / pop
if p > 0:
e -= p * log(p) / 0.6931471805599453
return <dtype_t>e * 1000
else:
return <dtype_t>(0)
# -----------------------------------------------------------------
# python wrappers
# -----------------------------------------------------------------
def autolevel(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def bottomhat(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def equalize(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_equalize, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def gradient(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def maximum(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_maximum, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def mean(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_mean, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def meansubtraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_meansubtraction, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def median(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_median, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def minimum(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_minimum, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def modal(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_modal, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_pop, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def threshold(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def tophat(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_tophat, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def entropy(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_entropy, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
@@ -1,82 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from skimage.filter.rank._core16 cimport _core16
# -----------------------------------------------------------------
# kernels uint16 take extra parameter for defining the bitdepth
# -----------------------------------------------------------------
ctypedef cnp.uint16_t dtype_t
cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, bilat_pop = 0
cdef float mean = 0.
if pop:
for i in range(maxbin):
if (g > (i - s0)) and (g < (i + s1)):
bilat_pop += histo[i]
mean += histo[i] * i
if bilat_pop:
return <dtype_t>(mean / bilat_pop)
else:
return <dtype_t>(0)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, bilat_pop = 0
if pop:
for i in range(maxbin):
if (g > (i - s0)) and (g < (i + s1)):
bilat_pop += histo[i]
return <dtype_t>(bilat_pop)
else:
return <dtype_t>(0)
# -----------------------------------------------------------------
# python wrappers
# -----------------------------------------------------------------
def mean(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1):
"""average greylevel (clipped on uint8)
"""
_core16(kernel_mean, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0., 0., s0, s1)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1):
"""returns the number of actual pixels of the structuring element inside
the mask
"""
_core16(kernel_pop, image, selem, mask, out, shift_x, shift_y,
bitdepth, .0, .0, s0, s1)
@@ -1,330 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from skimage.filter.rank._core16 cimport _core16, int_min, int_max
# -----------------------------------------------------------------
# kernels uint16 (SOFT version using percentiles)
# -----------------------------------------------------------------
ctypedef cnp.uint16_t dtype_t
cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(maxbin):
sum += histo[i]
if sum > p0 * pop:
imin = i
break
sum = 0
for i in range(maxbin - 1, -1, -1):
sum += histo[i]
if sum > p1 * pop:
imax = i
break
delta = imax - imin
if delta > 0:
return <dtype_t>(1.0 * (maxbin - 1)
* (int_min(int_max(imin, g), imax)
- imin) / delta)
else:
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(maxbin):
sum += histo[i]
if sum >= p0 * pop:
imin = i
break
sum = 0
for i in range((maxbin - 1), -1, -1):
sum += histo[i]
if sum >= p1 * pop:
imax = i
break
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(maxbin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return <dtype_t>(1.0 * mean / n)
else:
return <dtype_t>(0)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_mean_subtraction(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(maxbin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return <dtype_t>((g - (mean / n)) * .5 + midbin)
else:
return <dtype_t>(0)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(maxbin):
sum += histo[i]
if sum > p0 * pop:
imin = i
break
sum = 0
for i in range((maxbin - 1), -1, -1):
sum += histo[i]
if sum > p1 * pop:
imax = i
break
if g > imax:
return <dtype_t>imax
if g < imin:
return <dtype_t>imin
if imax - g < g - imin:
return <dtype_t>imax
else:
return <dtype_t>imin
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i
cdef float sum = 0.
if pop:
for i in range(maxbin):
sum += histo[i]
if sum >= p0 * pop:
break
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, n
if pop:
sum = 0
n = 0
for i in range(maxbin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
return <dtype_t>(n)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop,
dtype_t g, Py_ssize_t bitdepth,
Py_ssize_t maxbin, Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i
cdef float sum = 0.
if pop:
for i in range(maxbin):
sum += histo[i]
if sum >= p0 * pop:
break
return <dtype_t>((maxbin - 1) * (g >= i))
else:
return <dtype_t>(0)
# -----------------------------------------------------------------
# python wrappers
# -----------------------------------------------------------------
def autolevel(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""bottom hat
"""
_core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def gradient(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""return p0,p1 percentile gradient
"""
_core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def mean(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""return mean between [p0 and p1] percentiles
"""
_core16(kernel_mean, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def mean_subtraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""return original - mean between [p0 and p1] percentiles *.5 +127
"""
_core16(
kernel_mean_subtraction, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""reforce contrast using percentiles
"""
_core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def percentile(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0.):
"""return p0 percentile
"""
_core16(kernel_percentile, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, .0, <Py_ssize_t>0, <Py_ssize_t>0)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""return nb of pixels between [p0 and p1]
"""
_core16(kernel_pop, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def threshold(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0.):
"""return (maxbin-1) if g > percentile p0
"""
_core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, 0., <Py_ssize_t>0, <Py_ssize_t>0)
-483
View File
@@ -1,483 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from libc.math cimport log
from skimage.filter.rank._core8 cimport _core8
# -----------------------------------------------------------------
# kernels uint8
# -----------------------------------------------------------------
ctypedef cnp.uint8_t dtype_t
cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, delta
if pop:
for i in range(255, -1, -1):
if histo[i]:
imax = i
break
for i in range(256):
if histo[i]:
imin = i
break
delta = imax - imin
if delta > 0:
return <dtype_t>(255. * (g - imin) / delta)
else:
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(256):
if histo[i]:
break
return <dtype_t>(g - i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float sum = 0.
if pop:
for i in range(256):
sum += histo[i]
if i >= g:
break
return <dtype_t>((255 * sum) / pop)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(255, -1, -1):
if histo[i]:
imax = i
break
for i in range(256):
if histo[i]:
imin = i
break
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(255, -1, -1):
if histo[i]:
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(256):
mean += histo[i] * i
return <dtype_t>(mean / pop)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_meansubtraction(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(256):
mean += histo[i] * i
return <dtype_t>((g - mean / pop) / 2. + 127)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float sum = pop / 2.0
if pop:
for i in range(256):
if histo[i]:
sum -= histo[i]
if sum < 0:
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(256):
if histo[i]:
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t hmax = 0, imax = 0
if pop:
for i in range(256):
if histo[i] > hmax:
hmax = histo[i]
imax = i
return <dtype_t>(imax)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(255, -1, -1):
if histo[i]:
imax = i
break
for i in range(256):
if histo[i]:
imin = i
break
if imax - g < g - imin:
return <dtype_t>(imax)
else:
return <dtype_t>(imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
return <dtype_t>(pop)
cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(256):
mean += histo[i] * i
return <dtype_t>(g > (mean / pop))
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(255, -1, -1):
if histo[i]:
break
return <dtype_t>(i - g)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_noise_filter(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t min_i
# early stop if at least one pixel of the neighborhood has the same g
if histo[g] > 0:
return <dtype_t>0
for i in range(g, -1, -1):
if histo[i]:
break
min_i = g - i
for i in range(g, 256):
if histo[i]:
break
if i - g < min_i:
return <dtype_t>(i - g)
else:
return <dtype_t>min_i
cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float e, p
if pop:
e = 0.
for i in range(256):
p = histo[i] / pop
if p > 0:
e -= p * log(p) / 0.6931471805599453
return <dtype_t>e * 10
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_otsu(Py_ssize_t * histo, float pop, dtype_t g,
float p0, float p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t max_i
cdef float P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b
cdef float mu = 0.
# compute local mean
if pop:
for i in range(256):
mu += histo[i] * i
mu = (mu / pop)
else:
return <dtype_t>(0)
# maximizing the between class variance
max_i = 0
q1 = histo[0] / pop
m1 = 0.
max_sigma_b = 0.
for i in range(1, 256):
P = histo[i] / pop
new_q1 = q1 + P
if new_q1 > 0:
mu1 = (q1 * mu1 + i * P) / new_q1
mu2 = (mu - new_q1 * mu1) / (1. - new_q1)
sigma_b = new_q1 * (1. - new_q1) * (mu1 - mu2) ** 2
if sigma_b > max_sigma_b:
max_sigma_b = sigma_b
max_i = i
q1 = new_q1
return <dtype_t>max_i
# -----------------------------------------------------------------
# python wrappers
# used only internally
# -----------------------------------------------------------------
def autolevel(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def bottomhat(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def equalize(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_equalize, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def gradient(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def maximum(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_maximum, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def mean(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_mean, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def meansubtraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_meansubtraction, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def median(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_median, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def minimum(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def modal(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_modal, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_pop, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def threshold(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0,
<Py_ssize_t>0, <Py_ssize_t>0)
def tophat(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def noise_filter(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def entropy(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def otsu(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
-294
View File
@@ -1,294 +0,0 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min
# -----------------------------------------------------------------
# kernels uint8 (SOFT version using percentiles)
# -----------------------------------------------------------------
ctypedef cnp.uint8_t dtype_t
cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
imin = 0
imax = 255
for i in range(256):
sum += histo[i]
if sum > (p0 * pop):
imin = i
break
sum = 0
for i in range(255, -1, -1):
sum += histo[i]
if sum > (p1 * pop):
imax = i
break
delta = imax - imin
if delta > 0:
return <dtype_t>(255 * (uint8_min(uint8_max(imin, g), imax)
- imin) / delta)
else:
return <dtype_t>(imax - imin)
else:
return <dtype_t>(128)
cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(256):
sum += histo[i]
if sum >= p0 * pop:
imin = i
break
sum = 0
for i in range(255, -1, -1):
sum += histo[i]
if sum >= p1 * pop:
imax = i
break
return <dtype_t>(imax - imin)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(256):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return <dtype_t>(1.0 * mean / n)
else:
return <dtype_t>(0)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_mean_subtraction(Py_ssize_t * histo,
float pop,
dtype_t g,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(256):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return <dtype_t>((g - (mean / n)) * .5 + 127)
else:
return <dtype_t>(0)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo,
float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(256):
sum += histo[i]
if sum >= p0 * pop:
imin = i
break
sum = 0
for i in range(255, -1, -1):
sum += histo[i]
if sum >= p1 * pop:
imax = i
break
if g > imax:
return <dtype_t>imax
if g < imin:
return <dtype_t>imin
if imax - g < g - imin:
return <dtype_t>imax
else:
return <dtype_t>imin
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i
cdef float sum = 0.
if pop:
for i in range(256):
sum += histo[i]
if sum >= p0 * pop:
break
return <dtype_t>(i)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, n
if pop:
sum = 0
n = 0
for i in range(256):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
return <dtype_t>(n)
else:
return <dtype_t>(0)
cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i
cdef float sum = 0.
if pop:
for i in range(256):
sum += histo[i]
if sum >= p0 * pop:
break
return <dtype_t>(255 * (g >= i))
else:
return <dtype_t>(0)
# -----------------------------------------------------------------
# python wrappers
# -----------------------------------------------------------------
def autolevel(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""autolevel
"""
_core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, p0, p1,
<Py_ssize_t>0, <Py_ssize_t>0)
def gradient(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""return p0,p1 percentile gradient
"""
_core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, p0, p1,
<Py_ssize_t>0, <Py_ssize_t>0)
def mean(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""return mean between [p0 and p1] percentiles
"""
_core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, p0, p1,
<Py_ssize_t>0, <Py_ssize_t>0)
def mean_subtraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""return original - mean between [p0 and p1] percentiles *.5 +127
"""
_core8(kernel_mean_subtraction, image, selem, mask, out, shift_x, shift_y,
p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""reforce contrast using percentiles
"""
_core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y,
p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
def percentile(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0.):
"""return p0 percentile
"""
_core8(kernel_percentile, image, selem, mask, out, shift_x, shift_y,
p0, 0., <Py_ssize_t>0, <Py_ssize_t>0)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""return nb of pixels between [p0 and p1]
"""
_core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, p0, p1,
<Py_ssize_t>0, <Py_ssize_t>0)
def threshold(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0.):
"""return 255 if g > percentile p0
"""
_core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, p0, 0.,
<Py_ssize_t>0, <Py_ssize_t>0)
@@ -3,19 +3,16 @@
The local histogram is computed using a sliding window similar to the method
described in [1]_.
Input image must be 16-bit with a value < 4096 (i.e. 12 bit),
the number of histogram bins is determined from the
maximum value present in the image.
The pixel neighborhood is defined by:
* the given structuring element
* an interval [g-s0,g+s1] in greylevel around g the processed pixel greylevel
* an interval [g-s0, g+s1] in greylevel around g the processed pixel greylevel
The kernel is flat (i.e. each pixel belonging to the neighborhood contributes
equally).
Result image is 16-bit with respect to the input image.
Result image is 8-/16-bit or double with respect to the input image and the
rank filter operation.
References
----------
@@ -28,50 +25,27 @@ References
import numpy as np
from skimage import img_as_ubyte
from skimage.filter.rank import _crank16_bilateral
from skimage.filter.rank.generic import find_bitdepth
from . import bilateral_cy
from .generic import _handle_input
__all__ = ['bilateral_mean', 'bilateral_pop']
__all__ = ['mean_bilateral', 'pop_bilateral']
def _apply(func8, func16, image, selem, out, mask, shift_x, shift_y, s0, s1):
selem = img_as_ubyte(selem > 0)
image = np.ascontiguousarray(image)
def _apply(func, image, selem, out, mask, shift_x, shift_y, s0, s1,
out_dtype=None):
if mask is None:
mask = np.ones(image.shape, dtype=np.uint8)
else:
mask = np.ascontiguousarray(mask)
mask = img_as_ubyte(mask)
image, selem, out, mask, max_bin = _handle_input(image, selem, out, mask,
out_dtype)
if image is out:
raise NotImplementedError("Cannot perform rank operation in place.")
if image.dtype == np.uint8:
if func8 is None:
raise TypeError("Not implemented for uint8 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint8)
func8(image, selem, shift_x=shift_x, shift_y=shift_y,
mask=mask, out=out, s0=s0, s1=s1)
elif image.dtype == np.uint16:
if func16 is None:
raise TypeError("Not implemented for uint16 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint16)
bitdepth = find_bitdepth(image)
if bitdepth > 11:
raise ValueError("Only uint16 <4096 image (12bit) supported.")
func16(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
bitdepth=bitdepth + 1, out=out, s0=s0, s1=s1)
else:
raise TypeError("Only uint8 and uint16 image supported.")
func(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
out=out, max_bin=max_bin, s0=s0, s1=s1)
return out
def bilateral_mean(image, selem, out=None, mask=None, shift_x=False,
def mean_bilateral(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, s0=10, s1=10):
"""Apply a flat kernel bilateral filter.
@@ -81,43 +55,38 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False,
Spatial closeness is measured by considering only the local pixel
neighborhood given by a structuring element (selem).
Radiometric similarity is defined by the greylevel interval [g-s0,g+s1]
Radiometric similarity is defined by the greylevel interval [g-s0, g+s1]
where g is the current pixel greylevel. Only pixels belonging to the
structuring element AND having a greylevel inside this interval are
averaged. Return greyscale local bilateral_mean of an image.
Parameters
----------
image : ndarray
Image array (uint16). As the algorithm uses max. 12bit histogram,
an exception will be raised if image has a value > 4095
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : (int)
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
s0, s1 : int
define the [s0, s1] interval to be considered for computing the value.
Define the [s0, s1] interval around the greyvalue of the center pixel
to be considered for computing the value.
Returns
-------
out : uint16 array
The result of the local bilateral mean.
out : ndarray (same dtype as input image)
Output image.
See also
--------
skimage.filter.denoise_bilateral() for a gaussian bilateral filter.
Notes
-----
* input image are 16-bit only
skimage.filter.denoise_bilateral for a gaussian bilateral filter.
Examples
--------
@@ -128,13 +97,14 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False,
>>> ima = data.camera().astype(np.uint16)
>>> # bilateral filtering of cameraman image using a flat kernel
>>> bilat_ima = bilateral_mean(ima, disk(20), s0=10,s1=10)
"""
return _apply(None, _crank16_bilateral.mean, image, selem, out=out,
return _apply(bilateral_cy._mean, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y, s0=s0, s1=s1)
def bilateral_pop(image, selem, out=None, mask=None, shift_x=False,
def pop_bilateral(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, s0=10, s1=10):
"""Return the number (population) of pixels actually inside the bilateral
neighborhood, i.e. being inside the structuring element AND having a gray
@@ -142,32 +112,27 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray
Image array (uint16). As the algorithm uses max. 12bit histogram,
an exception will be raised if image has a value > 4095
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : (int)
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
s0, s1 : int
define the [s0, s1] interval to be considered for computing the value.
Define the [s0, s1] interval around the greyvalue of the center pixel
to be considered for computing the value.
Returns
-------
out : uint16 array
the local number of pixels inside the bilateral neighborhood
Notes
-----
* input image are 16-bit only
out : ndarray (same dtype as input image)
Output image.
Examples
--------
@@ -175,10 +140,10 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False,
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> ima16 = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint16)
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint16)
>>> rank.bilateral_pop(ima16, square(3), s0=10,s1=10)
array([[3, 4, 3, 4, 3],
[4, 4, 6, 4, 4],
@@ -188,5 +153,5 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False,
"""
return _apply(None, _crank16_bilateral.pop, image, selem, out=out,
return _apply(bilateral_cy._pop, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y, s0=s0, s1=s1)
+70
View File
@@ -0,0 +1,70 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from libc.math cimport log
from .core_cy cimport dtype_t, dtype_t_out, _core
cdef inline double _kernel_mean(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t bilat_pop = 0
cdef Py_ssize_t mean = 0
if pop:
for i in range(max_bin):
if (g > (i - s0)) and (g < (i + s1)):
bilat_pop += histo[i]
mean += histo[i] * i
if bilat_pop:
return mean / bilat_pop
else:
return 0
else:
return 0
cdef inline double _kernel_pop(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t bilat_pop = 0
if pop:
for i in range(max_bin):
if (g > (i - s0)) and (g < (i + s1)):
bilat_pop += histo[i]
return bilat_pop
else:
return 0
def _mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t s0, Py_ssize_t s1,
Py_ssize_t max_bin):
_core(_kernel_mean[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, s0, s1, max_bin)
def _pop(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t s0, Py_ssize_t s1,
Py_ssize_t max_bin):
_core(_kernel_pop[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, s0, s1, max_bin)
+28
View File
@@ -0,0 +1,28 @@
from numpy cimport uint8_t, uint16_t, double_t
ctypedef fused dtype_t:
uint8_t
uint16_t
ctypedef fused dtype_t_out:
uint8_t
uint16_t
double_t
cdef dtype_t _max(dtype_t a, dtype_t b)
cdef dtype_t _min(dtype_t a, dtype_t b)
cdef void _core(double kernel(Py_ssize_t*, double, dtype_t,
Py_ssize_t, Py_ssize_t, double,
double, Py_ssize_t, Py_ssize_t),
dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1,
Py_ssize_t max_bin) except *
@@ -9,29 +9,29 @@ cimport numpy as cnp
from libc.stdlib cimport malloc, free
cdef inline dtype_t uint8_max(dtype_t a, dtype_t b):
cdef inline dtype_t _max(dtype_t a, dtype_t b):
return a if a >= b else b
cdef inline dtype_t uint8_min(dtype_t a, dtype_t b):
cdef inline dtype_t _min(dtype_t a, dtype_t b):
return a if a <= b else b
cdef inline void histogram_increment(Py_ssize_t * histo, float * pop,
cdef inline void histogram_increment(Py_ssize_t* histo, double* pop,
dtype_t value):
histo[value] += 1
pop[0] += 1
cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop,
cdef inline void histogram_decrement(Py_ssize_t* histo, double* pop,
dtype_t value):
histo[value] -= 1
pop[0] -= 1
cdef inline dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c,
dtype_t * mask):
cdef inline char is_in_mask(Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c,
char* mask):
"""Check whether given coordinate is within image and mask is true."""
if r < 0 or r > rows - 1 or c < 0 or c > cols - 1:
return 0
@@ -42,14 +42,17 @@ cdef inline dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols,
return 0
cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
float, Py_ssize_t, Py_ssize_t),
cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask,
cnp.ndarray[dtype_t, ndim=2] out,
char shift_x, char shift_y, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1) except *:
cdef void _core(double kernel(Py_ssize_t*, double, dtype_t,
Py_ssize_t, Py_ssize_t, double,
double, Py_ssize_t, Py_ssize_t),
dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1,
Py_ssize_t max_bin) except *:
"""Compute histogram for each pixel neighborhood, apply kernel function and
use kernel function return value for output image.
"""
@@ -59,8 +62,8 @@ cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
cdef Py_ssize_t srows = selem.shape[0]
cdef Py_ssize_t scols = selem.shape[1]
cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y
cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x
cdef Py_ssize_t centre_r = <Py_ssize_t>(selem.shape[0] / 2) + shift_y
cdef Py_ssize_t centre_c = <Py_ssize_t>(selem.shape[1] / 2) + shift_x
# check that structuring element center is inside the element bounding box
assert centre_r >= 0
@@ -68,54 +71,56 @@ cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
assert centre_r < srows
assert centre_c < scols
# define pointers to the data
# add 1 to ensure maximum value is included in histogram -> range(max_bin)
max_bin += 1
cdef dtype_t * out_data = <dtype_t * >out.data
cdef dtype_t * image_data = <dtype_t * >image.data
cdef dtype_t * mask_data = <dtype_t * >mask.data
cdef Py_ssize_t mid_bin = max_bin / 2
# define pointers to the data
cdef char* mask_data = &mask[0, 0]
# define local variable types
cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row
# number of pixels actually inside the neighborhood (float)
cdef float pop
# allocate memory with malloc
cdef Py_ssize_t max_se = srows * scols
# number of element in each attack border
cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w
# number of pixels actually inside the neighborhood (double)
cdef double pop = 0
# the current local histogram distribution
cdef Py_ssize_t * histo = <Py_ssize_t * >malloc(256 * sizeof(Py_ssize_t))
cdef Py_ssize_t* histo = <Py_ssize_t*>malloc(max_bin * sizeof(Py_ssize_t))
for i in range(max_bin):
histo[i] = 0
# these lists contain the relative pixel row and column for each of the 4
# attack borders east, west, north and south e.g. se_e_r lists the rows of
# the east structuring element border
cdef Py_ssize_t * se_e_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_e_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_w_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_w_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_n_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_n_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_s_r = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t * se_s_c = <Py_ssize_t * >malloc(max_se * sizeof(Py_ssize_t))
# build attack and release borders
# by using difference along axis
cdef Py_ssize_t max_se = srows * scols
# number of element in each attack border
cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w
num_se_n = num_se_s = num_se_e = num_se_w = 0
cdef Py_ssize_t* se_e_r = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_e_c = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_w_r = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_w_c = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_n_r = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_n_c = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_s_r = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
cdef Py_ssize_t* se_s_c = <Py_ssize_t*>malloc(max_se * sizeof(Py_ssize_t))
# build attack and release borders by using difference along axis
t = np.hstack((selem, np.zeros((selem.shape[0], 1))))
t_e = np.diff(t, axis=1) < 0
cdef char[:, :] t_e = (np.diff(t, axis=1) < 0).view(np.uint8)
t = np.hstack((np.zeros((selem.shape[0], 1)), selem))
t_w = np.diff(t, axis=1) > 0
cdef char[:, :] t_w = (np.diff(t, axis=1) > 0).view(np.uint8)
t = np.vstack((selem, np.zeros((1, selem.shape[1]))))
t_s = np.diff(t, axis=0) < 0
cdef char[:, :] t_s = (np.diff(t, axis=0) < 0).view(np.uint8)
t = np.vstack((np.zeros((1, selem.shape[1])), selem))
t_n = np.diff(t, axis=0) > 0
num_se_n = num_se_s = num_se_e = num_se_w = 0
cdef char[:, :] t_n = (np.diff(t, axis=0) > 0).view(np.uint8)
for r in range(srows):
for c in range(scols):
@@ -136,92 +141,41 @@ cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
se_s_c[num_se_s] = c - centre_c
num_se_s += 1
# initial population and histogram (kernel is centered on the first row and
# column)
for i in range(256):
histo[i] = 0
pop = 0
for r in range(srows):
for c in range(scols):
rr = r - centre_r
cc = c - centre_c
if selem[r, c]:
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
histogram_increment(histo, &pop, image[rr, cc])
r = 0
c = 0
# kernel -------------------------------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
out[r, c] = <dtype_t_out>kernel(histo, pop, image[r, c], max_bin, mid_bin,
p0, p1, s0, s1)
# kernel -------------------------------------------------------------------
# main loop
r = 0
for even_row in range(0, rows, 2):
# ---> west to east
for c in range(1, cols):
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
histogram_increment(histo, &pop, image[rr, cc])
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s] - 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
histogram_decrement(histo, &pop, image[rr, cc])
# kernel -----------------------------------------------------------
out_data[r * cols + c] = \
kernel(histo, pop, image_data[r * cols + c], p0, p1, s0, s1)
# kernel -----------------------------------------------------------
out[r, c] = <dtype_t_out>kernel(histo, pop, image[r, c],
max_bin, mid_bin, p0, p1, s0, s1)
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel ---------------------------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
p0, p1, s0, s1)
# kernel ---------------------------------------------------------------
# ---> east to west
for c in range(cols - 2, -1, -1):
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s] + 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -----------------------------------------------------------
out_data[r * cols + c] = kernel(
histo, pop, image_data[r * cols + c], p0, p1, s0, s1)
# kernel -----------------------------------------------------------
r += 1 # pass to the next row
r += 1 # pass to the next row
if r >= rows:
break
@@ -230,21 +184,55 @@ cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
histogram_increment(histo, &pop, image[rr, cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
histogram_decrement(histo, &pop, image[rr, cc])
# kernel ---------------------------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
p0, p1, s0, s1)
# kernel ---------------------------------------------------------------
out[r, c] = <dtype_t_out>kernel(histo, pop, image[r, c],
max_bin, mid_bin, p0, p1, s0, s1)
# ---> east to west
for c in range(cols - 2, -1, -1):
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image[rr, cc])
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s] + 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image[rr, cc])
out[r, c] = <dtype_t_out>kernel(histo, pop, image[r, c],
max_bin, mid_bin, p0, p1, s0, s1)
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image[rr, cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image[rr, cc])
out[r, c] = <dtype_t_out>kernel(histo, pop, image[r, c],
max_bin, mid_bin, p0, p1, s0, s1)
# release memory allocated by malloc
free(se_e_r)
free(se_e_c)
free(se_w_r)
@@ -253,5 +241,4 @@ cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float,
free(se_n_c)
free(se_s_r)
free(se_s_c)
free(histo)
+736 -7
View File
@@ -1,11 +1,740 @@
"""The local histogram is computed using a sliding window similar to the method
described in [1]_.
Input image can be 8-bit or 16-bit, for 16-bit input images, the number of
histogram bins is determined from the maximum value present in the image.
Result image is 8-/16-bit or double with respect to the input image and the
rank filter operation.
References
----------
.. [1] Huang, T. ,Yang, G. ; Tang, G.. "A fast two-dimensional
median filtering algorithm", IEEE Transactions on Acoustics, Speech and
Signal Processing, Feb 1979. Volume: 27 , Issue: 1, Page(s): 13 - 18.
"""
import warnings
import numpy as np
from skimage import img_as_ubyte
from . import generic_cy
def find_bitdepth(image):
"""returns the max bith depth of a uint16 image
"""
umax = np.max(image)
if umax > 2:
return int(np.log2(umax))
__all__ = ['autolevel', 'bottomhat', 'equalize', 'gradient', 'maximum', 'mean',
'subtract_mean', 'median', 'minimum', 'modal', 'enhance_contrast',
'pop', 'threshold', 'tophat', 'noise_filter', 'entropy', 'otsu']
def _handle_input(image, selem, out, mask, out_dtype=None):
if image.dtype not in (np.uint8, np.uint16):
image = img_as_ubyte(image)
selem = np.ascontiguousarray(img_as_ubyte(selem > 0))
image = np.ascontiguousarray(image)
if mask is None:
mask = np.ones(image.shape, dtype=np.uint8)
else:
return 1
mask = img_as_ubyte(mask)
mask = np.ascontiguousarray(mask)
if out is None:
if out_dtype is None:
out_dtype = image.dtype
out = np.empty_like(image, dtype=out_dtype)
if image is out:
raise NotImplementedError("Cannot perform rank operation in place.")
is_8bit = image.dtype in (np.uint8, np.int8)
if is_8bit:
max_bin = 255
else:
max_bin = max(4, image.max())
bitdepth = int(np.log2(max_bin))
if bitdepth > 10:
warnings.warn("Bitdepth of %d may result in bad rank filter "
"performance due to large number of bins." % bitdepth)
return image, selem, out, mask, max_bin
def _apply(func, image, selem, out, mask, shift_x, shift_y, out_dtype=None):
image, selem, out, mask, max_bin = _handle_input(image, selem, out, mask,
out_dtype)
func(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
out=out, max_bin=max_bin)
return out
def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Autolevel image using local histogram.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import autolevel
>>> # Load test image
>>> ima = data.camera()
>>> # Stretch image contrast locally
>>> auto = autolevel(ima, disk(20))
"""
return _apply(generic_cy._autolevel, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def bottomhat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns greyscale local bottomhat of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
bottomhat : ndarray (same dtype as input image)
The result of the local bottomhat.
"""
return _apply(generic_cy._bottomhat, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Equalize image using local histogram.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import equalize
>>> # Load test image
>>> ima = data.camera()
>>> # Local equalization
>>> equ = equalize(ima, disk(20))
"""
return _apply(generic_cy._equalize, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def gradient(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local gradient of an image (i.e. local maximum - local
minimum).
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(generic_cy._gradient, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local maximum of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
See also
--------
skimage.morphology.dilation
Note
----
* the lower algorithm complexity makes the rank.maximum() more efficient
for larger images and structuring elements
"""
return _apply(generic_cy._maximum, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def mean(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mean of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import mean
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = mean(ima, disk(20))
"""
return _apply(generic_cy._mean, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def subtract_mean(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Return image subtracted from its local mean.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(generic_cy._subtract_mean, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local median of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import median
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = median(ima, disk(20))
"""
return _apply(generic_cy._median, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local minimum of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
See also
--------
skimage.morphology.erosion
Note
----
* the lower algorithm complexity makes the rank.minimum() more efficient
for larger images and structuring elements
"""
return _apply(generic_cy._minimum, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def modal(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mode of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(generic_cy._modal, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def enhance_contrast(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Enhance an image replacing each pixel by the local maximum if pixel
greylevel is closest to maximimum than local minimum OR local minimum
otherwise.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
Output image.
out : ndarray (same dtype as input image)
The result of the local enhance_contrast.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import enhance_contrast
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = enhance_contrast(ima, disk(20))
"""
return _apply(generic_cy._enhance_contrast, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def pop(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return the number (population) of pixels actually inside the
neighborhood.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> # Local mean
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> rank.pop(ima, square(3))
array([[4, 6, 6, 6, 4],
[6, 9, 9, 9, 6],
[6, 9, 9, 9, 6],
[6, 9, 9, 9, 6],
[4, 6, 6, 6, 4]], dtype=uint8)
"""
return _apply(generic_cy._pop, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local threshold of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
Examples
--------
>>> # Local threshold
>>> from skimage.morphology import square
>>> from skimage.filter.rank import threshold
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> threshold(ima, square(3))
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=uint8)
"""
return _apply(generic_cy._threshold, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def tophat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local tophat of an image.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(generic_cy._tophat, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def noise_filter(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Returns the noise feature as described in [Hashimoto12]_
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
References
----------
.. [Hashimoto12] N. Hashimoto et al. Referenceless image quality evaluation
for whole slide imaging. J Pathol Inform 2012;3:9.
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
# ensure that the central pixel in the structuring element is empty
centre_r = int(selem.shape[0] / 2) + shift_y
centre_c = int(selem.shape[1] / 2) + shift_x
# make a local copy
selem_cpy = selem.copy()
selem_cpy[centre_r, centre_c] = 0
return _apply(generic_cy._noise_filter, image, selem_cpy, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the entropy [1]_ computed locally. Entropy is computed
using base 2 logarithm i.e. the filter returns the minimum number of
bits needed to encode local greylevel distribution.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (double)
Output image.
References
----------
.. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory)>
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import entropy
>>> from skimage.morphology import disk
>>> a8 = data.camera()
>>> ent8 = entropy(a8, disk(5))
"""
return _apply(generic_cy._entropy, image, selem,
out=out, mask=mask, shift_x=shift_x, shift_y=shift_y,
out_dtype=np.double)
def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the Otsu's threshold value for each pixel.
Parameters
----------
image : ndarray
Image array (uint8 array).
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : ndarray (same dtype as input image)
Output image.
References
----------
.. [otsu] http://en.wikipedia.org/wiki/Otsu's_method
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import otsu
>>> from skimage.morphology import disk
>>> # defining a 8-bit test images
>>> a8 = data.camera()
>>> loc_otsu = otsu(a8, disk(5))
>>> thresh_image = a8 >= loc_otsu
"""
return _apply(generic_cy._otsu, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
+506
View File
@@ -0,0 +1,506 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from libc.math cimport log
from .core_cy cimport dtype_t, dtype_t_out, _core
cdef inline double _kernel_autolevel(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, delta
if pop:
for i in range(max_bin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(max_bin):
if histo[i]:
imin = i
break
delta = imax - imin
if delta > 0:
return <double>(max_bin - 1) * (g - imin) / delta
else:
return 0
else:
return 0
cdef inline double _kernel_bottomhat(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(max_bin):
if histo[i]:
break
return g - i
else:
return 0
cdef inline double _kernel_equalize(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t sum = 0
if pop:
for i in range(max_bin):
sum += histo[i]
if i >= g:
break
return ((max_bin - 1) * sum) / pop
else:
return 0
cdef inline double _kernel_gradient(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(max_bin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(max_bin):
if histo[i]:
imin = i
break
return imax - imin
else:
return 0
cdef inline double _kernel_maximum(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(max_bin - 1, -1, -1):
if histo[i]:
return i
else:
return 0
cdef inline double _kernel_mean(Py_ssize_t* histo, double pop,dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t mean = 0
if pop:
for i in range(max_bin):
mean += histo[i] * i
return mean / pop
else:
return 0
cdef inline double _kernel_subtract_mean(Py_ssize_t* histo, double pop,
dtype_t g,
Py_ssize_t max_bin,
Py_ssize_t mid_bin, double p0,
double p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t mean = 0
if pop:
for i in range(max_bin):
mean += histo[i] * i
return (g - mean / pop) / 2. + 127
else:
return 0
cdef inline double _kernel_median(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef double sum = pop / 2.0
if pop:
for i in range(max_bin):
if histo[i]:
sum -= histo[i]
if sum < 0:
return i
else:
return 0
cdef inline double _kernel_minimum(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(max_bin):
if histo[i]:
return i
else:
return 0
cdef inline double _kernel_modal(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t hmax = 0, imax = 0
if pop:
for i in range(max_bin):
if histo[i] > hmax:
hmax = histo[i]
imax = i
return imax
else:
return 0
cdef inline double _kernel_enhance_contrast(Py_ssize_t* histo, double pop,
dtype_t g,
Py_ssize_t max_bin,
Py_ssize_t mid_bin, double p0,
double p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax
if pop:
for i in range(max_bin - 1, -1, -1):
if histo[i]:
imax = i
break
for i in range(max_bin):
if histo[i]:
imin = i
break
if imax - g < g - imin:
return imax
else:
return imin
else:
return 0
cdef inline double _kernel_pop(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
return pop
cdef inline double _kernel_threshold(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t mean = 0
if pop:
for i in range(max_bin):
mean += histo[i] * i
return g > (mean / pop)
else:
return 0
cdef inline double _kernel_tophat(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
if pop:
for i in range(max_bin - 1, -1, -1):
if histo[i]:
break
return i - g
else:
return 0
cdef inline double _kernel_noise_filter(Py_ssize_t* histo, double pop,
dtype_t g, Py_ssize_t max_bin,
Py_ssize_t mid_bin, double p0,
double p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t min_i
# early stop if at least one pixel of the neighborhood has the same g
if histo[g] > 0:
return 0
for i in range(g, -1, -1):
if histo[i]:
break
min_i = g - i
for i in range(g, max_bin):
if histo[i]:
break
if i - g < min_i:
return i - g
else:
return min_i
cdef inline double _kernel_entropy(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef double e, p
if pop:
e = 0.
for i in range(max_bin):
p = histo[i] / pop
if p > 0:
e -= p * log(p) / 0.6931471805599453
return e
else:
return 0
cdef inline double _kernel_otsu(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t max_i
cdef double P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b
cdef double mu = 0.
# compute local mean
if pop:
for i in range(max_bin):
mu += histo[i] * i
mu = mu / pop
else:
return 0
# maximizing the between class variance
max_i = 0
q1 = histo[0] / pop
mu1 = 0.
max_sigma_b = 0.
for i in range(1, max_bin):
P = histo[i] / pop
new_q1 = q1 + P
if new_q1 > 0:
mu1 = (q1 * mu1 + i * P) / new_q1
mu2 = (mu - new_q1 * mu1) / (1. - new_q1)
sigma_b = new_q1 * (1. - new_q1) * (mu1 - mu2) ** 2
if sigma_b > max_sigma_b:
max_sigma_b = sigma_b
max_i = i
q1 = new_q1
return max_i
def _autolevel(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_autolevel[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _bottomhat(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_bottomhat[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _equalize(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_equalize[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _gradient(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_gradient[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _maximum(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_maximum[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_mean[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _subtract_mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_subtract_mean[dtype_t], image, selem, mask,
out, shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _median(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_median[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _minimum(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_minimum[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _enhance_contrast(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_enhance_contrast[dtype_t], image, selem, mask,
out, shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _modal(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_modal[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _pop(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_pop[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _threshold(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_threshold[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _tophat(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_tophat[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _noise_filter(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_noise_filter[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _entropy(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_entropy[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
def _otsu(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, Py_ssize_t max_bin):
_core(_kernel_otsu[dtype_t], image, selem, mask, out,
shift_x, shift_y, 0, 0, 0, 0, max_bin)
@@ -1,17 +1,17 @@
"""Inferior and superior ranks, provided by the user, are passed to the kernel
function to provide a softer version of the rank filters. E.g.
percentile_autolevel will stretch image levels between percentile [p0, p1]
``autolevel_percentile`` will stretch image levels between percentile [p0, p1]
instead of using [min, max]. It means that isolated bright or dark pixels will
not produce halos.
The local histogram is computed using a sliding window similar to the method
described in [1]_.
Input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit), for 16-bit
input images, the number of histogram bins is determined from the maximum value
present in the image.
Input image can be 8-bit or 16-bit, for 16-bit input images, the number of
histogram bins is determined from the maximum value present in the image.
Result image is 8 or 16-bit with respect to the input image.
Result image is 8-/16-bit or double with respect to the input image and the
rank filter operation.
References
----------
@@ -23,55 +23,31 @@ References
"""
import numpy as np
from skimage import img_as_ubyte
from skimage.filter.rank.generic import find_bitdepth
from skimage.filter.rank import _crank16_percentiles, _crank8_percentiles
from . import percentile_cy
from .generic import _handle_input
__all__ = ['percentile_autolevel', 'percentile_gradient',
'percentile_mean', 'percentile_mean_subtraction',
'percentile_morph_contr_enh', 'percentile', 'percentile_pop',
'percentile_threshold']
__all__ = ['autolevel_percentile', 'gradient_percentile',
'mean_percentile', 'subtract_mean_percentile',
'enhance_contrast_percentile', 'percentile', 'pop_percentile',
'threshold_percentile']
def _apply(func8, func16, image, selem, out, mask, shift_x, shift_y, p0, p1):
selem = img_as_ubyte(selem > 0)
image = np.ascontiguousarray(image)
def _apply(func, image, selem, out, mask, shift_x, shift_y, p0, p1,
out_dtype=None):
if mask is None:
mask = np.ones(image.shape, dtype=np.uint8)
else:
mask = np.ascontiguousarray(mask)
mask = img_as_ubyte(mask)
image, selem, out, mask, max_bin = _handle_input(image, selem, out, mask,
out_dtype)
if image is out:
raise NotImplementedError("Cannot perform rank operation in place.")
if image.dtype == np.uint8:
if func8 is None:
raise TypeError("Not implemented for uint8 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint8)
func8(image, selem, shift_x=shift_x, shift_y=shift_y,
mask=mask, out=out, p0=p0, p1=p1)
elif image.dtype == np.uint16:
if func16 is None:
raise TypeError("Not implemented for uint16 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint16)
bitdepth = find_bitdepth(image)
if bitdepth > 11:
raise ValueError("Only uint16 <4096 image (12bit) supported.")
func16(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
bitdepth=bitdepth + 1, out=out, p0=p0, p1=p1)
else:
raise TypeError("Only uint8 and uint16 image supported.")
func(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
out=out, max_bin=max_bin, p0=p0, p1=p1)
return out
def percentile_autolevel(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0, p1=1.):
def autolevel_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local autolevel of an image.
Autolevel is computed on the given structuring element. Only levels between
@@ -79,15 +55,13 @@ def percentile_autolevel(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -100,59 +74,56 @@ def percentile_autolevel(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
local autolevel : uint8 array or uint16
The result of the local autolevel.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(
_crank8_percentiles.autolevel, _crank16_percentiles.autolevel,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_gradient(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0, p1=1.):
"""Return greyscale local percentile_gradient of an image.
percentile_gradient is computed on the given structuring element. Only
levels between percentiles [p0, p1] are used.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
p0, p1 : float in [0, ..., 1]
Define the [p0, p1] percentile interval to be considered for computing
the value.
Returns
-------
local percentile_gradient : uint8 array or uint16
The result of the local percentile_gradient.
"""
return _apply(_crank8_percentiles.gradient, _crank16_percentiles.gradient,
return _apply(percentile_cy._autolevel,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_mean(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0, p1=1.):
def gradient_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local gradient of an image.
gradient is computed on the given structuring element. Only
levels between percentiles [p0, p1] are used.
Parameters
----------
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
p0, p1 : float in [0, ..., 1]
Define the [p0, p1] percentile interval to be considered for computing
the value.
Returns
-------
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(percentile_cy._gradient,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def mean_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local mean of an image.
Mean is computed on the given structuring element. Only levels between
@@ -160,15 +131,13 @@ def percentile_mean(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -181,34 +150,32 @@ def percentile_mean(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
local mean : uint8 array or uint16
The result of the local mean.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(_crank8_percentiles.mean, _crank16_percentiles.mean,
return _apply(percentile_cy._mean,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_mean_subtraction(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=.0, p1=1.):
"""Return greyscale local mean_subtraction of an image.
def subtract_mean_percentile(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=0, p1=1):
"""Return greyscale local subtract_mean of an image.
mean_subtraction is computed on the given structuring element. Only levels
subtract_mean is computed on the given structuring element. Only levels
between percentiles [p0, p1] are used.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -221,36 +188,32 @@ def percentile_mean_subtraction(image, selem, out=None, mask=None,
Returns
-------
local mean_subtraction : uint8 array or uint16
The result of the local mean_subtraction.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(_crank8_percentiles.mean_subtraction,
_crank16_percentiles.mean_subtraction,
return _apply(percentile_cy._subtract_mean,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_morph_contr_enh(
image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0, p1=1.):
"""Return greyscale local morph_contr_enh of an image.
def enhance_contrast_percentile(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=0, p1=1):
"""Return greyscale local enhance_contrast of an image.
morph_contr_enh is computed on the given structuring element. Only levels
enhance_contrast is computed on the given structuring element. Only levels
between percentiles [p0, p1] are used.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -263,19 +226,18 @@ def percentile_morph_contr_enh(
Returns
-------
local morph_contr_enh : uint8 array or uint16
The result of the local morph_contr_enh.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(_crank8_percentiles.morph_contr_enh,
_crank16_percentiles.morph_contr_enh,
return _apply(percentile_cy._enhance_contrast,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
p0=.0):
p0=0):
"""Return greyscale local percentile of an image.
percentile is computed on the given structuring element. Returns the value
@@ -283,15 +245,13 @@ def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -303,19 +263,18 @@ def percentile(image, selem, out=None, mask=None, shift_x=False, shift_y=False,
Returns
-------
local percentile : uint8 array or uint16
The result of the local percentile.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(_crank8_percentiles.percentile,
_crank16_percentiles.percentile,
return _apply(percentile_cy._percentile,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=0.)
def percentile_pop(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0, p1=1.):
def pop_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0, p1=1):
"""Return greyscale local pop of an image.
pop is computed on the given structuring element. Only levels between
@@ -323,15 +282,13 @@ def percentile_pop(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -344,18 +301,18 @@ def percentile_pop(image, selem, out=None, mask=None, shift_x=False,
Returns
-------
local pop : uint8 array or uint16
The result of the local pop.
out : ndarray (same dtype as input image)
Output image.
"""
return _apply(_crank8_percentiles.pop, _crank16_percentiles.pop,
return _apply(percentile_cy._pop,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_threshold(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=.0):
def threshold_percentile(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, p0=0):
"""Return greyscale local threshold of an image.
threshold is computed on the given structuring element. Returns
@@ -365,15 +322,13 @@ def percentile_threshold(image, selem, out=None, mask=None, shift_x=False,
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, as the
algorithm uses max. 12bit histogram, an exception will be raised if
image has a value > 4095.
image : ndarray (uint8, uint16)
Image array.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
out : ndarray (same dtype as input)
If None, a new array will be allocated.
mask : ndarray (uint8)
mask : ndarray
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
@@ -383,14 +338,13 @@ def percentile_threshold(image, selem, out=None, mask=None, shift_x=False,
p0 : float in [0, ..., 1]
Set the percentile value.
Returns
-------
local threshold : uint8 array or uint16
out : ndarray (same dtype as input image)
Output image.
local threshold : ndarray (same dtype as input)
The result of the local threshold.
"""
return _apply(
_crank8_percentiles.threshold, _crank16_percentiles.threshold,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=0.)
return _apply(percentile_cy._threshold,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=0)
+301
View File
@@ -0,0 +1,301 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
from .core_cy cimport dtype_t, dtype_t_out, _core, _min, _max
cdef inline double _kernel_autolevel(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(max_bin):
sum += histo[i]
if sum > p0 * pop:
imin = i
break
sum = 0
for i in range(max_bin - 1, -1, -1):
sum += histo[i]
if sum > p1 * pop:
imax = i
break
delta = imax - imin
if delta > 0:
return <double>(max_bin - 1) * (_min(_max(imin, g), imax)
- imin) / delta
else:
return imax - imin
else:
return 0
cdef inline double _kernel_gradient(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(max_bin):
sum += histo[i]
if sum >= p0 * pop:
imin = i
break
sum = 0
for i in range(max_bin - 1, -1, -1):
sum += histo[i]
if sum >= p1 * pop:
imax = i
break
return imax - imin
else:
return 0
cdef inline double _kernel_mean(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(max_bin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return mean / n
else:
return 0
else:
return 0
cdef inline double _kernel_subtract_mean(Py_ssize_t* histo, double pop,
dtype_t g,
Py_ssize_t max_bin,
Py_ssize_t mid_bin, double p0,
double p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i, sum, mean, n
if pop:
sum = 0
mean = 0
n = 0
for i in range(max_bin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
mean += histo[i] * i
if n > 0:
return (g - (mean / n)) * .5 + mid_bin
else:
return 0
else:
return 0
cdef inline double _kernel_enhance_contrast(Py_ssize_t* histo, double pop,
dtype_t g,
Py_ssize_t max_bin,
Py_ssize_t mid_bin, double p0,
double p1, Py_ssize_t s0,
Py_ssize_t s1):
cdef Py_ssize_t i, imin, imax, sum, delta
if pop:
sum = 0
p1 = 1.0 - p1
for i in range(max_bin):
sum += histo[i]
if sum > p0 * pop:
imin = i
break
sum = 0
for i in range(max_bin - 1, -1, -1):
sum += histo[i]
if sum > p1 * pop:
imax = i
break
if g > imax:
return imax
if g < imin:
return imin
if imax - g < g - imin:
return imax
else:
return imin
else:
return 0
cdef inline double _kernel_percentile(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef Py_ssize_t sum = 0
if pop:
if p0 == 1: # make sure p0 = 1 returns the maximum filter
for i in range(max_bin - 1, -1, -1):
if histo[i]:
break
else:
for i in range(max_bin):
sum += histo[i]
if sum > p0 * pop:
break
return i
else:
return 0
cdef inline double _kernel_pop(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i, sum, n
if pop:
sum = 0
n = 0
for i in range(max_bin):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
n += histo[i]
return n
else:
return 0
cdef inline double _kernel_threshold(Py_ssize_t* histo, double pop, dtype_t g,
Py_ssize_t max_bin, Py_ssize_t mid_bin,
double p0, double p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i
cdef Py_ssize_t sum = 0
if pop:
for i in range(max_bin):
sum += histo[i]
if sum >= p0 * pop:
break
return (max_bin - 1) * (g >= i)
else:
return 0
def _autolevel(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_autolevel[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _gradient(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_gradient[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_mean[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _subtract_mean(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_subtract_mean[dtype_t], image, selem, mask,
out, shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _enhance_contrast(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_enhance_contrast[dtype_t], image, selem, mask,
out, shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _percentile(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_percentile[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, 1, 0, 0, max_bin)
def _pop(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_pop[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, p1, 0, 0, max_bin)
def _threshold(dtype_t[:, ::1] image,
char[:, ::1] selem,
char[:, ::1] mask,
dtype_t_out[:, ::1] out,
char shift_x, char shift_y, double p0, double p1,
Py_ssize_t max_bin):
_core(_kernel_threshold[dtype_t], image, selem, mask, out,
shift_x, shift_y, p0, 1, 0, 0, max_bin)
-769
View File
@@ -1,769 +0,0 @@
"""The local histogram is computed using a sliding window similar to the method
described in [1]_.
Input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit), for 16-bit
input images, the number of histogram bins is determined from the maximum value
present in the image.
Result image is 8 or 16-bit with respect to the input image.
References
----------
.. [1] Huang, T. ,Yang, G. ; Tang, G.. "A fast two-dimensional
median filtering algorithm", IEEE Transactions on Acoustics, Speech and
Signal Processing, Feb 1979. Volume: 27 , Issue: 1, Page(s): 13 - 18.
"""
import numpy as np
from skimage import img_as_ubyte
from skimage.filter.rank import _crank8, _crank16
from skimage.filter.rank.generic import find_bitdepth
__all__ = ['autolevel', 'bottomhat', 'equalize', 'gradient', 'maximum', 'mean',
'meansubtraction', 'median', 'minimum', 'modal', 'morph_contr_enh',
'pop', 'threshold', 'tophat', 'noise_filter', 'entropy', 'otsu']
def _apply(func8, func16, image, selem, out, mask, shift_x, shift_y):
selem = img_as_ubyte(selem > 0)
image = np.ascontiguousarray(image)
if mask is None:
mask = np.ones(image.shape, dtype=np.uint8)
else:
mask = np.ascontiguousarray(mask)
mask = img_as_ubyte(mask)
if image is out:
raise NotImplementedError("Cannot perform rank operation in place.")
if image.dtype == np.uint8:
if func8 is None:
raise TypeError("Not implemented for uint8 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint8)
func8(image, selem, shift_x=shift_x, shift_y=shift_y,
mask=mask, out=out)
elif image.dtype == np.uint16:
if func16 is None:
raise TypeError("Not implemented for uint16 image.")
if out is None:
out = np.zeros(image.shape, dtype=np.uint16)
bitdepth = find_bitdepth(image)
if bitdepth > 11:
raise ValueError("Only uint16 <4096 image (12bit) supported.")
func16(image, selem, shift_x=shift_x, shift_y=shift_y, mask=mask,
bitdepth=bitdepth + 1, out=out)
else:
raise TypeError("Only uint8 and uint16 image supported.")
return out
def autolevel(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Autolevel image using local histogram.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The result of the local autolevel.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import autolevel
>>> # Load test image
>>> ima = data.camera()
>>> # Stretch image contrast locally
>>> auto = autolevel(ima, disk(20))
"""
return _apply(_crank8.autolevel, _crank16.autolevel, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def bottomhat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns greyscale local bottomhat of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
local bottomhat : uint8 array or uint16 array depending on input image
The result of the local bottomhat.
"""
return _apply(_crank8.bottomhat, _crank16.bottomhat, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def equalize(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Equalize image using local histogram.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The result of the local equalize.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import equalize
>>> # Load test image
>>> ima = data.camera()
>>> # Local equalization
>>> equ = equalize(ima, disk(20))
"""
return _apply(_crank8.equalize, _crank16.equalize, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def gradient(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local gradient of an image (i.e. local maximum - local
minimum).
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local gradient.
"""
return _apply(_crank8.gradient, _crank16.gradient, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def maximum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local maximum of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local maximum.
See also
--------
skimage.morphology.dilation
Note
----
* input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit)
* the lower algorithm complexity makes the rank.maximum() more efficient for
larger images and structuring elements
"""
return _apply(_crank8.maximum, _crank16.maximum, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def mean(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mean of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local mean.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import mean
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = mean(ima, disk(20))
"""
return _apply(_crank8.mean, _crank16.mean, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def meansubtraction(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Return image subtracted from its local mean.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The result of the local meansubtraction.
"""
return _apply(_crank8.meansubtraction, _crank16.meansubtraction, image,
selem, out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def median(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local median of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local median.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import median
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = median(ima, disk(20))
"""
return _apply(_crank8.median, _crank16.median, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def minimum(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local minimum of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local minimum.
See also
--------
skimage.morphology.erosion
Note
----
* input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit)
* the lower algorithm complexity makes the rank.minimum() more efficient
for larger images and structuring elements
"""
return _apply(_crank8.minimum, _crank16.minimum, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def modal(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local mode of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The local modal.
"""
return _apply(_crank8.modal, _crank16.modal, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def morph_contr_enh(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Enhance an image replacing each pixel by the local maximum if pixel
greylevel is closest to maximimum than local minimum OR local minimum
otherwise.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The result of the local morph_contr_enh.
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import morph_contr_enh
>>> # Load test image
>>> ima = data.camera()
>>> # Local mean
>>> avg = morph_contr_enh(ima, disk(20))
"""
return _apply(_crank8.morph_contr_enh, _crank16.morph_contr_enh, image,
selem, out=out, mask=mask, shift_x=shift_x, shift_y=shift_y)
def pop(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return the number (population) of pixels actually inside the
neighborhood.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The number of pixels belonging to the neighborhood.
Examples
--------
>>> # Local mean
>>> from skimage.morphology import square
>>> import skimage.filter.rank as rank
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> rank.pop(ima, square(3))
array([[4, 6, 6, 6, 4],
[6, 9, 9, 9, 6],
[6, 9, 9, 9, 6],
[6, 9, 9, 9, 6],
[4, 6, 6, 6, 4]], dtype=uint8)
"""
return _apply(_crank8.pop, _crank16.pop, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def threshold(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local threshold of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The result of the local threshold.
Examples
--------
>>> # Local threshold
>>> from skimage.morphology import square
>>> from skimage.filter.rank import threshold
>>> ima = 255 * np.array([[0, 0, 0, 0, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 1, 1, 1, 0],
... [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> threshold(ima, square(3))
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=uint8)
"""
return _apply(_crank8.threshold, _crank16.threshold, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def tophat(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Return greyscale local tophat of an image.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
The image tophat.
"""
return _apply(_crank8.tophat, _crank16.tophat, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def noise_filter(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Returns the noise feature as described in [Hashimoto12]_
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
References
----------
.. [Hashimoto12] N. Hashimoto et al. Referenceless image quality evaluation
for whole slide imaging. J Pathol Inform 2012;3:9.
Returns
-------
out : uint8 array or uint16 array (same as input image)
The image noise.
"""
# ensure that the central pixel in the structuring element is empty
centre_r = int(selem.shape[0] / 2) + shift_y
centre_c = int(selem.shape[1] / 2) + shift_x
# make a local copy
selem_cpy = selem.copy()
selem_cpy[centre_r, centre_c] = 0
return _apply(_crank8.noise_filter, None, image, selem_cpy, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the entropy [1]_ computed locally. Entropy is computed
using base 2 logarithm i.e. the filter returns the minimum number of
bits needed to encode local greylevel distribution.
Parameters
----------
image : ndarray
Image array (uint8 array or uint16). If image is uint16, the algorithm
uses max. 12bit histogram, an exception will be raised if image has a
value > 4095.
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array or uint16 array (same as input image)
entropy x10 (uint8 images) and entropy x1000 (uint16 images)
References
----------
.. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory)
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import entropy
>>> from skimage.morphology import disk
>>> # defining a 8- and a 16-bit test images
>>> a8 = data.camera()
>>> a16 = data.camera().astype(np.uint16) * 4
>>> # pixel values contain 10x the local entropy
>>> ent8 = entropy(a8, disk(5))
>>> # pixel values contain 1000x the local entropy
>>> ent16 = entropy(a16, disk(5))
"""
return _apply(_crank8.entropy, _crank16.entropy, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)
def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False):
"""Returns the Otsu's threshold value for each pixel.
Parameters
----------
image : ndarray
Image array (uint8 array).
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
If None, a new array will be allocated.
mask : ndarray (uint8)
Mask array that defines (>0) area of the image included in the local
neighborhood. If None, the complete image is used (default).
shift_x, shift_y : int
Offset added to the structuring element center point. Shift is bounded
to the structuring element sizes (center must be inside the given
structuring element).
Returns
-------
out : uint8 array
Otsu's threshold values
References
----------
.. [otsu] http://en.wikipedia.org/wiki/Otsu's_method
Notes
-----
* input image are 8-bit only
Examples
--------
>>> # Local entropy
>>> from skimage import data
>>> from skimage.filter.rank import otsu
>>> from skimage.morphology import disk
>>> # defining a 8-bit test images
>>> a8 = data.camera()
>>> loc_otsu = otsu(a8, disk(5))
>>> thresh_image = a8 >= loc_otsu
"""
return _apply(_crank8.otsu, None, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y)

Some files were not shown because too many files have changed in this diff Show More