mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-27 11:27:08 +08:00
Merge branch 'master' of git://github.com/scikit-image/scikit-image into filter_median_dtypes
This commit is contained in:
+6
-2
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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>``,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>`__.
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
"""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
cython>=0.17
|
||||
matplotlib>=1.0
|
||||
numpy>=1.6
|
||||
@@ -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
@@ -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 *
|
||||
|
||||
@@ -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, {})
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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__
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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 |
@@ -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()
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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, "Rudin–Osher–Fatemi Total Variation Denoising
|
||||
using Split Bregman" in Image Processing On Line on 2012–05–19,
|
||||
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]))
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 *
|
||||
@@ -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)
|
||||
@@ -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 *
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user