Merge tag 'v0.8.0' into releases

* tag 'v0.8.0': (625 commits)
  Fix release instruction for making docs
  Update docversion for 0.8
  Update version number for 0.8
  Add release notes for version 0.8
  DOC: Fix typo in remove_small_objects docstring.
  Improve performance of normalization
  Change color of circles for better visibility
  Remove unnecessary statement breaks
  Improve performance and usage of data types of hough circle transformation
  STY: Minor cleanup.
  Add specific error messages and exceptions
  Removing unnecessary typedefs for uint16_t in MSVC build
  Change data type of index variables
  Fix missed rename in example
  Add raise and labeled image tests
  Rename function to remove_small_objects
  Improve docstring for remove_small_connected_components
  Modify example to use remove_small_connected_components
  Add tests for remove_small_connected_components
  Add examples to docstring
  ...
This commit is contained in:
Yaroslav Halchenko
2013-06-03 10:25:40 -04:00
151 changed files with 10571 additions and 1697 deletions
+2 -1
View File
@@ -22,4 +22,5 @@ doc/source/auto_examples/images/plot_*.png
doc/source/auto_examples/images/thumb
doc/source/auto_examples/applications/
doc/source/_static/random.js
.idea/
*.log
+6 -5
View File
@@ -7,16 +7,17 @@
language: erlang
env:
- PYTHON=python PYSUF=''
# - PYTHON=python3 PYSUF=3 : python3-numpy not currently available
- PYTHON=python PYSUF='' PYVER=2.7
- PYTHON=python3 PYSUF='3' PYVER=3.2
install:
# - sudo apt-get build-dep $PYTHON-numpy
- sudo apt-get update # needed for python3-numpy
- sudo apt-get install $PYTHON-dev
- sudo apt-get install $PYTHON-numpy
- sudo apt-get install $PYTHON-scipy
- sudo apt-get install $PYTHON-setuptools
- sudo apt-get install $PYTHON-nose
- sudo apt-get install cython
- sudo easy_install$PYSUF pip
- sudo pip-$PYVER install cython
- sudo apt-get install libfreeimage3
- $PYTHON setup.py build
- sudo $PYTHON setup.py install
@@ -24,5 +25,5 @@ script:
# Change into an innocuous directory and find tests from installation
- mkdir for_test
- cd for_test
- nosetests --exe -v --cover-package=skimage skimage
- nosetests-$PYVER --exe -v --cover-package=skimage skimage
+19
View File
@@ -117,3 +117,22 @@
- Petter Strandmark
Perimeter calculation in regionprops.
- Olivier Debeir
Rank filters (8- and 16-bits) using sliding window.
- Luis Pedro Coelho
imread plugin
- Steven Silvester, Karel Zuiderveld
Adaptive Histogram Equalization
- Anders Boesen Lindbo Larsen
Dense DAISY feature description, circle perimeter drawing.
- François Boulogne
Andres Method for circle perimeter, ellipse perimeter drawing.
Circular Hough Transform
- Thouis Jones
Vectorized operators for arrays of 16-bit ints.
+21 -12
View File
@@ -1,8 +1,7 @@
Development process
-------------------
:doc:`Read this overview <gitwash/index>` of how to use Git with
``skimage``. Here's the long and short of it:
Here's the long and short of it:
* Go to `https://github.com/scikit-image/scikit-image
<http://github.com/scikit-image/scikit-image>`_ and follow the
@@ -11,9 +10,12 @@ Development process
branch name will appear in the merge message, use a sensible name
such as 'transform-speedups'.
* Commit locally as you progress.
* Push your changes back to github and create a pull request by
clicking "request pull" in GitHub.
* Optionally, mail the mailing list, explaining your changes.
* Push your changes back to GitHub and create a Pull Request by
clicking 'Pull Request' in GitHub.
* Optionally, post on the `mailing list <http://groups.google.com/group/scikit-image>`_ to explain your changes.
Read these :doc:`detailed documents <gitwash/index>` on how to use Git with
``scikit-image`` (`<http://scikit-image.org/docs/dev/gitwash/index.html>`_).
.. note::
@@ -33,8 +35,9 @@ project on how they manage to keep review overhead to a minimum:
http://mail.scipy.org/pipermail/ipython-dev/2010-October/006746.html
Guidelines
``````````
* All code should have tests (see "Test coverage" below for more details).
----------
* All code should have tests (see `test coverage`_ below for more details).
* All code should be documented, to the same
`standard <http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines>`_
as NumPy and SciPy. For new functionality, always add an example to the
@@ -47,7 +50,8 @@ Guidelines
* Examples in the gallery should have a maximum figure width of 8 inches.
Stylistic Guidelines
````````````````````
--------------------
* Use numpy data types instead of strings (``np.uint8`` instead of
``"uint8"``).
@@ -77,10 +81,14 @@ Stylistic Guidelines
hough(canny(my_image))
* Use `Py_ssize_t` as data type for all indexing, shape and size variables in
C/C++ and Cython code.
Test coverage
`````````````
-------------
Tests for a module should ideally cover all code in that module,
i.e. statement coverage should be at 100%.
i.e., statement coverage should be at 100%.
To measure the test coverage, install
`coverage.py <http://nedbatchelder.com/code/coverage/>`__
@@ -98,5 +106,6 @@ detailing the test coverage::
...
Bugs
````
Please `report bugs on Github <https://github.com/scikit-image/scikit-image/issues>`_.
----
Please `report bugs on GitHub <https://github.com/scikit-image/scikit-image/issues>`_.
-5
View File
@@ -29,8 +29,3 @@ this path to your PYTHONPATH variable and compiling the extensions:
License
-------
Please read LICENSE.txt in this directory.
Contact
-------
Stefan van der Walt <stefan at sun.ac.za>
+1 -1
View File
@@ -12,7 +12,7 @@ How to make a new release of ``skimage``
- Edit ``doc/source/themes/agogo/static/docversions.js`` and commit
- Build a clean version of the docs. Run ``make`` in the root dir, then
``rm build -rf; make html`` in the docs.
``rm -rf build; make html`` in the docs.
- Run ``make html`` again to copy the newly generated ``random.js`` into
place. Double check ``random.js``, otherwise the skimage.org front
page gets broken!
+4 -4
View File
@@ -15,14 +15,14 @@ How to contribute to ``skimage``
cell_profiler
Developing Open Source is great fun! Join us on the `skimage mailing
Developing Open Source is great fun! Join us on the `scikit-image mailing
list <http://groups.google.com/group/scikit-image>`_ and tell us which of the
following challenges you'd like to solve.
* Mentoring is available for those new to scientific programming in Python.
* The technical detail of the `development process`_ is given below.
* :doc:`How to use GitHub <gitwash/index>` when developing skimage
* If you're looking something to implement, you can find a list of `requested features on github <https://github.com/scikit-image/scikit-image/wiki/Requested-features>`__. In addition, you can browse the `open issues on github <https://github.com/scikit-image/scikit-image/issues?state=open>`__.
* If you're looking for something to implement, you can find a list of `requested features on GitHub <https://github.com/scikit-image/scikit-image/wiki/Requested-features>`__. In addition, you can browse the `open issues on GitHub <https://github.com/scikit-image/scikit-image/issues?state=open>`__.
* The technical detail of the `development process`_ is summed up below.
Refer to the :doc:`gitwash <gitwash/index>` for a step-by-step tutorial.
.. contents::
:local:
+37 -1
View File
@@ -1,5 +1,5 @@
Name: scikit-image
Version: 0.7.2
Version: 0.8.0
Summary: Image processing routines for SciPy
Url: http://scikit-image.org
DownloadUrl: http://github.com/scikit-image/scikit-image
@@ -64,6 +64,9 @@ Library:
Extension: skimage.filter._ctmf
Sources:
skimage/filter/_ctmf.pyx
Extension: skimage.filter._denoise_cy
Sources:
skimage/filter/_denoise_cy.pyx
Extension: skimage.morphology.ccomp
Sources:
skimage/morphology/ccomp.pyx
@@ -88,6 +91,9 @@ Library:
Extension: skimage.morphology._greyreconstruct
Sources:
skimage/morphology/_greyreconstruct.pyx
Extension: skimage.feature.corner_cy
Sources:
skimage/feature/corner_cy.pyx
Extension: skimage.feature._texture
Sources:
skimage/feature/_texture.pyx
@@ -115,6 +121,36 @@ Library:
Extension: skimage._shared.geometry
Sources:
skimage/_shared/geometry.pyx
Extension: skimage.filter.rank._core16
Sources:
skimage/filter/rank/_core16.pyx
Extension: skimage.filter.rank._crank8
Sources:
skimage/filter/rank/_crank8.pyx
Extension: skimage.filter.rank._crank16
Sources:
skimage/filter/rank/_crank16.pyx
Extension: skimage.filter.rank._core8
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
Executable: skivi
Module: skimage.scripts.skivi
+24 -25
View File
@@ -5,7 +5,7 @@ import os
import re
RE_CYTHON = re.compile("config.add_extension\(['\"]([\S]+)['\"]")
RE_CYTHON = re.compile("config.add_extension\(\s*['\"]([\S]+)['\"]")
BENTO_TEMPLATE = """
Extension: {module_path}
@@ -23,7 +23,7 @@ def each_setup_in_pkg(top_dir):
def each_cy_in_setup(top_dir):
"""Yield path and name for each cython extension package's setup file."""
"""Yield path for each cython extension package's setup file."""
for dir_path, f in each_setup_in_pkg(top_dir):
text = f.read()
match = RE_CYTHON.findall(text)
@@ -38,30 +38,27 @@ def each_cy_in_setup(top_dir):
else:
path = dir_path
full_path = os.path.join(path, cy_file)
yield full_path, cy_file
yield full_path
def each_cy_in_bento(bento_file='bento.info'):
"""Yield path and name for each cython extension in bento info file."""
"""Yield path for each cython extension in bento info file."""
with open(bento_file) as f:
for line in f:
line = line.strip()
if line.startswith('Extension:'):
parts = line.split('.')
ext_name = parts[-1]
path = line.lstrip('Extension:').strip()
yield path, ext_name
yield path
def remove_common_extensions(cy_bento, cy_setup):
for ext_name in cy_bento.keys():
if ext_name in cy_setup:
spath = cy_setup.pop(ext_name)
bpath = cy_bento.pop(ext_name)
if not spath.replace(os.path.sep, '.') == bpath:
print "Mismatched paths:"
print " setup.py: ", spath
print " bento.info:", bpath
# normalize so that cy_setup and cy_bento have the same separator
cy_setup = set(ext.replace('/', '.') for ext in cy_setup)
cy_setup_diff = cy_setup.difference(cy_bento)
cy_setup_diff = set(ext.replace('.', '/') for ext in cy_setup_diff)
cy_bento_diff = cy_bento.difference(cy_setup)
return cy_bento_diff, cy_setup_diff
def print_results(cy_bento, cy_setup):
def info(text):
@@ -69,28 +66,30 @@ def print_results(cy_bento, cy_setup):
print(text)
print('-' * len(text))
print "Bento errors:"
print "-------------"
if not (cy_bento or cy_setup):
print "bento.info and setup.py files match."
if cy_bento:
info("The following extensions in 'bento.info' were not found:")
print('\n'.join(cy_bento.keys()))
info("Extensions found in 'bento.info' but not in any 'setup.py:")
print('\n'.join(cy_bento))
if cy_setup:
info("The following cython files exist but were not in 'bento.info':")
info("Extensions found in a 'setup.py' but not in any 'bento.info:")
print('\n'.join(cy_setup))
info("Consider adding the following to the 'bento.info' Library:")
for ext_name, dir_path in cy_setup.iteritems():
print BENTO_TEMPLATE.format(module_path=dir_path.replace('/', '.'),
for dir_path in cy_setup:
module_path = dir_path.replace('/', '.')
print BENTO_TEMPLATE.format(module_path=module_path,
dir_path=dir_path)
if __name__ == '__main__':
# All cython extensions defined in 'setup.py' files.
cy_setup = dict((ext, path) for path, ext in each_cy_in_setup('skimage'))
cy_setup = set(each_cy_in_setup('skimage'))
# All cython extensions defined 'bento.info' file.
cy_bento = dict((ext, path) for path, ext in each_cy_in_bento())
cy_bento = set(each_cy_in_bento())
remove_common_extensions(cy_bento, cy_setup)
cy_bento, cy_setup = remove_common_extensions(cy_bento, cy_setup)
print_results(cy_bento, cy_setup)
View File
@@ -90,12 +90,8 @@ plt.title('Filling the holes')
Small spurious objects are easily removed by setting a minimum size for valid
objects.
"""
label_objects, nb_labels = ndimage.label(fill_coins)
sizes = np.bincount(label_objects.ravel())
mask_sizes = sizes > 20
mask_sizes[0] = 0
coins_cleaned = mask_sizes[label_objects]
from skimage import morphology
coins_cleaned = morphology.remove_small_objects(fill_coins, 21)
plt.figure(figsize=(4, 3))
plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest')
@@ -149,8 +145,7 @@ plt.title('markers')
Finally, we use the watershed transform to fill regions of the elevation map starting from the markers determined above:
"""
from skimage.morphology import watershed
segmentation = watershed(elevation_map, markers)
segmentation = morphology.watershed(elevation_map, markers)
plt.figure(figsize=(4, 3))
plt.imshow(segmentation, cmap=plt.cm.gray, interpolation='nearest')
@@ -0,0 +1,719 @@
"""
============
Rank filters
============
Rank filters are non-linear filters using the local greylevels 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.
Rank filters can be used for several purposes such as:
* image quality enhancement
e.g. image smoothing, sharpening
* image pre-processing
e.g. noise reduction, contrast enhancement
* feature extraction
e.g. border detection, isolated point detection
* post-processing
e.g. small object removal, object grouping, contour smoothing
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`.
.. [1] Pierre Soille, On morphological operators based on rank filters, Pattern
Recognition 35 (2002) 527-535.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
ima = data.camera()
hist = np.histogram(ima, 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.axis('off')
plt.subplot(1, 2, 2)
plt.plot(hist[1][:-1], hist[0], lw=2)
plt.title('histogram of grey values')
"""
.. image:: PLOT2RST.current_figure
Noise removal
=============
Some noise is added to the image, 1% of pixels are randomly set to 255, 1% are
randomly set to 0. The **median** filter is applied to remove the noise.
.. note::
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])
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.subplot(2, 2, 2)
plt.imshow(lo, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=1$')
plt.subplot(2, 2, 3)
plt.imshow(hi, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=5$')
plt.subplot(2, 2, 4)
plt.imshow(ext, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('median $r=20$')
"""
.. 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.
Image smoothing
================
The example hereunder shows how a local **mean** smoothes the camera man image.
"""
from skimage.filter.rank import mean
fig = plt.figure(figsize=[10, 7])
loc_mean = mean(nima, disk(10))
plt.subplot(1, 2, 1)
plt.imshow(ima, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('original')
plt.subplot(1, 2, 2)
plt.imshow(loc_mean, cmap=plt.cm.gray, vmin=0, vmax=255)
plt.xlabel('local mean $r=10$')
"""
.. image:: PLOT2RST.current_figure
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
the central one.
.. note::
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)
bilat = bilateral_mean(ima.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.subplot(2, 2, 3)
plt.imshow(bilat, cmap=plt.cm.gray)
plt.xlabel('bilateral mean')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.subplot(2, 2, 4)
plt.imshow(bilat[200:350, 350:450], cmap=plt.cm.gray)
"""
.. image:: PLOT2RST.current_figure
One can see that the large continuous part of the image (e.g. sky) is smoothed
whereas other details are preserved.
Contrast enhancement
====================
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.
.. [2] http://en.wikipedia.org/wiki/Histogram_equalization
.. [3] http://en.wikipedia.org/wiki/Adaptive_histogram_equalization
"""
from skimage import exposure
from skimage.filter import rank
ima = data.camera()
# equalize globally and locally
glob = exposure.equalize(ima) * 255
loc = rank.equalize(ima, disk(20))
# extract histogram for each image
hist = np.histogram(ima, 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.axis('off')
plt.subplot(322)
plt.plot(hist[1][:-1], hist[0], lw=2)
plt.title('histogram of grey values')
plt.subplot(323)
plt.imshow(glob, cmap=plt.cm.gray, 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.subplot(325)
plt.imshow(loc, cmap=plt.cm.gray, 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')
"""
.. 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.
The following example shows how local autolevel enhances the camara man picture.
"""
from skimage.filter.rank import autolevel
ima = data.camera()
selem = disk(10)
auto = autolevel(ima.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.subplot(1, 2, 2)
plt.imshow(auto, cmap=plt.cm.gray)
plt.xlabel('local autolevel')
"""
.. 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.
"""
from skimage.filter.rank import percentile_autolevel
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)
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')
ax1.imshow(
np.hstack((loc_perc_autolevel0, loc_perc_autolevel1)), vmin=0, vmax=255)
ax1.set_title('percentile autolevel 0%,1%')
ax2.imshow(
np.hstack((loc_perc_autolevel2, loc_perc_autolevel3)), vmin=0, vmax=255)
ax2.set_title('percentile autolevel 5% and 10%')
for ax in axes:
ax.axis('off')
"""
.. 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.
"""
from skimage.filter.rank import morph_contr_enh
ima = data.camera()
enh = morph_contr_enh(ima, 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.subplot(2, 2, 3)
plt.imshow(enh, cmap=plt.cm.gray)
plt.xlabel('local morphlogical contrast enhancement')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.subplot(2, 2, 4)
plt.imshow(enh[200:350, 350:450], cmap=plt.cm.gray)
"""
.. image:: PLOT2RST.current_figure
The percentile version of the local morphological contrast enhancement uses
percentile *p0* and *p1* instead of the local minimum and maximum.
"""
from skimage.filter.rank import percentile_morph_contr_enh
ima = data.camera()
penh = percentile_morph_contr_enh(ima, 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.subplot(2, 2, 3)
plt.imshow(penh, cmap=plt.cm.gray)
plt.xlabel('local percentile morphlogical\n contrast enhancement')
plt.subplot(2, 2, 2)
plt.imshow(ima[200:350, 350:450], cmap=plt.cm.gray)
plt.subplot(2, 2, 4)
plt.imshow(penh[200:350, 350:450], cmap=plt.cm.gray)
"""
.. image:: PLOT2RST.current_figure
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 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`.
.. [1] http://en.wikipedia.org/wiki/Otsu's_method
"""
from skimage.filter.rank import otsu
from skimage.filter import threshold_otsu
p8 = data.page()
radius = 10
selem = disk(radius)
# t_loc_otsu is an image
t_loc_otsu = otsu(p8, selem)
loc_otsu = p8 >= t_loc_otsu
# t_glob_otsu is a scalar
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.colorbar()
plt.subplot(2, 2, 2)
plt.imshow(t_loc_otsu, cmap=plt.cm.gray)
plt.xlabel('local Otsu ($radius=%d$)' % radius)
plt.colorbar()
plt.subplot(2, 2, 3)
plt.imshow(p8 >= t_loc_otsu, cmap=plt.cm.gray)
plt.xlabel('original>=local Otsu' % t_glob_otsu)
plt.subplot(2, 2, 4)
plt.imshow(glob_otsu, cmap=plt.cm.gray)
plt.xlabel('global Otsu ($t=%d$)' % t_glob_otsu)
"""
.. image:: PLOT2RST.current_figure
The following example shows how local Otsu's threshold handles a global level
shift applied to a synthetic image .
"""
n = 100
theta = np.linspace(0, 10 * np.pi, n)
x = np.sin(theta)
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.subplot(1, 2, 2)
plt.imshow(m >= t, interpolation='nearest')
plt.xlabel('local Otsu ($radius=%d$)' % radius)
"""
.. image:: PLOT2RST.current_figure
Image morphology
================
Local maximum and local minimum are the base operators for greylevel
morphology.
.. note::
`skimage.dilate` and `skimage.erode` are equivalent filters (see below for
comparison).
Here is an example of the classical morphological greylevel filters: opening,
closing and morphological gradient.
"""
from skimage.filter.rank import maximum, minimum, gradient
ima = data.camera()
closing = maximum(minimum(ima, disk(5)), disk(5))
opening = minimum(maximum(ima, disk(5)), disk(5))
grad = gradient(ima, 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.subplot(2, 2, 2)
plt.imshow(closing, cmap=plt.cm.gray)
plt.xlabel('greylevel closing')
plt.subplot(2, 2, 3)
plt.imshow(opening, cmap=plt.cm.gray)
plt.xlabel('greylevel opening')
plt.subplot(2, 2, 4)
plt.imshow(grad, cmap=plt.cm.gray)
plt.xlabel('morphological gradient')
"""
.. image:: PLOT2RST.current_figure
Feature extraction
===================
Local histogram 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
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.
.. note::
to better use the available image bit, the function returns 10x entropy for
8-bit images and 1000x entropy for 16-bit images.
"""
from skimage import data
from skimage.filter.rank import entropy
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
ent8 = entropy(a8, disk(5)) # pixel value contain 10x the local entropy
ent16 = entropy(a16, disk(5)) # pixel value contain 1000x the local entropy
# 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.colorbar()
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()
"""
.. 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.
"""
from time import time
from scipy.ndimage.filters import percentile_filter
from skimage.morphology import dilation
from skimage.filter import median_filter
from skimage.filter.rank import median, maximum
def exec_and_timeit(func):
"""Decorator that returns both function results and execution time."""
def wrapper(*arg):
t1 = time()
res = func(*arg)
t2 = time()
ms = (t2 - t1) * 1000.0
return (res, ms)
return wrapper
@exec_and_timeit
def cr_med(image, selem):
return median(image=image, selem=selem)
@exec_and_timeit
def cr_max(image, selem):
return maximum(image=image, selem=selem)
@exec_and_timeit
def cm_dil(image, selem):
return dilation(image=image, selem=selem)
@exec_and_timeit
def ctmf_med(image, radius):
return median_filter(image=image, radius=radius)
@exec_and_timeit
def ndi_med(image, n):
return percentile_filter(image, 50, size=n * 2 - 1)
"""
Comparison between
* `rank.maximum`
* `cmorph.dilate`
on increasing structuring element size
"""
a = data.camera()
rec = []
e_range = range(1, 20, 2)
for r in e_range:
elem = disk(r + 1)
rc, ms_rc = cr_max(a, elem)
rcm, ms_rcm = cm_dil(a, elem)
rec.append((ms_rc, ms_rcm))
rec = np.asarray(rec)
plt.figure()
plt.title('increasing element size')
plt.ylabel('time (ms)')
plt.xlabel('element radius')
plt.plot(e_range, rec)
plt.legend(['crank.maximum', 'cmorph.dilate'])
"""
and increasing image size
.. image:: PLOT2RST.current_figure
"""
r = 9
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')
(rc, ms_rc) = cr_max(a, elem)
(rcm, ms_rcm) = cm_dil(a, elem)
rec.append((ms_rc, ms_rcm))
rec = np.asarray(rec)
plt.figure()
plt.title('increasing image size')
plt.ylabel('time (ms)')
plt.xlabel('image size')
plt.plot(s_range, rec)
plt.legend(['crank.maximum', 'cmorph.dilate'])
"""
.. image:: PLOT2RST.current_figure
Comparison between:
* `rank.median`
* `ctmf.median_filter`
* `ndimage.percentile`
on increasing structuring element size
"""
a = data.camera()
rec = []
e_range = range(2, 30, 4)
for r in e_range:
elem = disk(r + 1)
rc, ms_rc = cr_med(a, elem)
rctmf, ms_rctmf = ctmf_med(a, r)
rndi, ms_ndi = ndi_med(a, r)
rec.append((ms_rc, ms_rctmf, ms_ndi))
rec = np.asarray(rec)
plt.figure()
plt.title('increasing element size')
plt.plot(e_range, rec)
plt.legend(['rank.median', 'ctmf.median_filter', 'ndimage.percentile'])
plt.ylabel('time (ms)')
plt.xlabel('element radius')
"""
.. image:: PLOT2RST.current_figure
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')
"""
.. image:: PLOT2RST.current_figure
and increasing image size
"""
r = 9
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')
(rc, ms_rc) = cr_med(a, elem)
rctmf, ms_rctmf = ctmf_med(a, r)
rndi, ms_ndi = ndi_med(a, r)
rec.append((ms_rc, ms_rctmf, ms_ndi))
rec = np.asarray(rec)
plt.figure()
plt.title('increasing image size')
plt.plot(s_range, rec)
plt.legend(['rank.median', 'ctmf.median_filter', 'ndimage.percentile'])
plt.ylabel('time (ms)')
plt.xlabel('image size')
"""
.. image:: PLOT2RST.current_figure
"""
plt.show()
+47
View File
@@ -0,0 +1,47 @@
"""
==============================
Bilateral mean
==============================
This example compares
* local mean
* percentile mean
* bilateral mean
build on the local histogram distribution
local mean uses all pixels belonging to the structuring element to compute average gray level,
percentile mean uses only values between percentiles p0 and p1 (here 10% and 90%),
whereas bilateral mean uses only pixels of the structuring element having a gray level situated inside
g-s0 and g+s1 (here g-500 and g+500).
The filters are applied on a 16 bit image (actual bitdepth is 12bit).
Percentile and usual mean give here similar results, these filters smooth the complete image (background and details).
Bilateral mean exhibits a high filtering rate for continuous area (i.e. background) while image higher frequencies
remains untouched.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
from skimage.morphology import disk
import skimage.filter.rank as rank
a16 = (data.coins()).astype('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)
# display results
fig, axes = plt.subplots(nrows=3, figsize=(15, 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')
plt.show()
+72
View File
@@ -0,0 +1,72 @@
"""
========================
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
# Load picture and detect edges
image = data.coins()[0:95, 70:370]
edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
# Detect two radii
hough_radii = np.arange(15, 30, 2)
hough_res = hough_circle(edges, hough_radii)
centers = []
accums = []
radii = []
for radius, h in zip(hough_radii, hough_res):
# For each radius, extract two circles
peaks = peak_local_max(h, num_peaks=2)
centers.extend(peaks - hough_radii.max())
accums.extend(h[peaks[:, 0], peaks[:, 1]])
radii.extend([radius, radius])
# Draw the most prominent 5 circles
image = color.gray2rgb(image)
for idx in np.argsort(accums)[::-1][:5]:
center_x, center_y = centers[idx]
radius = radii[idx]
cx, cy = circle_perimeter(center_y, center_x, radius)
image[cy, cx] = (220, 20, 20)
ax.imshow(image, cmap=plt.cm.gray)
plt.show()
+37
View File
@@ -0,0 +1,37 @@
"""
================
Corner detection
================
Detect corner points using the Harris corner detector and determine subpixel
position of corners.
.. [1] http://en.wikipedia.org/wiki/Corner_detection
.. [2] http://en.wikipedia.org/wiki/Interest_point_detection
"""
from matplotlib import pyplot as plt
from skimage import data
from skimage.feature import corner_harris, corner_subpix, corner_peaks
from skimage.transform import warp, AffineTransform
from skimage.draw import ellipse
tform = AffineTransform(scale=(1.3, 1.1), rotation=1, shear=0.7,
translation=(210, 50))
image = warp(data.checkerboard(), tform.inverse, output_shape=(350, 350))
rr, cc = ellipse(310, 175, 10, 100)
image[rr, cc] = 1
image[180:230, 10:60] = 1
image[230:280, 60:110] = 1
coords = corner_peaks(corner_harris(image), min_distance=5)
coords_subpix = corner_subpix(image, coords, window_size=13)
plt.gray()
plt.imshow(image, interpolation='nearest')
plt.plot(coords[:, 1], coords[:, 0], '.b', markersize=3)
plt.plot(coords_subpix[:, 1], coords_subpix[:, 0], '+r', markersize=15)
plt.axis((0, 350, 350, 0))
plt.show()
+28
View File
@@ -0,0 +1,28 @@
"""
===============================
Dense DAISY feature description
===============================
The DAISY local image descriptor is based on gradient orientation histograms
similar to the SIFT descriptor. It is formulated in a way that allows for fast
dense extraction which is useful for e.g. bag-of-features image
representations.
In this example a limited number of DAISY descriptors are extracted at a large
scale for illustrative purposes.
"""
from skimage.feature import daisy
from skimage import data
import matplotlib.pyplot as plt
img = data.camera()
descs, descs_img = daisy(img, step=180, radius=58, rings=2, histograms=6,
orientations=8, visualize=True)
plt.axis('off')
plt.imshow(descs_img)
descs_num = descs.shape[0] * descs.shape[1]
plt.title('%i DAISY descriptors extracted:' % descs_num)
plt.show()
+68
View File
@@ -0,0 +1,68 @@
"""
=============================
Denoising the picture of Lena
=============================
In this example, we denoise a noisy version of the picture of Lena using the
total variation and bilateral denoising filter.
These algorithms typically produce "posterized" images with flat domains
separated by sharp edges. It is possible to change the degree of posterization
by controlling the tradeoff between denoising and faithfulness to the original
image.
Total variation filter
----------------------
The result of this filter is an image that has a minimal total variation norm,
while being as close to the initial image as possible. The total variation is
the L1 norm of the gradient of the image.
Bilateral filter
----------------
A bilateral filter is an edge-preserving and noise reducing filter. It averages
pixels based on their spatial closeness and radiometric similarity.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color, img_as_float
from skimage.filter import denoise_tv_chambolle, denoise_bilateral
lena = img_as_float(data.lena())
lena = lena[220:300, 220:320]
noisy = lena + 0.6 * lena.std() * np.random.random(lena.shape)
noisy = np.clip(noisy, 0, 1)
fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(8, 5))
plt.gray()
ax[0, 0].imshow(noisy)
ax[0, 0].axis('off')
ax[0, 0].set_title('noisy')
ax[0, 1].imshow(denoise_tv_chambolle(noisy, weight=0.1, multichannel=True))
ax[0, 1].axis('off')
ax[0, 1].set_title('TV')
ax[0, 2].imshow(denoise_bilateral(noisy, sigma_range=0.05, sigma_spatial=15))
ax[0, 2].axis('off')
ax[0, 2].set_title('Bilateral')
ax[1, 0].imshow(denoise_tv_chambolle(noisy, weight=0.2, multichannel=True))
ax[1, 0].axis('off')
ax[1, 0].set_title('(more) TV')
ax[1, 1].imshow(denoise_bilateral(noisy, sigma_range=0.1, sigma_spatial=15))
ax[1, 1].axis('off')
ax[1, 1].set_title('(more) Bilateral')
ax[1, 2].imshow(lena)
ax[1, 2].axis('off')
ax[1, 2].set_title('original')
fig.subplots_adjust(wspace=0.02, hspace=0.2,
top=0.9, bottom=0.05, left=0, right=1)
plt.show()
+44
View File
@@ -0,0 +1,44 @@
"""
===================
Entropy
===================
"""
from skimage import data
from skimage.filter.rank import entropy
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
ent8 = entropy(a8,disk(5)) # pixel value contain 10x the local entropy
ent16 = entropy(a16,disk(5)) # pixel value contain 1000x the local entropy
# 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.colorbar()
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()
+19 -11
View File
@@ -18,18 +18,18 @@ that fall within the 2nd and 98th percentiles [2]_.
"""
from skimage import data
from skimage.util.dtype import dtype_range
from skimage import data, img_as_float
from skimage import exposure
import matplotlib.pyplot as plt
import numpy as np
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
"""
img = img_as_float(img)
ax_img, ax_hist = axes
ax_cdf = ax_hist.twinx()
@@ -38,16 +38,16 @@ def plot_img_and_hist(img, axes, bins=256):
ax_img.set_axis_off()
# Display histogram
ax_hist.hist(img.ravel(), bins=bins)
ax_hist.hist(img.ravel(), bins=bins, histtype='step', color='black')
ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
ax_hist.set_xlabel('Pixel intensity')
xmin, xmax = dtype_range[img.dtype.type]
ax_hist.set_xlim(xmin, xmax)
ax_hist.set_xlim(0, 1)
ax_hist.set_yticks([])
# Display cumulative distribution
img_cdf, bins = exposure.cumulative_distribution(img, bins)
ax_cdf.plot(bins, img_cdf, 'r')
ax_cdf.set_yticks([])
return ax_img, ax_hist, ax_cdf
@@ -61,25 +61,33 @@ p98 = np.percentile(img, 98)
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
# Equalization
img_eq = exposure.equalize(img)
img_eq = exposure.equalize_hist(img)
# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)
# Display results
f, axes = plt.subplots(2, 3, figsize=(8, 4))
f, axes = plt.subplots(2, 4, figsize=(8, 4))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')
ax_cdf.set_ylabel('Fraction of total intensity')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')
ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))
# prevent overlap of y-axis labels
plt.subplots_adjust(wspace=0.4)
plt.show()
-42
View File
@@ -1,42 +0,0 @@
"""
===============================================================================
Harris Corner detector
===============================================================================
The Harris corner filter [1]_ detects "interest points" [2]_ using edge
detection in multiple directions.
.. [1] http://en.wikipedia.org/wiki/Corner_detection
.. [2] http://en.wikipedia.org/wiki/Interest_point_detection
"""
import numpy as np
from matplotlib import pyplot as plt
from skimage import data, img_as_float
from skimage.feature import harris
def plot_harris_points(image, filtered_coords):
""" plots corners found in image"""
plt.imshow(image)
y, x = np.transpose(filtered_coords)
plt.plot(x, y, 'b.')
plt.axis('off')
# display results
plt.figure(figsize=(8, 3))
im_lena = img_as_float(data.lena())
im_text = img_as_float(data.text())
filtered_coords = harris(im_lena, min_distance=4)
plt.axes([0, 0, 0.3, 0.95])
plot_harris_points(im_lena, filtered_coords)
filtered_coords = harris(im_text, min_distance=4)
plt.axes([0.2, 0, 0.77, 1])
plot_harris_points(im_text, filtered_coords)
plt.show()
+13 -5
View File
@@ -59,7 +59,7 @@ References
'''
from skimage.transform import hough, probabilistic_hough
from skimage.transform import hough, hough_peaks, probabilistic_hough
from skimage.filter import canny
from skimage import data
@@ -81,11 +81,11 @@ h, theta, d = hough(image)
plt.figure(figsize=(8, 4))
plt.subplot(121)
plt.subplot(131)
plt.imshow(image, cmap=plt.cm.gray)
plt.title('Input image')
plt.subplot(122)
plt.subplot(132)
plt.imshow(np.log(1 + h),
extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]),
d[-1], d[0]],
@@ -94,6 +94,15 @@ plt.title('Hough transform')
plt.xlabel('Angles (degrees)')
plt.ylabel('Distance (pixels)')
plt.subplot(133)
plt.imshow(image, cmap=plt.cm.gray)
rows, cols = image.shape
for _, angle, dist in zip(*hough_peaks(h, theta, d)):
y0 = (dist - 0 * np.cos(angle)) / np.sin(angle)
y1 = (dist - cols * np.cos(angle)) / np.sin(angle)
plt.plot((0, cols), (y0, y1), '-r')
plt.axis((0, cols, rows, 0))
plt.title('Detected lines')
# Line finding, using the Probabilistic Hough Transform
@@ -118,7 +127,6 @@ for line in lines:
p0, p1 = line
plt.plot((p0[0], p1[0]), (p0[1], p1[1]))
plt.title('Lines found with PHT')
plt.title('Probabilistic Hough')
plt.axis('image')
plt.show()
+69
View File
@@ -0,0 +1,69 @@
"""
==========================================
Find the intersection of two segmentations
==========================================
When segmenting an image, you may want to combine multiple alternative
segmentations. The `skimage.segmentation.join_segmentations` function
computes the join of two segmentations, in which a pixel is placed in
the same segment if and only if it is in the same segment in _both_
segmentations.
"""
import numpy as np
from scipy import ndimage as nd
import matplotlib.pyplot as plt
import matplotlib as mpl
from skimage.filter import sobel
from skimage.segmentation import slic, join_segmentations
from skimage.morphology import watershed
from skimage import data
coins = 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
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)
# combine the two
segj = join_segmentations(seg1, seg2)
### Display the result ###
# make a random colormap for a set number of values
def random_cmap(im):
np.random.seed(9)
cmap_array = np.concatenate(
(np.zeros((1, 3)), np.random.rand(np.ceil(im.max()), 3)))
return mpl.colors.ListedColormap(cmap_array)
# show the segmentations
fig, axes = plt.subplots(ncols=4, figsize=(9, 2.5))
axes[0].imshow(coins, cmap=plt.cm.gray, interpolation='nearest')
axes[0].set_title('Image')
axes[1].imshow(seg1, cmap=random_cmap(seg1), interpolation='nearest')
axes[1].set_title('Sobel+Watershed')
axes[2].imshow(seg2, cmap=random_cmap(seg2), interpolation='nearest')
axes[2].set_title('SLIC superpixels')
axes[3].imshow(segj, cmap=random_cmap(segj), interpolation='nearest')
axes[3].set_title('Join')
for ax in axes:
ax.axis('off')
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0, right=1)
plt.show()
-51
View File
@@ -1,51 +0,0 @@
"""
====================================================
Denoising the picture of Lena using total variation
====================================================
In this example, we denoise a noisy version of the picture of Lena
using the total variation denoising filter. The result of this filter
is an image that has a minimal total variation norm, while being as
close to the initial image as possible. The total variation is the L1
norm of the gradient of the image, and minimizing the total variation
typically produces "posterized" images with flat domains separated by
sharp edges.
It is possible to change the degree of posterization by controlling
the tradeoff between denoising and faithfulness to the original image.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color, img_as_ubyte
from skimage.filter import tv_denoise
l = img_as_ubyte(color.rgb2gray(data.lena()))
l = l[230:290, 220:320]
noisy = l + 0.4 * l.std() * np.random.random(l.shape)
tv_denoised = tv_denoise(noisy, weight=10)
plt.figure(figsize=(8, 2))
plt.subplot(131)
plt.imshow(noisy, cmap=plt.cm.gray, vmin=40, vmax=220)
plt.axis('off')
plt.title('noisy', fontsize=20)
plt.subplot(132)
plt.imshow(tv_denoised, cmap=plt.cm.gray, vmin=40, vmax=220)
plt.axis('off')
plt.title('TV denoising', fontsize=20)
tv_denoised = tv_denoise(noisy, weight=50)
plt.subplot(133)
plt.imshow(tv_denoised, cmap=plt.cm.gray, vmin=40, vmax=220)
plt.axis('off')
plt.title('(more) TV denoising', fontsize=20)
plt.subplots_adjust(wspace=0.02, hspace=0.02, top=0.9, bottom=0, left=0,
right=1)
plt.show()
+84
View File
@@ -0,0 +1,84 @@
"""
===============================
Local Histogram Equalization
===============================
This examples enhances an image with low contrast, using a method called
*local histogram equalization*, which "spreads out the most frequent intensity
values" in an image .
The equalized image [1]_ has a roughly linear cumulative distribution function for each pixel neighborhood.
The local version [2]_ of the histogram equalization emphasized every local graylevel variations.
.. [1] http://en.wikipedia.org/wiki/Histogram_equalization
.. [2] http://en.wikipedia.org/wiki/Adaptive_histogram_equalization
"""
from skimage import data
from skimage.util.dtype import dtype_range
from skimage import exposure
from skimage.morphology import disk
import matplotlib.pyplot as plt
import numpy as np
from skimage.filter import rank
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
"""
ax_img, ax_hist = axes
ax_cdf = ax_hist.twinx()
# Display image
ax_img.imshow(img, cmap=plt.cm.gray)
ax_img.set_axis_off()
# Display histogram
ax_hist.hist(img.ravel(), bins=bins)
ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
ax_hist.set_xlabel('Pixel intensity')
xmin, xmax = dtype_range[img.dtype.type]
ax_hist.set_xlim(xmin, xmax)
# Display cumulative distribution
img_cdf, bins = exposure.cumulative_distribution(img, bins)
ax_cdf.plot(bins, img_cdf, 'r')
return ax_img, ax_hist, ax_cdf
# Load an example image
img = data.moon()
# Contrast stretching
p2 = np.percentile(img, 2)
p98 = np.percentile(img, 98)
img_rescale = exposure.equalize_hist(img)
# Equalization
selem = disk(30)
img_eq = rank.equalize(img, selem=selem)
# Display results
f, axes = plt.subplots(2, 3, figsize=(8, 4))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
ax_hist.set_ylabel('Number of pixels')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Global equalise')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Local equalize')
ax_cdf.set_ylabel('Fraction of total intensity')
# prevent overlap of y-axis labels
plt.subplots_adjust(wspace=0.4)
plt.show()
+49
View File
@@ -0,0 +1,49 @@
"""
=====================
Local Otsu Threshold
=====================
This example shows how Otsu's threshold [1]_ method can be applied locally.
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.
.. note: local threshold is much slower than global one.
.. [1] http://en.wikipedia.org/wiki/Otsu's_method
"""
import matplotlib.pyplot as plt
from skimage import data
from skimage.morphology.selem import disk
import skimage.filter.rank as rank
from skimage.filter import threshold_otsu
p8 = data.page()
radius = 10
selem = disk(radius)
loc_otsu = rank.otsu(p8, selem)
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.colorbar()
plt.subplot(2, 2, 2)
plt.imshow(loc_otsu, cmap=plt.cm.gray)
plt.xlabel('local Otsu ($radius=%d$)' % radius)
plt.colorbar()
plt.subplot(2, 2, 3)
plt.imshow(p8 >= loc_otsu, cmap=plt.cm.gray)
plt.xlabel('original>=local Otsu' % t_glob_otsu)
plt.subplot(2, 2, 4)
plt.imshow(glob_otsu, cmap=plt.cm.gray)
plt.xlabel('global Otsu ($t=%d$)' % t_glob_otsu)
plt.show()
+54
View File
@@ -0,0 +1,54 @@
"""
================================
Markers for watershed transform
================================
The watershed is a classical algorithm used for **segmentation**, that
is, for separating different objects in an image.
Here a marker image is build from the region of low gradient inside the image.
See Wikipedia_ for more details on the algorithm.
.. _Wikipedia: http://en.wikipedia.org/wiki/Watershed_(image_processing)
"""
from scipy import ndimage
import matplotlib.pyplot as plt
from skimage.morphology import watershed, disk
from skimage import data
# original data
from skimage.filter import rank
image = data.camera()
# denoise image
denoised = rank.median(image, disk(2))
# find continuous region (low gradient) --> markers
markers = rank.gradient(denoised, disk(5)) < 10
markers = ndimage.label(markers)[0]
#local gradient
gradient = rank.gradient(denoised, disk(2))
# process the watershed
labels = watershed(gradient, markers)
# display results
fig, axes = plt.subplots(ncols=4, figsize=(8, 2.7))
ax0, ax1, ax2, ax3 = axes
ax0.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax1.imshow(gradient, cmap=plt.cm.spectral, interpolation='nearest')
ax2.imshow(markers, cmap=plt.cm.spectral, interpolation='nearest')
ax3.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax3.imshow(labels, cmap=plt.cm.spectral, interpolation='nearest', alpha=.7)
for ax in axes:
ax.axis('off')
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0, right=1)
plt.show()
@@ -19,7 +19,6 @@ values, and use the random walker for the segmentation.
.. [1] *Random walks for image segmentation*, Leo Grady, IEEE Trans. Pattern
Anal. Mach. Intell. 2006 Nov; 28(11):1768-83
"""
print __doc__
import numpy as np
from scipy import ndimage
+5 -5
View File
@@ -63,8 +63,8 @@ import matplotlib.pyplot as plt
import numpy as np
from skimage.data import lena
from skimage.segmentation import felzenszwalb, \
visualize_boundaries, slic, quickshift
from skimage.segmentation import felzenszwalb, slic, quickshift
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float
img = img_as_float(lena()[::2, ::2])
@@ -80,11 +80,11 @@ fig, ax = plt.subplots(1, 3)
fig.set_size_inches(8, 3, forward=True)
plt.subplots_adjust(0.05, 0.05, 0.95, 0.95, 0.05, 0.05)
ax[0].imshow(visualize_boundaries(img, segments_fz))
ax[0].imshow(mark_boundaries(img, segments_fz))
ax[0].set_title("Felzenszwalbs's method")
ax[1].imshow(visualize_boundaries(img, segments_slic))
ax[1].imshow(mark_boundaries(img, segments_slic))
ax[1].set_title("SLIC")
ax[2].imshow(visualize_boundaries(img, segments_quick))
ax[2].imshow(mark_boundaries(img, segments_quick))
ax[2].set_title("Quickshift")
for a in ax:
a.set_xticks(())
+6 -2
View File
@@ -13,7 +13,7 @@ This example shows how to fill several different shapes:
import matplotlib.pyplot as plt
from skimage.draw import line, polygon, circle, ellipse
from skimage.draw import line, polygon, circle, circle_perimeter, ellipse
import numpy as np
@@ -42,5 +42,9 @@ img[rr,cc,:] = (255, 255, 0)
rr, cc = ellipse(300, 300, 100, 200, img.shape)
img[rr,cc,2] = 255
# circle
rr, cc = circle_perimeter(120, 400, 50)
img[rr, cc, :] = (255, 0, 255)
plt.imshow(img)
plt.show()
plt.show()
+1 -1
View File
@@ -26,7 +26,7 @@ See Wikipedia_ for more details on the algorithm.
"""
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
from skimage.morphology import watershed, is_local_maximum
+11 -1
View File
@@ -69,6 +69,7 @@ import os
import shutil
import token
import tokenize
import traceback
import numpy as np
import matplotlib
@@ -247,7 +248,16 @@ def write_gallery(gallery_index, src_dir, rst_dir, cfg, depth=0):
gallery_index.write(TOCTREE_TEMPLATE % (sub_dir + '\n '.join(ex_names)))
for src_name in examples:
write_example(src_name, src_dir, rst_dir, cfg)
try:
write_example(src_name, src_dir, rst_dir, cfg)
except Exception:
print "Exception raised while running:"
print "%s in %s" % (src_name, src_dir)
print '~' * 60
traceback.print_exc()
print '~' * 60
continue
link_name = sub_dir.pjoin(src_name)
link_name = link_name.replace(os.path.sep, '_')
+71
View File
@@ -0,0 +1,71 @@
Announcement: scikits-image 0.8.0
=================================
We're happy to announce the 8th version of scikit-image!
scikit-image is an image processing toolbox for SciPy that includes algorithms
for segmentation, geometric transformations, color space manipulation,
analysis, filtering, morphology, feature detection, and more.
For more information, examples, and documentation, please visit our website:
http://scikit-image.org
New Features
------------
- New rank filter package with many new functions and a very fast underlying
local histogram algorithm, especially for large structuring elements
`skimage.filter.rank.*`
- New function for small object removal
`skimage.morphology.remove_small_objects`
- New circular hough transformation `skimage.transform.hough_circle`
- New function to draw circle perimeter `skimage.draw.circle_perimeter` and
ellipse perimeter `skimage.draw.ellipse_perimeter`
- New dense DAISY feature descriptor `skimage.feature.daisy`
- New bilateral filter `skimage.filter.denoise_bilateral`
- New faster TV denoising filter based on split-Bregman algorithm
`skimage.filter.denoise_tv_bregman`
- New linear hough peak detection `skimage.transform.hough_peaks`
- New Scharr edge detection `skimage.filter.scharr`
- New geometric image scaling as convenience function
`skimage.transform.rescale`
- New theme for documentation and website
- Faster median filter through vectorization `skimage.filter.median_filter`
- Grayscale images supported for SLIC segmentation
- Unified peak detection with more options `skimage.feature.peak_local_max`
- `imread` can read images via URL and knows more formats `skimage.io.imread`
Additionally, this release adds lots of bug fixes, new examples, and
performance enhancements.
Contributors to this release
----------------------------
This release was only possible due to the efforts of many contributors, both
new and old.
- Adam Ginsburg
- Anders Boesen Lindbo Larsen
- Andreas Mueller
- Christoph Gohlke
- Christos Psaltis
- Colin Lea
- François Boulogne
- Jan Margeta
- Johannes Schönberger
- Josh Warner (Mac)
- Juan Nunez-Iglesias
- Luis Pedro Coelho
- Marianne Corvellec
- Matt McCormick
- Nicolas Pinto
- Olivier Debeir
- Paul Ivanov
- Sergey Karayev
- Stefan van der Walt
- Steven Silvester
- Thouis (Ray) Jones
- Tony S Yu
+1 -1
View File
@@ -1,5 +1,5 @@
function insert_version_links() {
var labels = ['dev', '0.7.0', '0.6', '0.5', '0.4', '0.3'];
var labels = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3'];
for (i = 0; i < labels.length; i++){
open_list = '<li>'
-5
View File
@@ -71,11 +71,6 @@ issued::
float64 to uint8
array([ 0, 128, 255], dtype=uint8)
Wherever possible, functions should try to handle input without explicit
conversion. For example, there is no need to force values to a specific type
for doing a convolution; a plotting function, on the other hand, needs to know
the range of the input.
Output types
============
+5 -4
View File
@@ -17,7 +17,7 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za'
URL = 'http://scikit-image.org'
LICENSE = 'Modified BSD'
DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image'
VERSION = '0.7.2'
VERSION = '0.8.0'
PYTHON_VERSION = (2, 5)
DEPENDENCIES = {
'numpy': (1, 6),
@@ -27,6 +27,7 @@ DEPENDENCIES = {
import os
import sys
import re
import setuptools
from numpy.distutils.core import setup
try:
@@ -72,8 +73,8 @@ def get_package_version(package):
for version_attr in ('version', 'VERSION', '__version__'):
if hasattr(package, version_attr) \
and isinstance(getattr(package, version_attr), str):
version_info = getattr(package, version_attr)
for part in version_info.split('.'):
version_info = getattr(package, version_attr, '')
for part in re.split('\D+', version_info):
try:
version.append(int(part))
except ValueError:
@@ -137,7 +138,7 @@ if __name__ == "__main__":
configuration=configuration,
packages=setuptools.find_packages(),
packages=setuptools.find_packages(exclude=['doc']),
include_package_data=True,
zip_safe=False, # the package can run out of an .egg file
+23 -23
View File
@@ -52,6 +52,8 @@ img_as_ubyte
"""
import os.path as _osp
import imp as _imp
import functools as _functools
pkg_dir = _osp.abspath(_osp.dirname(__file__))
data_dir = _osp.join(pkg_dir, 'data')
@@ -62,30 +64,28 @@ except ImportError:
__version__ = "unbuilt-dev"
def _setup_test(verbose=False):
import functools
try:
_imp.find_module('nose')
except ImportError:
def _test(verbose=False):
"""This would invoke the skimage test suite, but nose couldn't be
imported so the test suite can not run.
"""
raise ImportError("Could not load nose. Unit tests not available.")
else:
def _test(verbose=False):
"""Invoke the skimage test suite."""
import nose
args = ['', pkg_dir, '--exe']
if verbose:
args.extend(['-v', '-s'])
nose.run('skimage', argv=args)
args = ['', pkg_dir, '--exe']
if verbose:
args.extend(['-v', '-s'])
try:
import nose as _nose
except ImportError:
def broken_test_func():
"""This would invoke the skimage test suite, but nose couldn't be
imported so the test suite can not run.
"""
raise ImportError("Could not load nose. Unit tests not available.")
return broken_test_func
else:
f = functools.partial(_nose.run, 'skimage', argv=args)
f.__doc__ = 'Invoke the skimage test suite.'
return f
test = _setup_test()
test_verbose = _setup_test(verbose=True)
# do not use `test` as function name as this leads to a recursion problem with
# the nose test suite
test = _test
test_verbose = _functools.partial(test, verbose=True)
test_verbose.__doc__ = test.__doc__
def get_log(name=None):
+7
View File
@@ -4,6 +4,13 @@ import hashlib
import subprocess
# WindowsError is not defined on unix systems
try:
WindowsError
except NameError:
WindowsError = None
def cython(pyx_files, working_path=''):
"""Use Cython to convert the given files to C.
+3 -3
View File
@@ -1,6 +1,6 @@
cdef unsigned char point_in_polygon(int nr_verts, double *xp, double *yp,
cdef unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp,
double x, double y)
cdef void points_in_polygon(int nr_verts, double *xp, double *yp,
int nr_points, double *x, double *y,
cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp,
Py_ssize_t nr_points, double *x, double *y,
unsigned char *result)
+7 -7
View File
@@ -4,8 +4,8 @@
#cython: wraparound=False
cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp,
double x, double y):
cdef inline unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp,
double *yp, double x, double y):
"""Test whether point lies inside a polygon.
Parameters
@@ -17,9 +17,9 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp,
x, y : double
Coordinates of point.
"""
cdef int i
cdef Py_ssize_t i
cdef unsigned char c = 0
cdef int j = nr_verts - 1
cdef Py_ssize_t j = nr_verts - 1
for i in range(nr_verts):
if (
(((yp[i] <= y) and (y < yp[j])) or
@@ -31,8 +31,8 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp,
return c
cdef void points_in_polygon(int nr_verts, double *xp, double *yp,
int nr_points, double *x, double *y,
cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp,
Py_ssize_t nr_points, double *x, double *y,
unsigned char *result):
"""Test whether points lie inside a polygon.
@@ -49,6 +49,6 @@ cdef void points_in_polygon(int nr_verts, double *xp, double *yp,
result : unsigned char array
Test results for each point.
"""
cdef int n
cdef Py_ssize_t n
for n in range(nr_points):
result[n] = point_in_polygon(nr_verts, xp, yp, x[n], y[n])
+11 -8
View File
@@ -1,24 +1,27 @@
cdef double nearest_neighbour_interpolation(double* image, int rows,
int cols, double r,
cdef double nearest_neighbour_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r,
double c, char mode,
double cval)
cdef double bilinear_interpolation(double* image, int rows, int cols,
cdef double bilinear_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef double quadratic_interpolation(double x, double[3] f)
cdef double biquadratic_interpolation(double* image, int rows, int cols,
cdef double biquadratic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef double cubic_interpolation(double x, double[4] f)
cdef double bicubic_interpolation(double* image, int rows, int cols,
cdef double bicubic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols,
double r, double c, char mode,
double cval)
cdef double get_pixel(double* image, int rows, int cols, int r, int c,
char mode, double cval)
cdef double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t r,
Py_ssize_t c, char mode, double cval)
cdef int coord_map(int dim, int coord, char mode)
cdef double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims,
Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, char mode, double cval)
cdef Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode)
+77 -43
View File
@@ -5,12 +5,12 @@
from libc.math cimport ceil, floor
cdef inline int round(double r):
return <int>((r + 0.5) if (r > 0.0) else (r - 0.5))
cdef inline Py_ssize_t round(double r):
return <Py_ssize_t>((r + 0.5) if (r > 0.0) else (r - 0.5))
cdef inline double nearest_neighbour_interpolation(double* image, int rows,
int cols, double r,
cdef inline double nearest_neighbour_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r,
double c, char mode,
double cval):
"""Nearest neighbour interpolation at a given position in the image.
@@ -35,13 +35,12 @@ cdef inline double nearest_neighbour_interpolation(double* image, int rows,
"""
return get_pixel(image, rows, cols, <int>round(r), <int>round(c),
mode, cval)
return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval)
cdef inline double bilinear_interpolation(double* image, int rows, int cols,
double r, double c, char mode,
double cval):
cdef inline double bilinear_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bilinear interpolation at a given position in the image.
Parameters
@@ -64,18 +63,18 @@ cdef inline double bilinear_interpolation(double* image, int rows, int cols,
"""
cdef double dr, dc
cdef int minr, minc, maxr, maxc
cdef Py_ssize_t minr, minc, maxr, maxc
minr = <int>floor(r)
minc = <int>floor(c)
maxr = <int>ceil(r)
maxc = <int>ceil(c)
minr = <Py_ssize_t>floor(r)
minc = <Py_ssize_t>floor(c)
maxr = <Py_ssize_t>ceil(r)
maxc = <Py_ssize_t>ceil(c)
dr = r - minr
dc = c - minc
top = (1 - dc) * get_pixel(image, rows, cols, minr, minc, mode, cval) \
+ dc * get_pixel(image, rows, cols, minr, maxc, mode, cval)
bottom = (1 - dc) * get_pixel(image, rows, cols, maxr, minc, mode, cval) \
+ dc * get_pixel(image, rows, cols, maxr, maxc, mode, cval)
top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, minr, maxc, mode, cval)
bottom = (1 - dc) * get_pixel2d(image, rows, cols, maxr, minc, mode, cval) \
+ dc * get_pixel2d(image, rows, cols, maxr, maxc, mode, cval)
return (1 - dr) * top + dr * bottom
@@ -98,9 +97,9 @@ cdef inline double quadratic_interpolation(double x, double[3] f):
return f[1] - 0.25 * (f[0] - f[2]) * x
cdef inline double biquadratic_interpolation(double* image, int rows, int cols,
double r, double c, char mode,
double cval):
cdef inline double biquadratic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Biquadratic interpolation at a given position in the image.
Parameters
@@ -123,8 +122,8 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols,
"""
cdef int r0 = <int>round(r)
cdef int c0 = <int>round(c)
cdef Py_ssize_t r0 = round(r)
cdef Py_ssize_t c0 = round(c)
if r < 0:
r0 -= 1
if c < 0:
@@ -139,12 +138,12 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols,
cdef double fc[3], fr[3]
cdef int pr, pc
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 3):
for pc in range(c0, c0 + 3):
fc[pc - c0] = get_pixel(image, rows, cols, pr, pc, mode, cval)
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = quadratic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
@@ -174,9 +173,9 @@ cdef inline double cubic_interpolation(double x, double[4] f):
(3.0 * (f[1] - f[2]) + f[3] - f[0])))
cdef inline double bicubic_interpolation(double* image, int rows, int cols,
double r, double c, char mode,
double cval):
cdef inline double bicubic_interpolation(double* image, Py_ssize_t rows,
Py_ssize_t cols, double r, double c,
char mode, double cval):
"""Bicubic interpolation at a given position in the image.
Parameters
@@ -199,8 +198,8 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols,
"""
cdef int r0 = <int>r - 1
cdef int c0 = <int>c - 1
cdef Py_ssize_t r0 = <Py_ssize_t>r - 1
cdef Py_ssize_t c0 = <Py_ssize_t>c - 1
if r < 0:
r0 -= 1
if c < 0:
@@ -211,20 +210,20 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols,
cdef double fc[4], fr[4]
cdef int pr, pc
cdef Py_ssize_t pr, pc
# row-wise cubic interpolation
for pr in range(r0, r0 + 4):
for pc in range(c0, c0 + 4):
fc[pc - c0] = get_pixel(image, rows, cols, pr, pc, mode, cval)
fc[pc - c0] = get_pixel2d(image, rows, cols, pr, pc, mode, cval)
fr[pr - r0] = cubic_interpolation(xc, fc)
# cubic interpolation for interpolated values of each row
return cubic_interpolation(xr, fr)
cdef inline double get_pixel(double* image, int rows, int cols, int r, int c,
char mode, double cval):
cdef inline double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c, char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
@@ -255,7 +254,42 @@ cdef inline double get_pixel(double* image, int rows, int cols, int r, int c,
return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)]
cdef inline int coord_map(int dim, int coord, char mode):
cdef inline double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t dims, Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
char mode, double cval):
"""Get a pixel from the image, taking wrapping mode into consideration.
Parameters
----------
image : double array
Input image.
rows, cols, dims : int
Shape of image.
r, c, d : int
Position at which to get the pixel.
mode : {'C', 'W', 'R', 'N'}
Wrapping mode. Constant, Wrap, Reflect or Nearest.
cval : double
Constant value to use for constant mode.
Returns
-------
value : double
Pixel value at given position.
"""
if mode == 'C':
if (r < 0) or (r > rows - 1) or (c < 0) or (c > cols - 1):
return cval
else:
return image[r * cols * dims + c * dims + d]
else:
return image[coord_map(rows, r, mode) * cols * dims
+ coord_map(cols, c, mode) * dims
+ d]
cdef inline Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode):
"""
Wrap a coordinate, according to a given mode.
@@ -274,20 +308,20 @@ cdef inline int coord_map(int dim, int coord, char mode):
if mode == 'R': # reflect
if coord < 0:
# How many times times does the coordinate wrap?
if <int>(-coord / dim) % 2 != 0:
return dim - <int>(-coord % dim)
if <Py_ssize_t>(-coord / dim) % 2 != 0:
return dim - <Py_ssize_t>(-coord % dim)
else:
return <int>(-coord % dim)
return <Py_ssize_t>(-coord % dim)
elif coord > dim:
if <int>(coord / dim) % 2 != 0:
return <int>(dim - (coord % dim))
if <Py_ssize_t>(coord / dim) % 2 != 0:
return <Py_ssize_t>(dim - (coord % dim))
else:
return <int>(coord % dim)
return <Py_ssize_t>(coord % dim)
elif mode == 'W': # wrap
if coord < 0:
return <int>(dim - (-coord % dim))
return <Py_ssize_t>(dim - (-coord % dim))
elif coord > dim:
return <int>(coord % dim)
return <Py_ssize_t>(coord % dim)
elif mode == 'N': # nearest
if coord < 0:
return 0
+1 -1
View File
@@ -2,4 +2,4 @@ cimport numpy as cnp
cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat,
int r0, int c0, int r1, int c1)
Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1)
+1 -1
View File
@@ -6,7 +6,7 @@ cimport numpy as cnp
cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat,
int r0, int c0, int r1, int c1):
Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1):
"""
Using a summed area table / integral image, calculate the sum
over a given window.
+43
View File
@@ -0,0 +1,43 @@
import warnings
import functools
__all__ = ['deprecated']
class deprecated(object):
"""Decorator to mark deprecated functions with warning.
Adapted from <http://wiki.python.org/moin/PythonDecoratorLibrary>.
Parameters
----------
alt_func : str
If given, tell user what function to use instead.
behavior : {'warn', 'raise'}
Behavior during call to deprecated function: 'warn' = warn user that
function is deprecated; 'raise' = raise error.
"""
def __init__(self, alt_func=None, behavior='warn'):
self.alt_func = alt_func
self.behavior = behavior
def __call__(self, func):
msg = "Call to deprecated function `%s`." % func.__name__
if self.alt_func is not None:
msg = msg + " Use `%s` instead." % self.alt_func
@functools.wraps(func)
def wrapped(*args, **kwargs):
if self.behavior == 'warn':
warnings.warn_explicit(msg,
category=DeprecationWarning,
filename=func.func_code.co_filename,
lineno=func.func_code.co_firstlineno + 1)
elif self.behavior == 'raise':
raise DeprecationWarning(msg)
return func(*args, **kwargs)
return wrapped
+110
View File
@@ -0,0 +1,110 @@
/* Intrinsic declarations */
#if defined(__SSE2__)
#include <emmintrin.h>
#elif defined(__MMX__)
#include <mmintrin.h>
#elif defined(__ALTIVEC__)
#include <altivec.h>
#endif
/* Compiler peculiarities */
#if defined(__GNUC__)
#include <stdint.h>
#elif defined(_MSC_VER)
#define inline __inline
typedef unsigned __int16 uint16_t;
#endif
/**
* Add 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if
* available.
*/
#if defined(__SSE2__)
static inline void add16(uint16_t *dest, uint16_t *src)
{
__m128i *d, *s;
d = (__m128i *) dest;
s = (__m128i *) src;
*d = _mm_add_epi16(*d, *s);
d++; s++;
*d = _mm_add_epi16(*d, *s);
}
#elif defined(__MMX__)
static inline void add16(uint16_t *dest, uint16_t *src)
{
__m64 *d, *s;
d = (__m64 *) dest;
s = (__m64 *) src;
*d = _mm_add_pi16(*d, *s);
d++; s++;
*d = _mm_add_pi16(*d, *s);
d++; s++;
*d = _mm_add_pi16(*d, *s);
d++; s++;
*d = _mm_add_pi16(*d, *s);
}
#elif defined(__ALTIVEC__)
static inline void add16(uint16_t *dest, uint16_t *src)
{
vector unsigned short *d, *s;
d = (vector unsigned short *) dest;
s = (vector unsigned short *) src;
*d = vec_add(*d, *s);
d++; s++;
*d = vec_add(*d, *s);
}
#else
static inline void add16(uint16_t *dest, uint16_t *src)
{
int i;
for (i = 0; i < 16; i++) dest[i] += src[i];
}
#endif
/**
* Subtract 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if
* available.
*/
#if defined(__SSE2__)
static inline void sub16(uint16_t *dest, uint16_t *src)
{
__m128i *d, *s;
d = (__m128i *) dest;
s = (__m128i *) src;
*d = _mm_sub_epi16(*d, *s);
d++; s++;
*d = _mm_sub_epi16(*d, *s);
}
#elif defined(__MMX__)
static inline void sub16(uint16_t *dest, uint16_t *src)
{
__m64 *d, *s;
d = (__m64 *) dest;
s = (__m64 *) src;
*d = _mm_sub_pi16(*d, *s);
d++; s++;
*d = _mm_sub_pi16(*d, *s);
d++; s++;
*d = _mm_sub_pi16(*d, *s);
d++; s++;
*d = _mm_sub_pi16(*d, *s);
}
#elif defined(__ALTIVEC__)
static inline void sub16(uint16_t *dest, uint16_t *src)
{
vector unsigned short *d, *s;
d = (vector unsigned short *) dest;
s = (vector unsigned short *) src;
*d = vec_sub(*d, *s);
d++; s++;
*d = vec_sub(*d, *s);
}
#else
static inline void sub16(uint16_t *dest, uint16_t *src)
{
int i;
for (i = 0; i < 16; i++) dest[i] -= src[i];
}
#endif
+55 -18
View File
@@ -45,7 +45,7 @@ from __future__ import division
__all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb',
'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb',
'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab'
'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'is_rgb', 'is_gray'
]
__docformat__ = "restructuredtext en"
@@ -55,6 +55,30 @@ from scipy import linalg
from ..util import dtype
def is_rgb(image):
"""Test whether the image is RGB or RGBA.
Parameters
----------
image : ndarray
Input image.
"""
return (image.ndim == 3 and image.shape[2] in (3, 4))
def is_gray(image):
"""Test whether the image is gray (i.e. has only one color band).
Parameters
----------
image : ndarray
Input image.
"""
return image.ndim == 2
def convert_colorspace(arr, fromspace, tospace):
"""Convert an image array to a new color space.
@@ -263,8 +287,8 @@ sb_primaries = np.array([1. / 155, 1. / 190, 1. / 225]) * 1e5
# From sRGB specification
xyz_from_rgb = np.array([[0.412453, 0.357580, 0.180423],
[0.212671, 0.715160, 0.072169],
[0.019334, 0.119193, 0.950227]])
[0.212671, 0.715160, 0.072169],
[0.019334, 0.119193, 0.950227]])
rgb_from_xyz = linalg.inv(xyz_from_rgb)
@@ -281,7 +305,7 @@ rgbcie_from_rgb = np.dot(rgbcie_from_xyz, xyz_from_rgb)
rgb_from_rgbcie = np.dot(rgb_from_xyz, xyz_from_rgbcie)
grey_from_rgb = np.array([[0.2125, 0.7154, 0.0721],
gray_from_rgb = np.array([[0.2125, 0.7154, 0.0721],
[0, 0, 0],
[0, 0, 0]])
@@ -354,7 +378,13 @@ def xyz2rgb(xyz):
>>> lena_xyz = rgb2xyz(lena)
>>> lena_rgb = xyz2rgb(lena_xyz)
"""
return _convert(rgb_from_xyz, xyz)
# Follow the algorithm from http://www.easyrgb.com/index.php
# except we don't multiply/divide by 100 in the conversion
arr = _convert(rgb_from_xyz, xyz)
mask = arr > 0.0031308
arr[mask] = 1.055 * np.power(arr[mask], 1 / 2.4) - 0.055
arr[~mask] *= 12.92
return arr
def rgb2xyz(rgb):
@@ -390,7 +420,13 @@ def rgb2xyz(rgb):
>>> lena = data.lena()
>>> lena_xyz = rgb2xyz(lena)
"""
return _convert(xyz_from_rgb, rgb)
# Follow the algorithm from http://www.easyrgb.com/index.php
# except we don't multiply/divide by 100 in the conversion
arr = _prepare_colorarray(rgb).copy()
mask = arr > 0.04045
arr[mask] = np.power((arr[mask] + 0.055) / 1.055, 2.4)
arr[~mask] /= 12.92
return _convert(xyz_from_rgb, arr)
def rgb2rgbcie(rgb):
@@ -458,7 +494,7 @@ def rgbcie2rgb(rgbcie):
return _convert(rgb_from_rgbcie, rgbcie)
def rgb2grey(rgb):
def rgb2gray(rgb):
"""Compute luminance of an RGB image.
Parameters
@@ -475,7 +511,7 @@ def rgb2grey(rgb):
Raises
------
ValueError
If `rgb2grey` is not a 3-D array of shape (.., .., 3) or
If `rgb2gray` is not a 3-D array of shape (.., .., 3) or
(.., .., 4).
References
@@ -493,21 +529,21 @@ def rgb2grey(rgb):
Examples
--------
>>> from skimage.color import rgb2grey
>>> from skimage.color import rgb2gray
>>> from skimage import data
>>> lena = data.lena()
>>> lena_grey = rgb2grey(lena)
>>> lena_gray = rgb2gray(lena)
"""
if rgb.ndim == 2:
return rgb
return _convert(grey_from_rgb, rgb[:, :, :3])[..., 0]
return _convert(gray_from_rgb, rgb[:, :, :3])[..., 0]
rgb2gray = rgb2grey
rgb2grey = rgb2gray
def gray2rgb(image):
"""Create an RGB representation of a grey-level image.
"""Create an RGB representation of a gray-level image.
Parameters
----------
@@ -525,11 +561,12 @@ def gray2rgb(image):
If the input is not 2-dimensional.
"""
if image.ndim != 2:
raise ValueError('Gray-level image should be two-dimensional.')
M, N = image.shape
return np.dstack((image, image, image))
if is_rgb(image):
return image
elif is_gray(image):
return np.dstack((image, image, image))
else:
raise ValueError("Input image expected to be RGB, RGBA or gray.")
def xyz2lab(xyz):
+45 -4
View File
@@ -25,10 +25,11 @@ from skimage.color import (
convert_colorspace,
rgb2grey, gray2rgb,
xyz2lab, lab2xyz,
lab2rgb, rgb2lab
lab2rgb, rgb2lab,
is_rgb, is_gray
)
from skimage import data_dir
from skimage import data_dir, data
import colorsys
@@ -112,10 +113,14 @@ class TestColorconv(TestCase):
# XYZ to RGB
def test_xyz2rgb_conversion(self):
# only roundtrip test, we checked rgb2xyz above already
assert_almost_equal(xyz2rgb(rgb2xyz(self.colbars_array)),
self.colbars_array)
# RGB<->XYZ roundtrip on another image
def test_xyz_rgb_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(xyz2rgb(rgb2xyz(img_rgb)), img_rgb)
# RGB to RGB CIE
def test_rgb2rgbcie_conversion(self):
gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574],
@@ -174,11 +179,29 @@ class TestColorconv(TestCase):
assert_array_almost_equal(lab2xyz(self.lab_array),
self.xyz_array, decimal=3)
def test_rgb2lab_brucelindbloom(self):
"""
Test the RGB->Lab conversion by comparing to the calculator on the
authoritative Bruce Lindbloom
[website](http://brucelindbloom.com/index.html?ColorCalculator.html).
"""
# Obtained with D65 white point, sRGB model and gamma
gt_for_colbars = np.array([
[100,0,0],
[97.1393, -21.5537, 94.4780],
[91.1132, -48.0875, -14.1312],
[87.7347, -86.1827, 83.1793],
[60.3242, 98.2343, -60.8249],
[53.2408, 80.0925, 67.2032],
[32.2970, 79.1875, -107.8602],
[0,0,0]]).T
gt_array = np.swapaxes(gt_for_colbars.reshape(3, 4, 2), 0, 2)
assert_array_almost_equal(rgb2lab(self.colbars_array), gt_array, decimal=2)
def test_lab_rgb_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(lab2rgb(rgb2lab(img_rgb)), img_rgb)
def test_gray2rgb():
x = np.array([0, 0.5, 1])
assert_raises(ValueError, gray2rgb, x)
@@ -196,5 +219,23 @@ def test_gray2rgb():
assert_equal(z[..., 0], x)
assert_equal(z[0, 1, :], [128, 128, 128])
def test_gray2rgb_rgb():
x = np.random.random((5, 5, 4))
y = gray2rgb(x)
assert_equal(x, y)
def test_is_rgb():
color = data.lena()
gray = data.camera()
assert is_rgb(color)
assert not is_gray(color)
assert is_gray(gray)
assert not is_gray(color)
if __name__ == "__main__":
run_module_suite()
+13
View File
@@ -114,3 +114,16 @@ def page():
"""
return load("page.png")
def clock():
"""Motion blurred clock.
This photograph of a wall clock was taken while moving the camera in an
aproximately horizontal direction. It may be used to illustrate
inverse filters and deconvolution.
Released into the public domain by the photographer (Stefan van der Walt).
"""
return load("clock_motion.png")
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

+5
View File
@@ -34,6 +34,11 @@ def test_page():
data.page()
def test_page():
""" Test that "clock" image can be loaded. """
data.clock()
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()
+4 -1
View File
@@ -1,2 +1,5 @@
from ._draw import line, polygon, ellipse, circle
from ._draw import line, polygon, ellipse, ellipse_perimeter, \
circle, circle_perimeter, set_color
bresenham = line
+259 -49
View File
@@ -1,14 +1,16 @@
import numpy as np
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
import math
import numpy as np
cimport numpy as cnp
from libc.math cimport sqrt
cimport numpy as np
cimport cython
from skimage._shared.geometry cimport point_in_polygon
@cython.boundscheck(False)
@cython.wraparound(False)
def line(int y, int x, int y2, int x2):
def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
"""Generate line pixel coordinates.
Parameters
@@ -26,26 +28,31 @@ def line(int y, int x, int y2, int x2):
``img[rr, cc] = 1``.
"""
cdef np.ndarray[np.int32_t, ndim=1, mode="c"] rr, cc
cdef int steep = 0
cdef int dx = abs(x2 - x)
cdef int dy = abs(y2 - y)
cdef int sx, sy, d, i
cdef cnp.ndarray[cnp.intp_t, ndim=1, mode="c"] rr, cc
if (x2 - x) > 0: sx = 1
else: sx = -1
if (y2 - y) > 0: sy = 1
else: sy = -1
cdef char steep = 0
cdef Py_ssize_t dx = abs(x2 - x)
cdef Py_ssize_t dy = abs(y2 - y)
cdef Py_ssize_t sx, sy, d, i
if (x2 - x) > 0:
sx = 1
else:
sx = -1
if (y2 - y) > 0:
sy = 1
else:
sy = -1
if dy > dx:
steep = 1
x,y = y,x
dx,dy = dy,dx
sx,sy = sy,sx
x, y = y, x
dx, dy = dy, dx
sx, sy = sy, sx
d = (2 * dy) - dx
rr = np.zeros(int(dx) + 1, dtype=np.int32)
cc = np.zeros(int(dx) + 1, dtype=np.int32)
rr = np.zeros(int(dx) + 1, dtype=np.intp)
cc = np.zeros(int(dx) + 1, dtype=np.intp)
for i in range(dx):
if steep:
@@ -66,9 +73,6 @@ def line(int y, int x, int y2, int x2):
return rr, cc
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def polygon(y, x, shape=None):
"""Generate coordinates of pixels within polygon.
@@ -89,26 +93,28 @@ def polygon(y, x, shape=None):
Pixel coordinates of polygon.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
"""
cdef int nr_verts = x.shape[0]
cdef int minr = <int>max(0, y.min())
cdef int maxr = <int>math.ceil(y.max())
cdef int minc = <int>max(0, x.min())
cdef int maxc = <int>math.ceil(x.max())
cdef Py_ssize_t nr_verts = x.shape[0]
cdef Py_ssize_t minr = int(max(0, y.min()))
cdef Py_ssize_t maxr = int(math.ceil(y.max()))
cdef Py_ssize_t minc = int(max(0, x.min()))
cdef Py_ssize_t maxc = int(math.ceil(x.max()))
# make sure output coordinates do not exceed image size
if shape is not None:
maxr = min(shape[0]-1, maxr)
maxc = min(shape[1]-1, maxc)
maxr = min(shape[0] - 1, maxr)
maxc = min(shape[1] - 1, maxc)
cdef int r, c
cdef Py_ssize_t r, c
#: make contigous arrays for r, c coordinates
cdef np.ndarray contiguous_rdata, contiguous_cdata
cdef cnp.ndarray contiguous_rdata, contiguous_cdata
contiguous_rdata = np.ascontiguousarray(y, 'double')
contiguous_cdata = np.ascontiguousarray(x, 'double')
cdef np.double_t* rptr = <np.double_t*>contiguous_rdata.data
cdef np.double_t* cptr = <np.double_t*>contiguous_cdata.data
cdef cnp.double_t* rptr = <cnp.double_t*>contiguous_rdata.data
cdef cnp.double_t* cptr = <cnp.double_t*>contiguous_cdata.data
#: output coordinate arrays
cdef list rr = list()
@@ -123,19 +129,19 @@ def polygon(y, x, shape=None):
return np.array(rr), np.array(cc)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True)
def ellipse(double cy, double cx, double b, double a, shape=None):
def ellipse(double cy, double cx, double yradius, double xradius, shape=None):
"""Generate coordinates of pixels within ellipse.
Parameters
----------
cy, cx : double
Centre coordinate of ellipse.
b, a: double
Minor and major semi-axes. ``(x/a)**2 + (y/b)**2 = 1``.
yradius, xradius : double
Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``.
shape : tuple, optional
image shape which is used to determine maximum extents of output pixel
coordinates. This is useful for ellipses which exceed the image size.
By default the full extents of the ellipse are used.
Returns
-------
@@ -143,18 +149,20 @@ def ellipse(double cy, double cx, double b, double a, shape=None):
Pixel coordinates of ellipse.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
"""
cdef int minr = <int>max(0, cy-b)
cdef int maxr = <int>math.ceil(cy+b)
cdef int minc = <int>max(0, cx-a)
cdef int maxc = <int>math.ceil(cx+a)
cdef Py_ssize_t minr = int(max(0, cy - yradius))
cdef Py_ssize_t maxr = int(math.ceil(cy + yradius))
cdef Py_ssize_t minc = int(max(0, cx - xradius))
cdef Py_ssize_t maxc = int(math.ceil(cx + xradius))
# make sure output coordinates do not exceed image size
if shape is not None:
maxr = min(shape[0]-1, maxr)
maxc = min(shape[1]-1, maxc)
maxr = min(shape[0] - 1, maxr)
maxc = min(shape[1] - 1, maxc)
cdef int r, c
cdef Py_ssize_t r, c
#: output coordinate arrays
cdef list rr = list()
@@ -162,7 +170,7 @@ def ellipse(double cy, double cx, double b, double a, shape=None):
for r in range(minr, maxr+1):
for c in range(minc, maxc+1):
if sqrt(((r - cy)/b)**2 + ((c - cx)/a)**2) < 1:
if sqrt(((r - cy) / yradius)**2 + ((c - cx) / xradius)**2) < 1:
rr.append(r)
cc.append(c)
@@ -178,6 +186,10 @@ def circle(double cy, double cx, double radius, shape=None):
Centre coordinate of circle.
radius: double
Radius of circle.
shape : tuple, optional
image shape which is used to determine maximum extents of output pixel
coordinates. This is useful for circles which exceed the image size.
By default the full extents of the circle are used.
Returns
-------
@@ -185,5 +197,203 @@ def circle(double cy, double cx, double radius, shape=None):
Pixel coordinates of circle.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
Notes
-----
This function is a wrapper for skimage.draw.ellipse()
"""
return ellipse(cy, cx, radius, radius, shape)
def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
method='bresenham'):
"""Generate circle perimeter coordinates.
Parameters
----------
cy, cx : int
Centre coordinate of circle.
radius: int
Radius of circle.
method : {'bresenham', 'andres'}, optional
bresenham : Bresenham method
andres : Andres method
Returns
-------
rr, cc : (N,) ndarray of int
Indices of pixels that belong to the circle perimeter.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
Notes
-----
Andres method presents the advantage that concentric
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.
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.
"""
cdef list rr = list()
cdef list cc = list()
cdef Py_ssize_t x = 0
cdef Py_ssize_t y = radius
cdef Py_ssize_t d = 0
cdef char cmethod
if method == 'bresenham':
d = 3 - 2 * radius
cmethod = 'b'
elif method == 'andres':
d = radius - 1
cmethod = 'a'
else:
raise ValueError('Wrong method')
while y >= x:
rr.extend([y, -y, y, -y, x, -x, x, -x])
cc.extend([x, x, -x, -x, y, y, -y, -y])
if cmethod == 'b':
if d < 0:
d += 4 * x + 6
else:
d += 4 * (x - y) + 10
y -= 1
x += 1
elif cmethod == 'a':
if d >= 2 * (x - 1):
d = d - 2 * x
x = x + 1
elif d <= 2 * (radius - y):
d = d + 2 * y - 1
y = y - 1
else:
d = d + 2 * (y - x - 1)
y = y - 1
x = x + 1
return np.array(rr) + cy, np.array(cc) + cx
def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
Py_ssize_t xradius):
"""Generate ellipse perimeter coordinates.
Parameters
----------
cy, cx : int
Centre coordinate of ellipse.
yradius, xradius: int
Main radial values.
Returns
-------
rr, cc : (N,) ndarray of int
Indices of pixels that belong to the circle perimeter.
May be used to directly index into an array, e.g.
``img[rr, cc] = 1``.
References
----------
.. [1] J. Kennedy "A fast Bresenham type algorithm for
drawing ellipses".
"""
# If both radii == 0, return the center to avoid infinite loop in 2nd set
if xradius == 0 and yradius == 0:
return np.array(cy), np.array(cx)
# a and b are xradius an yradius compute 2a^2 and 2b^2
cdef Py_ssize_t twoasquared = 2 * xradius**2
cdef Py_ssize_t twobsquared = 2 * yradius**2
# Pixels
cdef list px = list()
cdef list py = list()
# First set of points:
# start at the top
cdef Py_ssize_t x = xradius
cdef Py_ssize_t y = 0
cdef Py_ssize_t err = 0
cdef Py_ssize_t xstop = twobsquared * xradius
cdef Py_ssize_t ystop = 0
cdef Py_ssize_t xchange = yradius * yradius * (1 - 2 * xradius)
cdef Py_ssize_t ychange = xradius * xradius
while xstop > ystop:
px.extend([x, -x, -x, x])
py.extend([y, y, -y, -y])
y += 1
ystop += twoasquared
err += ychange
ychange += twoasquared
if (2 * err + xchange) > 0:
x -= 1
xstop -= twobsquared
err += xchange
xchange += twobsquared
# Second set of points:
x = 0
y = yradius
err = 0
xstop = 0
ystop = twoasquared * yradius
xchange = yradius * yradius
ychange = xradius * xradius * (1 - 2 * yradius)
while xstop <= ystop:
px.extend([x, -x, -x, x])
py.extend([y, y, -y, -y])
x += 1
xstop += twobsquared
err += xchange
xchange += twobsquared
if (2 * err + ychange) > 0:
y -= 1
ystop -= twoasquared
err += ychange
ychange += twobsquared
return np.array(py) + cy, np.array(px) + cx
def set_color(img, coords, color):
"""Set pixel color in the image at the given coordinates.
Coordinates that exceed the shape of the image will be ignored.
Parameters
----------
img : (M, N, D) ndarray
Image
coords : ((P,) ndarray, (P,) ndarray)
Coordinates of pixels to be colored.
color : (D,) ndarray
Color to be assigned to coordinates in the image.
Returns
-------
img : (M, N, D) ndarray
The updated image.
"""
rr, cc = coords
rr_inside = np.logical_and(rr >= 0, rr < img.shape[0])
cc_inside = np.logical_and(cc >= 0, cc < img.shape[1])
inside = np.logical_and(rr_inside, cc_inside)
img[rr[inside], cc[inside]] = color
+1 -1
View File
@@ -15,7 +15,7 @@ def configuration(parent_package='', top_path=None):
cython(['_draw.pyx'], working_path=base_path)
config.add_extension('_draw', sources=['_draw.c'],
include_dirs=[get_numpy_include_dirs(), '../shared'])
include_dirs=[get_numpy_include_dirs(), '../_shared'])
return config
+107 -1
View File
@@ -1,7 +1,7 @@
from numpy.testing import assert_array_equal
import numpy as np
from skimage.draw import line, polygon, circle, ellipse
from skimage.draw import line, polygon, circle, circle_perimeter, ellipse, ellipse_perimeter
def test_line_horizontal():
@@ -150,6 +150,68 @@ def test_circle():
assert_array_equal(img, img_)
def test_circle_perimeter_bresenham():
img = np.zeros((15, 15), 'uint8')
rr, cc = circle_perimeter(7, 7, 0, method='bresenham')
img[rr, cc] = 1
assert(np.sum(img) == 1)
assert(img[7][7] == 1)
img = np.zeros((17, 15), 'uint8')
rr, cc = circle_perimeter(7, 7, 7, method='bresenham')
img[rr, cc] = 1
img_ = np.array(
[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 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],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
)
assert_array_equal(img, img_)
def test_circle_perimeter_andres():
img = np.zeros((15, 15), 'uint8')
rr, cc = circle_perimeter(7, 7, 0, method='andres')
img[rr, cc] = 1
assert(np.sum(img) == 1)
assert(img[7][7] == 1)
img = np.zeros((17, 15), 'uint8')
rr, cc = circle_perimeter(7, 7, 7, method='andres')
img[rr, cc] = 1
img_ = np.array(
[[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 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, 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')
@@ -176,6 +238,50 @@ def test_ellipse():
assert_array_equal(img, img_)
def test_ellipse_perimeter():
img = np.zeros((30, 15), 'uint8')
rr, cc = ellipse_perimeter(15, 7, 0, 0)
img[rr, cc] = 1
assert(np.sum(img) == 1)
assert(img[15][7] == 1)
img = np.zeros((30, 15), 'uint8')
rr, cc = ellipse_perimeter(15, 7, 14, 6)
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, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]]
)
assert_array_equal(img, img_)
if __name__ == "__main__":
from numpy.testing import run_module_suite
+3 -2
View File
@@ -1,2 +1,3 @@
from .exposure import histogram, equalize, cumulative_distribution
from .exposure import rescale_intensity
from .exposure import histogram, equalize, equalize_hist
from .exposure import rescale_intensity, cumulative_distribution
from ._adapthist import equalize_adapthist
+326
View File
@@ -0,0 +1,326 @@
"""
Adapted code from "Contrast Limited Adaptive Histogram Equalization" by Karel
Zuiderveld <karel@cv.ruu.nl>, Graphics Gems IV, Academic Press, 1994.
http://tog.acm.org/resources/GraphicsGems/gems.html#gemsvi
The Graphics Gems code is copyright-protected. In other words, you cannot
claim the text of the code as your own and resell it. Using the code is
permitted in any program, product, or library, non-commercial or commercial.
Giving credit is not required, though is a nice gesture. The code comes as-is,
and if there are any flaws or problems with any Gems code, nobody involved with
Gems - authors, editors, publishers, or webmasters - are to be held
responsible. Basically, don't be a jerk, and remember that anything free
comes with no guarantee.
"""
import numpy as np
import skimage
from skimage import color
from skimage.exposure import rescale_intensity
from skimage.util import view_as_blocks
MAX_REG_X = 16 # max. # contextual regions in x-direction */
MAX_REG_Y = 16 # max. # contextual regions in y-direction */
NR_OF_GREY = 16384 # number of grayscale levels to use in CLAHE algorithm
def equalize_adapthist(image, ntiles_x=8, ntiles_y=8, clip_limit=0.01,
nbins=256):
"""Contrast Limited Adaptive Histogram Equalization.
Parameters
----------
image : array-like
Input image.
ntiles_x : int, optional
Number of tile regions in the X direction. Ranges between 2 and 16.
ntiles_y : int, optional
Number of tile regions in the Y direction. Ranges between 2 and 16.
clip_limit : float: optional
Clipping limit, normalized between 0 and 1 (higher values give more
contrast).
nbins : int, optional
Number of gray bins for histogram ("dynamic range").
Returns
-------
out : ndarray
Equalized image.
Notes
-----
* The algorithm relies on an image whose rows and columns are even
multiples of the number of tiles, so the extra rows and columns are left
at their original values, thus preserving the input image shape.
* For color images, the following steps are performed:
- The image is converted to LAB color space
- The CLAHE algorithm is run on the L channel
- The image is converted back to RGB space and returned
* For RGBA images, the original alpha channel is removed.
References
----------
.. [1] http://tog.acm.org/resources/GraphicsGems/gems.html#gemsvi
.. [2] https://en.wikipedia.org/wiki/CLAHE#CLAHE
"""
args = [None, ntiles_x, ntiles_y, clip_limit * nbins, nbins]
if image.ndim > 2:
lab_img = color.rgb2lab(skimage.img_as_float(image))
l_chan = lab_img[:, :, 0]
l_chan /= np.max(np.abs(l_chan))
l_chan = skimage.img_as_uint(l_chan)
args[0] = rescale_intensity(l_chan, out_range=(0, NR_OF_GREY - 1))
new_l = _clahe(*args).astype(float)
new_l = rescale_intensity(new_l, out_range=(0, 100))
lab_img[:new_l.shape[0], :new_l.shape[1], 0] = new_l
image = color.lab2rgb(lab_img)
image = rescale_intensity(image, out_range=(0, 1))
else:
image = skimage.img_as_uint(image)
args[0] = rescale_intensity(image, out_range=(0, NR_OF_GREY - 1))
out = _clahe(*args)
image[:out.shape[0], :out.shape[1]] = out
image = rescale_intensity(image)
return image
def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128):
"""Contrast Limited Adaptive Histogram Equalization.
Parameters
----------
image : array-like
Input image.
ntiles_x : int, optional
Number of tile regions in the X direction. Ranges between 2 and 16.
ntiles_y : int, optional
Number of tile regions in the Y direction. Ranges between 2 and 16.
clip_limit : float, optional
Normalized clipping limit (higher values give more contrast).
nbins : int, optional
Number of gray bins for histogram ("dynamic range").
Returns
-------
out : ndarray
Equalized image.
The number of "effective" greylevels in the output image is set by `nbins`;
selecting a small value (eg. 128) speeds up processing and still produce
an output image of good quality. The output image will have the same
minimum and maximum value as the input image. A clip limit smaller than 1
results in standard (non-contrast limited) AHE.
"""
ntiles_x = min(ntiles_x, MAX_REG_X)
ntiles_y = min(ntiles_y, MAX_REG_Y)
ntiles_y = max(ntiles_x, 2)
ntiles_x = max(ntiles_y, 2)
if clip_limit == 1.0:
return image # is OK, immediately returns original image.
map_array = np.zeros((ntiles_y, ntiles_x, nbins), dtype=int)
y_res = image.shape[0] - image.shape[0] % ntiles_y
x_res = image.shape[1] - image.shape[1] % ntiles_x
image = image[: y_res, : x_res]
x_size = image.shape[1] / ntiles_x # Actual size of contextual regions
y_size = image.shape[0] / ntiles_y
n_pixels = x_size * y_size
if clip_limit > 0.0: # Calculate actual cliplimit
clip_limit = int(clip_limit * (x_size * y_size) / nbins)
if clip_limit < 1:
clip_limit = 1
else:
clip_limit = NR_OF_GREY # Large value, do not clip (AHE)
bin_size = 1 + NR_OF_GREY / nbins
aLUT = np.arange(NR_OF_GREY)
aLUT /= bin_size
img_blocks = view_as_blocks(image, (y_size, x_size))
# Calculate greylevel mappings for each contextual region
for y in range(ntiles_y):
for x in range(ntiles_x):
sub_img = img_blocks[y, x]
hist = aLUT[sub_img.ravel()]
hist = np.bincount(hist)
hist = np.append(hist, np.zeros(nbins - hist.size, dtype=int))
hist = clip_histogram(hist, clip_limit)
hist = map_histogram(hist, 0, NR_OF_GREY - 1, n_pixels)
map_array[y, x] = hist
# Interpolate greylevel mappings to get CLAHE image
ystart = 0
for y in range(ntiles_y + 1):
xstart = 0
if y == 0: # special case: top row
ystep = y_size / 2.0
yU = 0
yB = 0
elif y == ntiles_y: # special case: bottom row
ystep = y_size / 2.0
yU = ntiles_y - 1
yB = yU
else: # default values
ystep = y_size
yU = y - 1
yB = yB + 1
for x in range(ntiles_x + 1):
if x == 0: # special case: left column
xstep = x_size / 2.0
xL = 0
xR = 0
elif x == ntiles_x: # special case: right column
xstep = x_size / 2.0
xL = ntiles_x - 1
xR = xL
else: # default values
xstep = x_size
xL = x - 1
xR = xL + 1
mapLU = map_array[yU, xL]
mapRU = map_array[yU, xR]
mapLB = map_array[yB, xL]
mapRB = map_array[yB, xR]
xslice = np.arange(xstart, xstart + xstep)
yslice = np.arange(ystart, ystart + ystep)
interpolate(image, xslice, yslice,
mapLU, mapRU, mapLB, mapRB, aLUT)
xstart += xstep # set pointer on next matrix */
ystart += ystep
return image
def clip_histogram(hist, clip_limit):
"""Perform clipping of the histogram and redistribution of bins.
The histogram is clipped and the number of excess pixels is counted.
Afterwards the excess pixels are equally redistributed across the
whole histogram (providing the bin count is smaller than the cliplimit).
Parameters
----------
hist : ndarray
Histogram array.
clip_limit : int
Maximum allowed bin count.
Returns
-------
hist : ndarray
Clipped histogram.
"""
# calculate total number of excess pixels
excess_mask = hist > clip_limit
excess = hist[excess_mask]
n_excess = excess.sum() - excess.size * clip_limit
# Second part: clip histogram and redistribute excess pixels in each bin
bin_incr = int(n_excess / hist.size) # average binincrement
upper = clip_limit - bin_incr # Bins larger than upper set to cliplimit
hist[excess_mask] = clip_limit
low_mask = hist < upper
n_excess -= hist[low_mask].size * bin_incr
hist[low_mask] += bin_incr
mid_mask = (hist >= upper) & (hist < clip_limit)
mid = hist[mid_mask]
n_excess -= mid.size * clip_limit - mid.sum()
hist[mid_mask] = clip_limit
while n_excess > 0: # Redistribute remaining excess
index = 0
while n_excess > 0 and index < hist.size:
step_size = int(hist[hist < clip_limit].size / n_excess)
step_size = max(step_size, 1)
indices = np.arange(index, hist.size, step_size)
under = hist[indices] < clip_limit
hist[under] += 1
n_excess -= hist[under].size
index += 1
return hist
def map_histogram(hist, min_val, max_val, n_pixels):
"""Calculate the equalized lookup table (mapping).
It does so by cumulating the input histogram.
Parameters
----------
hist : ndarray
Clipped histogram.
min_val : int
Minimum value for mapping.
max_val : int
Maximum value for mapping.
n_pixels : int
Number of pixels in the region.
Returns
-------
out : ndarray
Mapped intensity LUT.
"""
out = np.cumsum(hist).astype(float)
scale = ((float)(max_val - min_val)) / n_pixels
out *= scale
out += min_val
out[out > max_val] = max_val
return out.astype(int)
def interpolate(image, xslice, yslice,
mapLU, mapRU, mapLB, mapRB, aLUT):
"""Find the new grayscale level for a region using bilinear interpolation.
Parameters
----------
image : ndarray
Full image.
xslice, yslice : array-like
Indices of the region.
map* : ndarray
Mappings of greylevels from histograms.
aLUT : ndarray
Maps grayscale levels in image to histogram levels.
Returns
-------
out : ndarray
Original image with the subregion replaced.
Notes
-----
This function calculates the new greylevel assignments of pixels within
a submatrix of the image. This is done by a bilinear interpolation between
four different mappings in order to eliminate boundary artifacts.
"""
norm = xslice.size * yslice.size # Normalization factor
# interpolation weight matrices
x_coef, y_coef = np.meshgrid(np.arange(xslice.size),
np.arange(yslice.size))
x_inv_coef, y_inv_coef = x_coef[:, ::-1] + 1, y_coef[::-1] + 1
view = image[yslice[0]: yslice[-1] + 1, xslice[0]: xslice[-1] + 1]
im_slice = aLUT[view]
new = ((y_inv_coef * (x_inv_coef * mapLU[im_slice]
+ x_coef * mapRU[im_slice])
+ y_coef * (x_inv_coef * mapLB[im_slice]
+ x_coef * mapRB[im_slice]))
/ norm)
view[:, :] = new
return image
+8
View File
@@ -2,6 +2,9 @@ import numpy as np
from skimage import img_as_float
from skimage.util.dtype import dtype_range
import skimage.color as color
from skimage.util.dtype import convert
from skimage._shared.utils import deprecated
__all__ = ['histogram', 'cumulative_distribution', 'equalize',
@@ -76,7 +79,12 @@ def cumulative_distribution(image, nbins=256):
return img_cdf, bin_centers
@deprecated('equalize_hist')
def equalize(image, nbins=256):
return equalize_hist(image, nbins)
def equalize_hist(image, nbins=256):
"""Return image after histogram equalization.
Parameters
+99 -4
View File
@@ -1,21 +1,23 @@
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
import skimage
from skimage import data
from skimage import exposure
from skimage.color import rgb2gray
from skimage.util.dtype import dtype_range
# Test histogram equalization
# ===========================
# squeeze image intensities to lower image contrast
test_img = exposure.rescale_intensity(data.camera() / 5. + 100)
test_img = skimage.img_as_float(data.camera())
test_img = exposure.rescale_intensity(test_img / 5. + 100)
def test_equalize_ubyte():
img = skimage.img_as_ubyte(test_img)
img_eq = exposure.equalize(img)
img_eq = exposure.equalize_hist(img)
cdf, bin_edges = exposure.cumulative_distribution(img_eq)
check_cdf_slope(cdf)
@@ -23,7 +25,7 @@ def test_equalize_ubyte():
def test_equalize_float():
img = skimage.img_as_float(test_img)
img_eq = exposure.equalize(img)
img_eq = exposure.equalize_hist(img)
cdf, bin_edges = exposure.cumulative_distribution(img_eq)
check_cdf_slope(cdf)
@@ -71,6 +73,99 @@ def test_rescale_out_range():
assert_close(out, [0, 63, 127])
# Test adaptive histogram equalization
# ====================================
def test_adapthist_scalar():
'''Test a scalar uint8 image
'''
img = skimage.img_as_ubyte(data.moon())
adapted = exposure.equalize_adapthist(img, clip_limit=0.02)
assert adapted.min() == 0
assert adapted.max() == (1 << 16) - 1
assert img.shape == adapted.shape
full_scale = skimage.exposure.rescale_intensity(skimage.img_as_uint(img))
assert_almost_equal = np.testing.assert_almost_equal
assert_almost_equal(peak_snr(full_scale, adapted), 101.231, 3)
assert_almost_equal(norm_brightness_err(full_scale, adapted),
0.041, 3)
return img, adapted
def test_adapthist_grayscale():
'''Test a grayscale float image
'''
img = skimage.img_as_float(data.lena())
img = rgb2gray(img)
img = np.dstack((img, img, img))
adapted = exposure.equalize_adapthist(img, 10, 9, clip_limit=0.01,
nbins=128)
assert_almost_equal = np.testing.assert_almost_equal
assert img.shape == adapted.shape
assert_almost_equal(peak_snr(img, adapted), 97.531, 3)
assert_almost_equal(norm_brightness_err(img, adapted), 0.0313, 3)
return data, adapted
def test_adapthist_color():
'''Test an RGB color uint16 image
'''
img = skimage.img_as_uint(data.lena())
adapted = exposure.equalize_adapthist(img, clip_limit=0.01)
assert_almost_equal = np.testing.assert_almost_equal
assert adapted.min() == 0
assert adapted.max() == 1.0
assert img.shape == adapted.shape
full_scale = skimage.exposure.rescale_intensity(img)
assert_almost_equal(peak_snr(full_scale, adapted), 102.940, 3)
assert_almost_equal(norm_brightness_err(full_scale, adapted),
0.0110, 3)
return data, adapted
def peak_snr(img1, img2):
'''Peak signal to noise ratio of two images
Parameters
----------
img1 : array-like
img2 : array-like
Returns
-------
peak_snr : float
Peak signal to noise ratio
'''
if img1.ndim == 3:
img1, img2 = rgb2gray(img1.copy()), rgb2gray(img2.copy())
img1 = skimage.img_as_float(img1)
img2 = skimage.img_as_float(img2)
mse = 1. / img1.size * np.square(img1 - img2).sum()
_, max_ = dtype_range[img1.dtype.type]
return 20 * np.log(max_ / mse)
def norm_brightness_err(img1, img2):
'''Normalized Absolute Mean Brightness Error between two images
Parameters
----------
img1 : array-like
img2 : array-like
Returns
-------
norm_brightness_error : float
Normalized absolute mean brightness error
'''
if img1.ndim == 3:
img1, img2 = rgb2gray(img1), rgb2gray(img2)
ambe = np.abs(img1.mean() - img2.mean())
nbe = ambe / dtype_range[img1.dtype.type][1]
return nbe
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+4 -1
View File
@@ -1,5 +1,8 @@
from ._daisy import daisy
from ._hog import hog
from .texture import greycomatrix, greycoprops, local_binary_pattern
from .peak import peak_local_max
from ._harris import harris
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
+223
View File
@@ -0,0 +1,223 @@
import numpy as np
from scipy import sqrt, pi, arctan2, cos, sin, exp
from scipy.ndimage import gaussian_filter
import skimage.color
from skimage import img_as_float, draw
def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8,
normalization='l1', sigmas=None, ring_radii=None, visualize=False):
'''Extract DAISY feature descriptors densely for the given image.
DAISY is a feature descriptor similar to SIFT formulated in a way that
allows for fast dense extraction. Typically, this is practical for
bag-of-features image representations.
The implementation follows Tola et al. [1]_ but deviate on the following
points:
* Histogram bin contribution are smoothed with a circular Gaussian
window over the tonal range (the angular range).
* The sigma values of the spatial Gaussian smoothing in this code do not
match the sigma values in the original code by Tola et al. [2]_. In
their code, spatial smoothing is applied to both the input image and
the center histogram. However, this smoothing is not documented in [1]_
and, therefore, it is omitted.
Parameters
----------
img : (M, N) array
Input image (greyscale).
step : int, optional
Distance between descriptor sampling points.
radius : int, optional
Radius (in pixels) of the outermost ring.
rings : int, optional
Number of rings.
histograms : int, optional
Number of histograms sampled per ring.
orientations : int, optional
Number of orientations (bins) per histogram.
normalization : [ 'l1' | 'l2' | 'daisy' | 'off' ], optional
How to normalize the descriptors
* 'l1': L1-normalization of each descriptor.
* 'l2': L2-normalization of each descriptor.
* 'daisy': L2-normalization of individual histograms.
* 'off': Disable normalization.
sigmas : 1D array of float, optional
Standard deviation of spatial Gaussian smoothing for the center
histogram and for each ring of histograms. The array of sigmas should
be sorted from the center and out. I.e. the first sigma value defines
the spatial smoothing of the center histogram and the last sigma value
defines the spatial smoothing of the outermost ring. Specifying sigmas
overrides the following parameter.
``rings = len(sigmas) - 1``
ring_radii : 1D array of int, optional
Radius (in pixels) for each ring. Specifying ring_radii overrides the
following two parameters.
``rings = len(ring_radii)``
``radius = ring_radii[-1]``
If both sigmas and ring_radii are given, they must satisfy the
following predicate since no radius is needed for the center
histogram.
``len(ring_radii) == len(sigmas) + 1``
visualize : bool, optional
Generate a visualization of the DAISY descriptors
Returns
-------
descs : array
Grid of DAISY descriptors for the given image as an array
dimensionality (P, Q, R) where
``P = ceil((M - radius*2) / step)``
``Q = ceil((N - radius*2) / step)``
``R = (rings * histograms + 1) * orientations``
descs_img : (M, N, 3) array (only if visualize==True)
Visualization of the DAISY descriptors.
References
----------
.. [1] Tola et al. "Daisy: An efficient dense descriptor applied to wide-
baseline stereo." Pattern Analysis and Machine Intelligence, IEEE
Transactions on 32.5 (2010): 815-830.
.. [2] http://cvlab.epfl.ch/alumni/tola/daisy.html
'''
# Validate image format.
if img.ndim > 2:
raise ValueError('Only grey-level images are supported.')
if img.dtype.kind != 'f':
img = img_as_float(img)
# Validate parameters.
if sigmas is not None and ring_radii is not None \
and len(sigmas) - 1 != len(ring_radii):
raise ValueError('len(sigmas)-1 != len(ring_radii)')
if ring_radii is not None:
rings = len(ring_radii)
radius = ring_radii[-1]
if sigmas is not None:
rings = len(sigmas) - 1
if sigmas is None:
sigmas = [radius * (i + 1) / float(2 * rings) for i in range(rings)]
if ring_radii is None:
ring_radii = [radius * (i + 1) / float(rings) for i in range(rings)]
if normalization not in ['l1', 'l2', 'daisy', 'off']:
raise ValueError('Invalid normalization method.')
# Compute image derivatives.
dx = np.zeros(img.shape)
dy = np.zeros(img.shape)
dx[:, :-1] = np.diff(img, n=1, axis=1)
dy[:-1, :] = np.diff(img, n=1, axis=0)
# Compute gradient orientation and magnitude and their contribution
# to the histograms.
grad_mag = sqrt(dx ** 2 + dy ** 2)
grad_ori = arctan2(dy, dx)
orientation_kappa = orientations / pi
orientation_angles = [2 * o * pi / orientations - pi
for o in range(orientations)]
hist = np.empty((orientations,) + img.shape, dtype=float)
for i, o in enumerate(orientation_angles):
# Weigh bin contribution by the circular normal distribution
hist[i, :, :] = exp(orientation_kappa * cos(grad_ori - o))
# Weigh bin contribution by the gradient magnitude
hist[i, :, :] = np.multiply(hist[i, :, :], grad_mag)
# Smooth orientation histograms for the center and all rings.
sigmas = [sigmas[0]] + sigmas
hist_smooth = np.empty((rings + 1,) + hist.shape, dtype=float)
for i in range(rings + 1):
for j in range(orientations):
hist_smooth[i, j, :, :] = gaussian_filter(hist[j, :, :],
sigma=sigmas[i])
# Assemble descriptor grid.
theta = [2 * pi * j / histograms for j in range(histograms)]
desc_dims = (rings * histograms + 1) * orientations
descs = np.empty((desc_dims, img.shape[0] - 2 * radius,
img.shape[1] - 2 * radius))
descs[:orientations, :, :] = hist_smooth[0, :, radius:-radius,
radius:-radius]
idx = orientations
for i in range(rings):
for j in range(histograms):
y_min = radius + int(round(ring_radii[i] * sin(theta[j])))
y_max = descs.shape[1] + y_min
x_min = radius + int(round(ring_radii[i] * cos(theta[j])))
x_max = descs.shape[2] + x_min
descs[idx:idx + orientations, :, :] = hist_smooth[i + 1, :,
y_min:y_max,
x_min:x_max]
idx += orientations
descs = descs[:, ::step, ::step]
descs = descs.swapaxes(0, 1).swapaxes(1, 2)
# Normalize descriptors.
if normalization != 'off':
descs += 1e-10
if normalization == 'l1':
descs /= np.sum(descs, axis=2)[:, :, np.newaxis]
elif normalization == 'l2':
descs /= sqrt(np.sum(descs ** 2, axis=2))[:, :, np.newaxis]
elif normalization == 'daisy':
for i in range(0, desc_dims, orientations):
norms = sqrt(np.sum(descs[:, :, i:i + orientations] ** 2,
axis=2))
descs[:, :, i:i + orientations] /= norms[:, :, np.newaxis]
if visualize:
descs_img = skimage.color.gray2rgb(img)
for i in range(descs.shape[0]):
for j in range(descs.shape[1]):
# Draw center histogram sigma
color = (1, 0, 0)
desc_y = i * step + radius
desc_x = j * step + radius
coords = draw.circle_perimeter(desc_y, desc_x, int(sigmas[0]))
draw.set_color(descs_img, coords, color)
max_bin = np.max(descs[i, j, :])
for o_num, o in enumerate(orientation_angles):
# Draw center histogram bins
bin_size = descs[i, j, o_num] / max_bin
dy = sigmas[0] * bin_size * sin(o)
dx = sigmas[0] * bin_size * cos(o)
coords = draw.line(desc_y, desc_x, int(desc_y + dy),
int(desc_x + dx))
draw.set_color(descs_img, coords, color)
for r_num, r in enumerate(ring_radii):
color_offset = float(1 + r_num) / rings
color = (1 - color_offset, 1, color_offset)
for t_num, t in enumerate(theta):
# Draw ring histogram sigmas
hist_y = desc_y + int(round(r * sin(t)))
hist_x = desc_x + int(round(r * cos(t)))
coords = draw.circle_perimeter(hist_y, hist_x,
int(sigmas[r_num + 1]))
draw.set_color(descs_img, coords, color)
for o_num, o in enumerate(orientation_angles):
# Draw histogram bins
bin_size = descs[i, j, orientations + r_num *
histograms * orientations +
t_num * orientations + o_num]
bin_size /= max_bin
dy = sigmas[r_num + 1] * bin_size * sin(o)
dx = sigmas[r_num + 1] * bin_size * cos(o)
coords = draw.line(hist_y, hist_x,
int(hist_y + dy),
int(hist_x + dx))
draw.set_color(descs_img, coords, color)
return descs, descs_img
else:
return descs
-109
View File
@@ -1,109 +0,0 @@
"""
Harris corner detector
Inspired from Solem's implementation
http://www.janeriksolem.net/2009/01/harris-corner-detector-in-python.html
"""
from scipy import ndimage
from . import peak
def _compute_harris_response(image, eps=1e-6, gaussian_deviation=1):
"""Compute the Harris corner detector response function
for each pixel in the image
Parameters
----------
image : ndarray of floats
Input image.
eps : float, optional
Normalisation factor.
gaussian_deviation : integer, optional
Standard deviation used for the Gaussian kernel.
Returns
--------
image : (M, N) ndarray
Harris image response
"""
if len(image.shape) == 3:
image = image.mean(axis=2)
# derivatives
image = ndimage.gaussian_filter(image, gaussian_deviation)
imx = ndimage.sobel(image, axis=0, mode='constant')
imy = ndimage.sobel(image, axis=1, mode='constant')
Wxx = ndimage.gaussian_filter(imx * imx, 1.5, mode='constant')
Wxy = ndimage.gaussian_filter(imx * imy, 1.5, mode='constant')
Wyy = ndimage.gaussian_filter(imy * imy, 1.5, mode='constant')
# determinant and trace
Wdet = Wxx * Wyy - Wxy**2
Wtr = Wxx + Wyy
# Alternate formula for Harris response.
# Alison Noble, "Descriptions of Image Surfaces", PhD thesis (1989)
harris = Wdet / (Wtr + eps)
return harris
def harris(image, min_distance=10, threshold=0.1, eps=1e-6,
gaussian_deviation=1):
"""Return corners from a Harris response image
Parameters
----------
image : ndarray of floats
Input image.
min_distance : int, optional
Minimum number of pixels separating interest points and image boundary.
threshold : float, optional
Relative threshold impacting the number of interest points.
eps : float, optional
Normalisation factor.
gaussian_deviation : integer, optional
Standard deviation used for the Gaussian kernel.
Returns
-------
coordinates : (N, 2) array
(row, column) coordinates of interest points.
Examples
-------
>>> square = np.zeros([10,10])
>>> square[2:8,2:8] = 1
>>> square
array([[ 0., 0., 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., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 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., 0., 0.]])
>>> harris(square, min_distance=1)
Corners of the square
array([[3, 3],
[3, 6],
[6, 3],
[6, 6]])
"""
harrisim = _compute_harris_response(image, eps=eps,
gaussian_deviation=gaussian_deviation)
coordinates = peak.peak_local_max(harrisim, min_distance=min_distance,
threshold_rel=threshold)
return coordinates
+4 -2
View File
@@ -142,8 +142,10 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
centre = tuple([y * cy + cy // 2, x * cx + cx // 2])
dx = radius * cos(float(o) / orientations * np.pi)
dy = radius * sin(float(o) / orientations * np.pi)
rr, cc = draw.bresenham(centre[0] - dy, centre[1] - dx,
centre[0] + dy, centre[1] + dx)
rr, cc = draw.bresenham(int(centre[0] - dx),
int(centre[1] - dy),
int(centre[0] + dx),
int(centre[1] + dy))
hog_image[rr, cc] += orientation_histogram[y, x, o]
"""
+34 -23
View File
@@ -1,3 +1,8 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
"""
Template matching using normalized cross-correlation.
@@ -30,24 +35,31 @@ the image window *before* squaring.)
.. [2] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and
Magic.
"""
import cython
cimport numpy as np
import numpy as np
from scipy.signal import fftconvolve
from skimage.transform import integral
cimport numpy as cnp
from libc.math cimport sqrt, fabs
from skimage._shared.transform cimport integrate
@cython.boundscheck(False)
def match_template(np.ndarray[float, ndim=2, mode="c"] image,
np.ndarray[float, ndim=2, mode="c"] template):
cdef np.ndarray[float, ndim=2, mode="c"] corr
cdef np.ndarray[float, ndim=2, mode="c"] image_sat
cdef np.ndarray[float, ndim=2, mode="c"] image_sqr_sat
from skimage.transform import integral
def match_template(cnp.ndarray[float, ndim=2, mode="c"] image,
cnp.ndarray[float, ndim=2, mode="c"] template):
cdef cnp.ndarray[float, ndim=2, mode="c"] corr
cdef cnp.ndarray[float, ndim=2, mode="c"] image_sat
cdef cnp.ndarray[float, ndim=2, mode="c"] image_sqr_sat
cdef float template_mean = np.mean(template)
cdef float template_ssd
cdef float inv_area
cdef Py_ssize_t r, c, r_end, c_end
cdef Py_ssize_t template_rows = template.shape[0]
cdef Py_ssize_t template_cols = template.shape[1]
cdef float den, window_sqr_sum, window_mean_sqr, window_sum
image_sat = integral.integral_image(image)
image_sqr_sat = integral.integral_image(image**2)
@@ -63,24 +75,23 @@ def match_template(np.ndarray[float, ndim=2, mode="c"] image,
mode="valid"),
dtype=np.float32)
cdef int i, j
cdef float den, window_sqr_sum, window_mean_sqr, window_sum,
# move window through convolution results, normalizing in the process
for i in range(corr.shape[0]):
for j in range(corr.shape[1]):
# subtract 1 because `i_end` and `j_end` are used for indexing into
# summed-area table, instead of slicing windows of the image.
i_end = i + template.shape[0] - 1
j_end = j + template.shape[1] - 1
window_sum = integrate(image_sat, i, j, i_end, j_end)
# move window through convolution results, normalizing in the process
for r in range(corr.shape[0]):
for c in range(corr.shape[1]):
# subtract 1 because `i_end` and `c_end` are used for indexing into
# summed-area table, instead of slicing windows of the image.
r_end = r + template_rows - 1
c_end = c + template_cols - 1
window_sum = integrate(image_sat, r, c, r_end, c_end)
window_mean_sqr = window_sum * window_sum * inv_area
window_sqr_sum = integrate(image_sqr_sat, i, j, i_end, j_end)
window_sqr_sum = integrate(image_sqr_sat, r, c, r_end, c_end)
if window_sqr_sum <= window_mean_sqr:
corr[i, j] = 0
corr[r, c] = 0
continue
den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd)
corr[i, j] /= den
return corr
corr[r, c] /= den
return corr
+28 -26
View File
@@ -3,21 +3,20 @@
#cython: nonecheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as np
cimport numpy as cnp
from libc.math cimport sin, cos, abs
from skimage._shared.interpolation cimport bilinear_interpolation
def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2,
negative_indices=False, mode='c'] image,
np.ndarray[dtype=np.float64_t, ndim=1,
negative_indices=False, mode='c'] distances,
np.ndarray[dtype=np.float64_t, ndim=1,
negative_indices=False, mode='c'] angles,
def _glcm_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] image,
cnp.ndarray[dtype=cnp.float64_t, ndim=1,
negative_indices=False, mode='c'] distances,
cnp.ndarray[dtype=cnp.float64_t, ndim=1,
negative_indices=False, mode='c'] angles,
int levels,
np.ndarray[dtype=np.uint32_t, ndim=4,
negative_indices=False, mode='c'] out
):
cnp.ndarray[dtype=cnp.uint32_t, ndim=4,
negative_indices=False, mode='c'] out):
"""Perform co-occurrence matrix accumulation.
Parameters
@@ -37,23 +36,26 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2,
the results of the GLCM computation.
"""
cdef:
np.int32_t a_inx, d_idx
np.int32_t r, c, rows, cols, row, col
np.int32_t i, j
Py_ssize_t a_idx, d_idx, r, c, rows, cols, row, col
cnp.uint8_t i, j
cnp.float64_t angle, distance
rows = image.shape[0]
cols = image.shape[1]
for a_idx, angle in enumerate(angles):
for d_idx, distance in enumerate(distances):
for a_idx in range(len(angles)):
angle = angles[a_idx]
for d_idx in range(len(distances)):
distance = distances[d_idx]
for r in range(rows):
for c in range(cols):
i = image[r, c]
# compute the location of the offset pixel
row = r + <int>(sin(angle) * distance + 0.5)
col = c + <int>(cos(angle) * distance + 0.5);
col = c + <int>(cos(angle) * distance + 0.5)
# make sure the offset is within bounds
if row >= 0 and row < rows and \
@@ -79,7 +81,7 @@ cdef inline int _bit_rotate_right(int value, int length):
return (value >> 1) | ((value & 1) << (length - 1))
def _local_binary_pattern(np.ndarray[double, ndim=2] image,
def _local_binary_pattern(cnp.ndarray[double, ndim=2] image,
int P, float R, char method='D'):
"""Gray scale and rotation invariant LBP (Local Binary Patterns).
@@ -109,25 +111,25 @@ def _local_binary_pattern(np.ndarray[double, ndim=2] image,
"""
# texture weights
cdef np.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32)
cdef cnp.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32)
# local position of texture elements
rp = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.double) / P)
cp = R * np.cos(2 * np.pi * np.arange(P, dtype=np.double) / P)
cdef np.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5)
cdef cnp.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5)
# pre allocate arrays for computation
cdef np.ndarray[double, ndim=1] texture = np.zeros(P, np.double)
cdef np.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8)
cdef np.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32)
cdef cnp.ndarray[double, ndim=1] texture = np.zeros(P, np.double)
cdef cnp.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8)
cdef cnp.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32)
output_shape = (image.shape[0], image.shape[1])
cdef np.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double)
cdef cnp.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double)
cdef int rows = image.shape[0]
cdef int cols = image.shape[1]
cdef Py_ssize_t rows = image.shape[0]
cdef Py_ssize_t cols = image.shape[1]
cdef double lbp
cdef int r, c, changes, i
cdef Py_ssize_t r, c, changes, i
for r in range(image.shape[0]):
for c in range(image.shape[1]):
for i in range(P):
+505
View File
@@ -0,0 +1,505 @@
import numpy as np
from scipy import ndimage
from scipy import stats
from skimage.color import rgb2grey
from skimage.util import img_as_float
from skimage.feature import peak_local_max
def _compute_derivatives(image):
"""Compute derivatives in x and y direction using the Sobel operator.
Parameters
----------
image : ndarray
Input image.
Returns
-------
imx : ndarray
Derivative in x-direction.
imy : ndarray
Derivative in y-direction.
"""
imy = ndimage.sobel(image, axis=0, mode='constant', cval=0)
imx = ndimage.sobel(image, axis=1, mode='constant', cval=0)
return imx, imy
def _compute_auto_correlation(image, sigma):
"""Compute auto-correlation matrix using sum of squared differences.
Parameters
----------
image : ndarray
Input image.
sigma : float
Standard deviation used for the Gaussian kernel, which is used as
weighting function for the auto-correlation matrix.
Returns
-------
Axx : ndarray
Element of the auto-correlation matrix for each pixel in input image.
Axy : ndarray
Element of the auto-correlation matrix for each pixel in input image.
Ayy : ndarray
Element of the auto-correlation matrix for each pixel in input image.
"""
if image.ndim == 3:
image = img_as_float(rgb2grey(image))
imx, imy = _compute_derivatives(image)
# structure tensore
Axx = ndimage.gaussian_filter(imx * imx, sigma, mode='constant', cval=0)
Axy = ndimage.gaussian_filter(imx * imy, sigma, mode='constant', cval=0)
Ayy = ndimage.gaussian_filter(imy * imy, sigma, mode='constant', cval=0)
return Axx, Axy, Ayy
def corner_kitchen_rosenfeld(image):
"""Compute Kitchen and Rosenfeld corner measure response image.
The corner measure is calculated as follows::
(imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)
------------------------------------------------------
(imx**2 + imy**2)
Where imx and imy are the first and imxx, imxy, imyy the second derivatives.
Parameters
----------
image : ndarray
Input image.
Returns
-------
response : ndarray
Kitchen and Rosenfeld response image.
"""
imx, imy = _compute_derivatives(image)
imxx, imxy = _compute_derivatives(imx)
imyx, imyy = _compute_derivatives(imy)
response = (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy) \
/ (imx**2 + imy**2)
return response
def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
"""Compute Harris corner measure response image.
This corner detector uses information from the auto-correlation matrix A::
A = [(imx**2) (imx*imy)] = [Axx Axy]
[(imx*imy) (imy**2)] [Axy Ayy]
Where imx and imy are the first derivatives averaged with a gaussian filter.
The corner measure is then defined as::
det(A) - k * trace(A)**2
or::
2 * det(A) / (trace(A) + eps)
Parameters
----------
image : ndarray
Input image.
method : {'k', 'eps'}, optional
Method to compute the response image from the auto-correlation matrix.
k : float, optional
Sensitivity factor to separate corners from edges, typically in range
`[0, 0.2]`. Small values of k result in detection of sharp corners.
eps : float, optional
Normalisation factor (Noble's corner measure).
sigma : float, optional
Standard deviation used for the Gaussian kernel, which is used as
weighting function for the auto-correlation matrix.
Returns
-------
response : ndarray
Harris response image.
References
----------
..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm
..[2] http://en.wikipedia.org/wiki/Corner_detection
Examples
--------
>>> from skimage.feature import corner_harris, corner_peaks
>>> square = np.zeros([10, 10])
>>> square[2:8, 2:8] = 1
>>> square
array([[ 0, 0, 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, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 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, 0, 0]])
>>> corner_peaks(corner_harris(square), min_distance=1)
array([[2, 2],
[2, 7],
[7, 2],
[7, 7]])
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
# determinant
detA = Axx * Ayy - Axy**2
# trace
traceA = Axx + Ayy
if method == 'k':
response = detA - k * traceA**2
else:
response = 2 * detA / (traceA + eps)
return response
def corner_shi_tomasi(image, sigma=1):
"""Compute Shi-Tomasi (Kanade-Tomasi) corner measure response image.
This corner detector uses information from the auto-correlation matrix A::
A = [(imx**2) (imx*imy)] = [Axx Axy]
[(imx*imy) (imy**2)] [Axy Ayy]
Where imx and imy are the first derivatives averaged with a gaussian filter.
The corner measure is then defined as the smaller eigenvalue of A::
((Axx + Ayy) - sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2
Parameters
----------
image : ndarray
Input image.
sigma : float, optional
Standard deviation used for the Gaussian kernel, which is used as
weighting function for the auto-correlation matrix.
Returns
-------
response : ndarray
Shi-Tomasi response image.
References
----------
..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm
..[2] http://en.wikipedia.org/wiki/Corner_detection
Examples
--------
>>> from skimage.feature import corner_shi_tomasi, corner_peaks
>>> square = np.zeros([10, 10])
>>> square[2:8, 2:8] = 1
>>> square
array([[ 0, 0, 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, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 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, 0, 0]])
>>> corner_peaks(corner_shi_tomasi(square), min_distance=1)
array([[2, 2],
[2, 7],
[7, 2],
[7, 7]])
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
# minimum eigenvalue of A
response = ((Axx + Ayy) - np.sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2
return response
def corner_foerstner(image, sigma=1):
"""Compute Foerstner corner measure response image.
This corner detector uses information from the auto-correlation matrix A::
A = [(imx**2) (imx*imy)] = [Axx Axy]
[(imx*imy) (imy**2)] [Axy Ayy]
Where imx and imy are the first derivatives averaged with a gaussian filter.
The corner measure is then defined as::
w = det(A) / trace(A) (size of error ellipse)
q = 4 * det(A) / trace(A)**2 (roundness of error ellipse)
Parameters
----------
image : ndarray
Input image.
sigma : float, optional
Standard deviation used for the Gaussian kernel, which is used as
weighting function for the auto-correlation matrix.
Returns
-------
w : ndarray
Error ellipse sizes.
q : ndarray
Roundness of error ellipse.
References
----------
..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\
foerstner87.fast.pdf
..[2] http://en.wikipedia.org/wiki/Corner_detection
Examples
--------
>>> from skimage.feature import corner_foerstner, corner_peaks
>>> square = np.zeros([10, 10])
>>> square[2:8, 2:8] = 1
>>> square
array([[ 0, 0, 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, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 1, 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, 0, 0]])
>>> w, q = corner_foerstner(square)
>>> accuracy_thresh = 0.5
>>> roundness_thresh = 0.3
>>> foerstner = (q > roundness_thresh) * (w > accuracy_thresh) * w
>>> corner_peaks(foerstner, min_distance=1)
array([[2, 2],
[2, 7],
[7, 2],
[7, 7]])
"""
Axx, Axy, Ayy = _compute_auto_correlation(image, sigma)
# determinant
detA = Axx * Ayy - Axy**2
# trace
traceA = Axx + Ayy
w = detA / traceA
q = 4 * detA / traceA**2
return w, q
def corner_subpix(image, corners, window_size=11, alpha=0.99):
"""Determine subpixel position of corners.
Parameters
----------
image : ndarray
Input image.
corners : (N, 2) ndarray
Corner coordinates `(row, col)`.
window_size : int, optional
Search window size for subpixel estimation.
alpha : float, optional
Significance level for point classification.
Returns
-------
positions : (N, 2) ndarray
Subpixel corner positions. NaN for "not classified" corners.
References
----------
..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\
foerstner87.fast.pdf
..[2] http://en.wikipedia.org/wiki/Corner_detection
"""
# window extent in one direction
wext = (window_size - 1) / 2
# normal equation arrays
N_dot = np.zeros((2, 2), dtype=np.double)
N_edge = np.zeros((2, 2), dtype=np.double)
b_dot = np.zeros((2, ), dtype=np.double)
b_edge = np.zeros((2, ), dtype=np.double)
# critical statistical test values
redundancy = window_size**2 - 2
t_crit_dot = stats.f.isf(1 - alpha, redundancy, redundancy)
t_crit_edge = stats.f.isf(alpha, redundancy, redundancy)
# coordinates of pixels within window
y, x = np.mgrid[- wext:wext + 1, - wext:wext + 1]
corners_subpix = np.zeros_like(corners, dtype=np.double)
for i, (y0, x0) in enumerate(corners):
# crop window around corner + border for sobel operator
miny = y0 - wext - 1
maxy = y0 + wext + 2
minx = x0 - wext - 1
maxx = x0 + wext + 2
window = image[miny:maxy, minx:maxx]
winx, winy = _compute_derivatives(window)
# compute gradient suares and remove border
winx_winx = (winx * winx)[1:-1, 1:-1]
winx_winy = (winx * winy)[1:-1, 1:-1]
winy_winy = (winy * winy)[1:-1, 1:-1]
# sum of squared differences (mean instead of gaussian filter)
Axx = np.sum(winx_winx)
Axy = np.sum(winx_winy)
Ayy = np.sum(winy_winy)
# sum of squared differences weighted with coordinates
# (mean instead of gaussian filter)
bxx_x = np.sum(winx_winx * x)
bxx_y = np.sum(winx_winx * y)
bxy_x = np.sum(winx_winy * x)
bxy_y = np.sum(winx_winy * y)
byy_x = np.sum(winy_winy * x)
byy_y = np.sum(winy_winy * y)
# normal equations for subpixel position
N_dot[0, 0] = Axx
N_dot[0, 1] = N_dot[1, 0] = - Axy
N_dot[1, 1] = Ayy
N_edge[0, 0] = Ayy
N_edge[0, 1] = N_edge[1, 0] = Axy
N_edge[1, 1] = Axx
b_dot[:] = bxx_y - bxy_x, byy_x - bxy_y
b_edge[:] = byy_y + bxy_x, bxx_x + bxy_y
# estimated positions
est_dot = np.linalg.solve(N_dot, b_dot)
est_edge = np.linalg.solve(N_edge, b_edge)
# residuals
ry_dot = y - est_dot[0]
rx_dot = x - est_dot[1]
ry_edge = y - est_edge[0]
rx_edge = x - est_edge[1]
# squared residuals
rxx_dot = rx_dot * rx_dot
rxy_dot = rx_dot * ry_dot
ryy_dot = ry_dot * ry_dot
rxx_edge = rx_edge * rx_edge
rxy_edge = rx_edge * ry_edge
ryy_edge = ry_edge * ry_edge
# determine corner class (dot or edge)
# variance for different models
var_dot = np.sum(winx_winx * ryy_dot - 2 * winx_winy * rxy_dot \
+ winy_winy * rxx_dot)
var_edge = np.sum(winy_winy * ryy_edge + 2 * winx_winy * rxy_edge \
+ winx_winx * rxx_edge)
# test value (F-distributed)
t = var_edge / var_dot
# 1 for edge, -1 for dot, 0 for "not classified"
corner_class = (t < t_crit_edge) - (t > t_crit_dot)
if corner_class == - 1:
corners_subpix[i, :] = y0 + est_dot[0], x0 + est_dot[1]
elif corner_class == 0:
corners_subpix[i, :] = np.nan, np.nan
elif corner_class == 1:
corners_subpix[i, :] = y0 + est_edge[0], x0 + est_edge[1]
return corners_subpix
def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1,
exclude_border=True, indices=True, num_peaks=np.inf,
footprint=None, labels=None):
"""Find corners in corner measure response image.
This differs from `skimage.feature.peak_local_max` in that it suppresses
multiple connected peaks with the same accumulator value.
Parameters
----------
See `skimage.feature.peak_local_max`.
Returns
-------
See `skimage.feature.peak_local_max`.
Examples
--------
>>> from skimage.feature import peak_local_max, corner_peaks
>>> response = np.zeros((5, 5))
>>> response[2:4, 2:4] = 1
>>> response
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 1., 0.],
[ 0., 0., 1., 1., 0.],
[ 0., 0., 0., 0., 0.]])
>>> peak_local_max(response, exclude_border=False)
array([[2, 2],
[2, 3],
[3, 2],
[3, 3]])
>>> corner_peaks(response, exclude_border=False)
array([[2, 2]])
>>> corner_peaks(response, exclude_border=False, min_distance=0)
array([[2, 2],
[2, 3],
[3, 2],
[3, 3]])
"""
peaks = peak_local_max(image, min_distance=min_distance,
threshold_abs=threshold_abs,
threshold_rel=threshold_rel,
exclude_border=exclude_border,
indices=False, num_peaks=np.inf,
footprint=footprint, labels=labels)
if min_distance > 0:
coords = np.transpose(peaks.nonzero())
for r, c in coords:
if peaks[r, c]:
peaks[r - min_distance:r + min_distance + 1,
c - min_distance:c + min_distance + 1] = False
peaks[r, c] = True
if indices is True:
return np.transpose(peaks.nonzero())
else:
return peaks
+91
View File
@@ -0,0 +1,91 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as cnp
from libc.float cimport DBL_MAX
from skimage.color import rgb2grey
from skimage.util import img_as_float
def corner_moravec(image, Py_ssize_t window_size=1):
"""Compute Moravec corner measure response image.
This is one of the simplest corner detectors and is comparatively fast but
has several limitations (e.g. not rotation invariant).
Parameters
----------
image : ndarray
Input image.
window_size : int, optional
Window size.
Returns
-------
response : ndarray
Moravec response image.
References
----------
..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/moravec.htm
..[2] http://en.wikipedia.org/wiki/Corner_detection
Examples
--------
>>> from skimage.feature import moravec, peak_local_max
>>> square = np.zeros([7, 7])
>>> square[3, 3] = 1
>>> square
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., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]])
>>> moravec(square)
array([[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 1., 1., 0., 0.],
[ 0., 0., 1., 2., 1., 0., 0.],
[ 0., 0., 1., 1., 1., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]])
"""
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 msum, min_msum
cdef Py_ssize_t r, c, br, bc, mr, mc, a, b
for r in range(2 * window_size, rows - 2 * window_size):
for c in range(2 * window_size, cols - 2 * window_size):
min_msum = DBL_MAX
for br in range(r - window_size, r + window_size + 1):
for bc in range(c - window_size, c + window_size + 1):
if br != r and bc != c:
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
min_msum = min(msum, min_msum)
out_data[r * cols + c] = min_msum
return out
+89 -33
View File
@@ -1,46 +1,67 @@
import warnings
import numpy as np
from scipy import ndimage
import scipy.ndimage as ndi
from ..filter import rank_order
def peak_local_max(image, min_distance=10, threshold='deprecated',
threshold_abs=0, threshold_rel=0.1, num_peaks=np.inf):
"""Return coordinates of peaks in an image.
def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1,
exclude_border=True, indices=True, num_peaks=np.inf,
footprint=None, labels=None):
"""
Find peaks in an image, and return them as coordinates or a boolean array.
Peaks are the local maxima in a region of `2 * min_distance + 1`
(i.e. peaks are separated by at least `min_distance`).
NOTE: If peaks are flat (i.e. multiple pixels have exact same intensity),
the coordinates of all pixels are returned.
NOTE: If peaks are flat (i.e. multiple adjacent pixels have identical
intensities), the coordinates of all such pixels are returned.
Parameters
----------
image : ndarray of floats
Input image.
min_distance : int
Minimum number of pixels separating peaks and image boundary.
threshold : float
Deprecated. See `threshold_rel`.
Minimum number of pixels separating peaks in a region of `2 *
min_distance + 1` (i.e. peaks are separated by at least
`min_distance`). If `exclude_border` is True, this value also excludes
a border `min_distance` from the image boundary.
To find the maximum number of peaks, use `min_distance=1`.
threshold_abs : float
Minimum intensity of peaks.
threshold_rel : float
Minimum intensity of peaks calculated as `max(image) * threshold_rel`.
exclude_border : bool
If True, `min_distance` excludes peaks from the border of the image as
well as from each other.
indices : bool
If True, the output will be a matrix representing peak coordinates.
If False, the output will be a boolean matrix shaped as `image.shape`
with peaks present at True elements.
num_peaks : int
Maximum number of peaks. When the number of peaks exceeds `num_peaks`,
return `num_peaks` coordinates based on peak intensity.
return `num_peaks` peaks based on highest peak intensity.
footprint : ndarray of bools, optional
If provided, `footprint == 1` represents the local region within which
to search for peaks at every point in `image`. Overrides
`min_distance`, except for border exclusion if `exclude_border=True`.
labels : ndarray of ints, optional
If provided, each unique region `labels == value` represents a unique
region to search for peaks. Zero is reserved for background.
Returns
-------
coordinates : (N, 2) array
(row, column) coordinates of peaks.
output : (N, 2) array or ndarray of bools
* If `indices = True` : (row, column) coordinates of peaks.
* If `indices = False` : Boolean array shaped like `image`, with peaks
represented by True values.
Notes
-----
The peak local maximum function returns the coordinates of local peaks (maxima)
in a image. A maximum filter is used for finding local maxima. This operation
dilates the original image. After comparison between dilated and original image,
peak_local_max function returns the coordinates of peaks where
dilated image = original.
The peak local maximum function returns the coordinates of local peaks
(maxima) in a image. A maximum filter is used for finding local maxima.
This operation dilates the original image. After comparison between
dilated and original image, peak_local_max function returns the
coordinates of peaks where dilated image = original.
Examples
--------
@@ -64,35 +85,70 @@ def peak_local_max(image, min_distance=10, threshold='deprecated',
array([[3, 2]])
"""
out = np.zeros_like(image, dtype=np.bool)
# In the case of labels, recursively build and return an output
# operating on each label separately
if labels is not None:
label_values = np.unique(labels)
# Reorder label values to have consecutive integers (no gaps)
if np.any(np.diff(label_values) != 1):
mask = labels >= 1
labels[mask] = 1 + rank_order(labels[mask])[0].astype(labels.dtype)
labels = labels.astype(np.int32)
# New values for new ordering
label_values = np.unique(labels)
for label in label_values[label_values != 0]:
maskim = (labels == label)
out += peak_local_max(image * maskim, min_distance=min_distance,
threshold_abs=threshold_abs,
threshold_rel=threshold_rel,
exclude_border=exclude_border,
indices=False, num_peaks=np.inf,
footprint=footprint, labels=None)
if indices is True:
return np.transpose(out.nonzero())
else:
return out.astype(np.bool)
if np.all(image == image.flat[0]):
return []
if indices is True:
return []
else:
return out
image = image.copy()
# Non maximum filter
size = 2 * min_distance + 1
image_max = ndimage.maximum_filter(image, size=size, mode='constant')
if footprint is not None:
image_max = ndi.maximum_filter(image, footprint=footprint,
mode='constant')
else:
size = 2 * min_distance + 1
image_max = ndi.maximum_filter(image, size=size, mode='constant')
mask = (image == image_max)
image *= mask
# Remove the image borders
image[:min_distance] = 0
image[-min_distance:] = 0
image[:, :min_distance] = 0
image[:, -min_distance:] = 0
if exclude_border:
# Remove the image borders
image[:min_distance] = 0
image[-min_distance:] = 0
image[:, :min_distance] = 0
image[:, -min_distance:] = 0
if not threshold == 'deprecated':
msg = "`threshold` parameter deprecated; use `threshold_rel instead."
warnings.warn(msg, DeprecationWarning)
threshold_rel = threshold
# find top peak candidates above a threshold
peak_threshold = max(np.max(image.ravel()) * threshold_rel, threshold_abs)
image_t = (image > peak_threshold) * 1
# get coordinates of peaks
coordinates = np.transpose(image_t.nonzero())
coordinates = np.transpose((image > peak_threshold).nonzero())
if coordinates.shape[0] > num_peaks:
intensities = image[coordinates[:, 0], coordinates[:, 1]]
idx_maxsort = np.argsort(intensities)[::-1]
coordinates = coordinates[idx_maxsort][:num_peaks]
return coordinates
if indices is True:
return coordinates
else:
out[coordinates[:, 0], coordinates[:, 1]] = True
return out
+3
View File
@@ -12,9 +12,12 @@ def configuration(parent_package='', top_path=None):
config = Configuration('feature', parent_package, top_path)
config.add_data_dir('tests')
cython(['corner_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('_texture', sources=['_texture.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('_template', sources=['_template.c'],
+116
View File
@@ -0,0 +1,116 @@
import numpy as np
from numpy.testing import assert_array_equal
from skimage import data
from skimage import img_as_float
from skimage.feature import (corner_moravec, corner_harris, corner_shi_tomasi,
corner_subpix, peak_local_max, corner_peaks)
def test_square_image():
im = np.zeros((50, 50)).astype(float)
im[:25, :25] = 1.
# Moravec
results = peak_local_max(corner_moravec(im))
# interest points along edge
assert len(results) == 57
# Harris
results = peak_local_max(corner_harris(im))
# interest at corner
assert len(results) == 1
# Shi-Tomasi
results = peak_local_max(corner_shi_tomasi(im))
# interest at corner
assert len(results) == 1
def test_noisy_square_image():
im = np.zeros((50, 50)).astype(float)
im[:25, :25] = 1.
np.random.seed(seed=1234)
im = im + np.random.uniform(size=im.shape) * .2
# Moravec
results = peak_local_max(corner_moravec(im))
# undefined number of interest points
assert results.any()
# Harris
results = peak_local_max(corner_harris(im, sigma=1.5))
assert len(results) == 1
# Shi-Tomasi
results = peak_local_max(corner_shi_tomasi(im, sigma=1.5))
assert len(results) == 1
def test_squared_dot():
im = np.zeros((50, 50))
im[4:8, 4:8] = 1
im = img_as_float(im)
# Moravec fails
# Harris
results = peak_local_max(corner_harris(im))
assert (results == np.array([[6, 6]])).all()
# Shi-Tomasi
results = peak_local_max(corner_shi_tomasi(im))
assert (results == np.array([[6, 6]])).all()
def test_rotated_lena():
"""
The harris filter should yield the same results with an image and it's
rotation.
"""
im = img_as_float(data.lena().mean(axis=2))
im_rotated = im.T
# Moravec
results = peak_local_max(corner_moravec(im))
results_rotated = peak_local_max(corner_moravec(im_rotated))
assert (np.sort(results[:, 0]) == np.sort(results_rotated[:, 1])).all()
assert (np.sort(results[:, 1]) == np.sort(results_rotated[:, 0])).all()
# Harris
results = peak_local_max(corner_harris(im))
results_rotated = peak_local_max(corner_harris(im_rotated))
assert (np.sort(results[:, 0]) == np.sort(results_rotated[:, 1])).all()
assert (np.sort(results[:, 1]) == np.sort(results_rotated[:, 0])).all()
# Shi-Tomasi
results = peak_local_max(corner_shi_tomasi(im))
results_rotated = peak_local_max(corner_shi_tomasi(im_rotated))
assert (np.sort(results[:, 0]) == np.sort(results_rotated[:, 1])).all()
assert (np.sort(results[:, 1]) == np.sort(results_rotated[:, 0])).all()
def test_subpix():
img = np.zeros((50, 50))
img[:25,:25] = 255
img[25:,25:] = 255
corner = peak_local_max(corner_harris(img), num_peaks=1)
subpix = corner_subpix(img, corner)
assert_array_equal(subpix[0], (24.5, 24.5))
def test_corner_peaks():
response = np.zeros((5, 5))
response[2:4, 2:4] = 1
corners = corner_peaks(response, exclude_border=False)
assert len(corners) == 1
corners = corner_peaks(response, exclude_border=False, min_distance=0)
assert len(corners) == 4
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+95
View File
@@ -0,0 +1,95 @@
import numpy as np
from numpy.testing import assert_raises, assert_almost_equal
from numpy import sqrt, ceil
from skimage import data
from skimage import img_as_float
from skimage.feature import daisy
def test_daisy_color_image_unsupported_error():
img = np.zeros((20, 20, 3))
assert_raises(ValueError, daisy, img)
def test_daisy_desc_dims():
img = img_as_float(data.lena()[:128, :128].mean(axis=2))
rings = 2
histograms = 4
orientations = 3
descs = daisy(img, rings=rings, histograms=histograms,
orientations=orientations)
assert(descs.shape[2] == (rings * histograms + 1) * orientations)
rings = 4
histograms = 5
orientations = 13
descs = daisy(img, rings=rings, histograms=histograms,
orientations=orientations)
assert(descs.shape[2] == (rings * histograms + 1) * orientations)
def test_descs_shape():
img = img_as_float(data.lena()[:256, :256].mean(axis=2))
radius = 20
step = 8
descs = daisy(img, radius=radius, step=step)
assert(descs.shape[0] == ceil((img.shape[0] - radius * 2) / float(step)))
assert(descs.shape[1] == ceil((img.shape[1] - radius * 2) / float(step)))
img = img[:-1, :-2]
radius = 5
step = 3
descs = daisy(img, radius=radius, step=step)
assert(descs.shape[0] == ceil((img.shape[0] - radius * 2) / float(step)))
assert(descs.shape[1] == ceil((img.shape[1] - radius * 2) / float(step)))
def test_daisy_incompatible_sigmas_and_radii():
img = img_as_float(data.lena()[:128, :128].mean(axis=2))
sigmas = [1, 2]
radii = [1, 2]
assert_raises(ValueError, daisy, img, sigmas=sigmas, ring_radii=radii)
def test_daisy_normalization():
img = img_as_float(data.lena()[:64, :64].mean(axis=2))
descs = daisy(img, normalization='l1')
for i in range(descs.shape[0]):
for j in range(descs.shape[1]):
assert_almost_equal(np.sum(descs[i, j, :]), 1)
descs_ = daisy(img)
assert_almost_equal(descs, descs_)
descs = daisy(img, normalization='l2')
for i in range(descs.shape[0]):
for j in range(descs.shape[1]):
assert_almost_equal(sqrt(np.sum(descs[i, j, :] ** 2)), 1)
orientations = 8
descs = daisy(img, orientations=orientations, normalization='daisy')
desc_dims = descs.shape[2]
for i in range(descs.shape[0]):
for j in range(descs.shape[1]):
for k in range(0, desc_dims, orientations):
assert_almost_equal(sqrt(np.sum(
descs[i, j, k:k + orientations] ** 2)), 1)
img = np.zeros((50, 50))
descs = daisy(img, normalization='off')
for i in range(descs.shape[0]):
for j in range(descs.shape[1]):
assert_almost_equal(np.sum(descs[i, j, :]), 0)
assert_raises(ValueError, daisy, img, normalization='does_not_exist')
def test_daisy_visualization():
img = img_as_float(data.lena()[:128, :128].mean(axis=2))
descs, descs_img = daisy(img, visualize=True)
assert(descs_img.shape == (128, 128, 3))
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
-49
View File
@@ -1,49 +0,0 @@
import numpy as np
from skimage import data
from skimage import img_as_float
from skimage.feature import harris
def test_square_image():
im = np.zeros((50, 50)).astype(float)
im[:25, :25] = 1.
results = harris(im)
assert results.any()
assert len(results) == 1
def test_noisy_square_image():
im = np.zeros((50, 50)).astype(float)
im[:25, :25] = 1.
im = im + np.random.uniform(size=im.shape) * .5
results = harris(im)
assert results.any()
assert len(results) == 1
def test_squared_dot():
im = np.zeros((50, 50))
im[4:8, 4:8] = 1
im = img_as_float(im)
results = harris(im, min_distance=3)
assert (results == np.array([[6, 6]])).all()
def test_rotated_lena():
"""
The harris filter should yield the same results with an image and it's
rotation.
"""
im = img_as_float(data.lena().mean(axis=2))
results = harris(im)
im_rotated = im.T
results_rotated = harris(im_rotated)
assert (np.sort(results[:, 0]) == np.sort(results_rotated[:, 1])).all()
assert (np.sort(results[:, 1]) == np.sort(results_rotated[:, 0])).all()
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+1 -1
View File
@@ -102,7 +102,7 @@ def test_hog_orientations_circle():
width = height = 100
image = np.zeros((height, width))
rr, cc = draw.circle(height/2, width/2, width/3)
rr, cc = draw.circle(int(height / 2), int(width / 2), int(width / 3))
image[rr, cc] = 100
image = ndimage.gaussian_filter(image, 2)
+48 -1
View File
@@ -1,9 +1,17 @@
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_close
import scipy.ndimage
from skimage.feature import peak
def test_trivial_case():
trivial = np.zeros((25, 25))
peak_indices = peak.peak_local_max(trivial, min_distance=1, indices=True)
assert not peak_indices # inherent boolean-ness of empty list
peaks = peak.peak_local_max(trivial, min_distance=1, indices=False)
assert (peaks.astype(np.bool) == trivial).all()
def test_noisy_peaks():
peak_locations = [(7, 7), (7, 13), (13, 7), (13, 13)]
@@ -70,6 +78,45 @@ def test_num_peaks():
assert (3, 5) in peaks_limited
def test_reorder_labels():
np.random.seed(21)
image = np.random.uniform(size=(40, 60))
i, j = np.mgrid[0:40, 0:60]
labels = 1 + (i >= 20) + (j >= 30) * 2
labels[labels == 4] = 5
i, j = np.mgrid[-3:4, -3:4]
footprint = (i * i + j * j <= 9)
expected = np.zeros(image.shape, float)
for imin, imax in ((0, 20), (20, 40)):
for jmin, jmax in ((0, 30), (30, 60)):
expected[imin:imax, jmin:jmax] = scipy.ndimage.maximum_filter(
image[imin:imax, jmin:jmax], footprint=footprint)
expected = (expected == image)
result = peak.peak_local_max(image, labels=labels, min_distance=1,
threshold_rel=0, footprint=footprint,
indices=False, exclude_border=False)
assert (result == expected).all()
def test_indices_with_labels():
np.random.seed(21)
image = np.random.uniform(size=(40, 60))
i, j = np.mgrid[0:40, 0:60]
labels = 1 + (i >= 20) + (j >= 30) * 2
i, j = np.mgrid[-3:4, -3:4]
footprint = (i * i + j * j <= 9)
expected = np.zeros(image.shape, float)
for imin, imax in ((0, 20), (20, 40)):
for jmin, jmax in ((0, 30), (30, 60)):
expected[imin:imax, jmin:jmax] = scipy.ndimage.maximum_filter(
image[imin:imax, jmin:jmax], footprint=footprint)
expected = (expected == image)
result = peak.peak_local_max(image, labels=labels, min_distance=1,
threshold_rel=0, footprint=footprint,
indices=True, exclude_border=False)
assert (result == np.transpose(expected.nonzero())).all()
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
+4 -2
View File
@@ -1,7 +1,9 @@
from .lpi_filter import *
from .ctmf import median_filter
from ._canny import canny
from .edges import sobel, hsobel, vsobel, hprewitt, vprewitt, prewitt
from ._tv_denoise import tv_denoise
from .edges import (sobel, hsobel, vsobel, scharr, hscharr, vscharr, prewitt,
hprewitt, vprewitt)
from ._denoise import denoise_tv_chambolle, tv_denoise
from ._denoise_cy import denoise_bilateral, denoise_tv_bregman
from ._rank_order import rank_order
from .thresholding import threshold_otsu, threshold_adaptive
+167 -176
View File
@@ -10,14 +10,20 @@ Copyright (c) 2009-2011 Broad Institute
All rights reserved.
Original author: Lee Kamentsky
'''
import numpy as np
cimport numpy as np
cimport numpy as cnp
cimport cython
from libc.stdlib cimport malloc, free
from libc.string cimport memset
np.import_array()
cdef extern from "../_shared/vectorized_ops.h":
void add16(cnp.uint16_t *dest, cnp.uint16_t *src)
void sub16(cnp.uint16_t *dest, cnp.uint16_t *src)
##############################################################################
#
@@ -39,7 +45,7 @@ np.import_array()
DTYPE_UINT32 = np.uint32
DTYPE_BOOL = np.bool
ctypedef np.uint16_t pixel_count_t
ctypedef cnp.uint16_t pixel_count_t
###########
#
@@ -54,15 +60,15 @@ ctypedef np.uint16_t pixel_count_t
###########
cdef struct HistogramPiece:
np.uint16_t coarse[16]
np.uint16_t fine[256]
cnp.uint16_t coarse[16]
cnp.uint16_t fine[256]
cdef struct Histogram:
HistogramPiece top_left # top-left corner
HistogramPiece top_right # top-right corner
HistogramPiece edge # leading/trailing edge
HistogramPiece bottom_left # bottom-left corner
HistogramPiece bottom_right # bottom-right corner
HistogramPiece top_left # top-left corner
HistogramPiece top_right # top-right corner
HistogramPiece edge # leading/trailing edge
HistogramPiece bottom_left # bottom-left corner
HistogramPiece bottom_right # bottom-right corner
# The pixel count has the number of pixels histogrammed in
# each of the five compartments for this position. This changes
@@ -80,27 +86,27 @@ cdef struct PixelCount:
# relative offsets from the octagon center
#
cdef struct SCoord:
np.int32_t stride # add the stride to the memory location
np.int32_t x
np.int32_t y
Py_ssize_t stride # add the stride to the memory location
Py_ssize_t x
Py_ssize_t y
cdef struct Histograms:
void *memory # pointer to the allocated memory
Histogram *histogram # pointer to the histogram memory
PixelCount *pixel_count # pointer to the pixel count memory
np.uint8_t *data # pointer to the image data
np.uint8_t *mask # pointer to the image mask
np.uint8_t *output # pointer to the output array
np.int32_t column_count # number of columns represented by this
cnp.uint8_t *data # pointer to the image data
cnp.uint8_t *mask # pointer to the image mask
cnp.uint8_t *output # pointer to the output array
Py_ssize_t column_count # number of columns represented by this
# structure
np.int32_t stripe_length # number of columns including "radius" before
Py_ssize_t stripe_length # number of columns including "radius" before
# and after
np.int32_t row_count # number of rows available in image
np.int32_t current_column # the column being processed
np.int32_t current_row # the row being processed
np.int32_t current_stride # offset in data and mask to current location
np.int32_t radius # the "radius" of the octagon
np.int32_t a_2 # 1/2 of the length of a side of the octagon
Py_ssize_t row_count # number of rows available in image
Py_ssize_t current_column # the column being processed
Py_ssize_t current_row # the row being processed
Py_ssize_t current_stride # offset in data and mask to current location
Py_ssize_t radius # the "radius" of the octagon
Py_ssize_t a_2 # 1/2 of the length of a side of the octagon
#
#
# The strides are the offsets in the array to the points that need to
@@ -123,83 +129,83 @@ cdef struct Histograms:
#
# x -->
#
SCoord last_top_left # (-) left side of octagon's top - 1 row
SCoord top_left # (+) -1 row from trailing edge top
SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row
SCoord top_right # (+) -1 col -1 row from leading edge top
SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row
SCoord leading_edge # (+) leading edge bottom stride
SCoord last_bottom_right # (-) leading edge bottom - 1 col
SCoord bottom_right # (+) right side of octagon's bottom - 1 col
SCoord last_bottom_left # (-) trailing edge bottom - 1 col
SCoord bottom_left # (+) left side of octagon's bottom - 1 col
SCoord last_top_left # (-) left side of octagon's top - 1 row
SCoord top_left # (+) -1 row from trailing edge top
SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row
SCoord top_right # (+) -1 col -1 row from leading edge top
SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row
SCoord leading_edge # (+) leading edge bottom stride
SCoord last_bottom_right # (-) leading edge bottom - 1 col
SCoord bottom_right # (+) right side of octagon's bottom - 1 col
SCoord last_bottom_left # (-) trailing edge bottom - 1 col
SCoord bottom_left # (+) left side of octagon's bottom - 1 col
np.int32_t row_stride # stride between one row and the next
np.int32_t col_stride # stride between one column and the next
Py_ssize_t row_stride # stride between one row and the next
Py_ssize_t col_stride # stride between one column and the next
# The accumulator holds the running histogram
#
HistogramPiece accumulator
#
# The running count of pixels in the accumulator
#
np.uint32_t accumulator_count
Py_ssize_t accumulator_count
#
# The percent of pixels within the octagon whose value is
# less than or equal to the median-filtered value (e.g. for
# median, this is 50, for lower quartile it's 25)
#
np.int32_t percent
Py_ssize_t percent
#
# last_update_column keeps track of the column # of the last update
# to the fine histogram accumulator. Short-term, the median
# stays in one coarse block so only one fine histogram might
# need to be updated
#
np.int32_t last_update_column[16]
Py_ssize_t last_update_column[16]
############################################################################
#
# allocate_histograms - allocates the Histograms structure for the run
#
############################################################################
cdef Histograms *allocate_histograms(np.int32_t rows,
np.int32_t columns,
np.int32_t row_stride,
np.int32_t col_stride,
np.int32_t radius,
np.int32_t percent,
np.uint8_t *data,
np.uint8_t *mask,
np.uint8_t *output):
cdef Histograms *allocate_histograms(Py_ssize_t rows,
Py_ssize_t columns,
Py_ssize_t row_stride,
Py_ssize_t col_stride,
Py_ssize_t radius,
Py_ssize_t percent,
cnp.uint8_t *data,
cnp.uint8_t *mask,
cnp.uint8_t *output):
cdef:
unsigned int adjusted_stripe_length = columns + 2*radius + 1
unsigned int memory_size
Py_ssize_t adjusted_stripe_length = columns + 2*radius + 1
Py_ssize_t memory_size
void *ptr
Histograms *ph
size_t roundoff
int a
Py_ssize_t roundoff
Py_ssize_t a
SCoord *psc
memory_size = (adjusted_stripe_length *
(sizeof(Histogram) + sizeof(PixelCount))+
sizeof(Histograms)+32)
(sizeof(Histogram) + sizeof(PixelCount)) +
sizeof(Histograms) + 32)
ptr = malloc(memory_size)
memset(ptr, 0, memory_size)
ph = <Histograms *>ptr
ph = <Histograms *> ptr
if not ptr:
return ph
ph.memory = ptr
ptr = <void *>(ph+1)
ph.pixel_count = <PixelCount *>ptr
ptr = <void *>(ph.pixel_count + adjusted_stripe_length)
ptr = <void *> (ph + 1)
ph.pixel_count = <PixelCount *> ptr
ptr = <void *> (ph.pixel_count + adjusted_stripe_length)
#
# Align histogram memory to a 32-byte boundary
#
roundoff = <size_t>ptr
roundoff = <Py_ssize_t>ptr
roundoff += 31
roundoff -= roundoff % 32
ptr = <void *>roundoff
ph.histogram = <Histogram *>ptr
ptr = <void *> roundoff
ph.histogram = <Histogram *> ptr
#
# Fill in the statistical things we keep around
#
@@ -228,7 +234,7 @@ cdef Histograms *allocate_histograms(np.int32_t rows,
# a_2 is the offset from the center to each of the octagon
# corners
#
a = <int>(<np.float64_t>radius * 2.0 / 2.414213)
a = <Py_ssize_t>(<cnp.float64_t>radius * 2.0 / 2.414213)
a_2 = a / 2
if a_2 == 0:
a_2 = 1
@@ -322,34 +328,18 @@ cdef void set_stride(Histograms *ph, SCoord *psc):
# a column that is "radius" to the left.
#
############################################################################
cdef inline np.int32_t tl_br_colidx(Histograms *ph, np.int32_t colidx):
cdef inline Py_ssize_t tl_br_colidx(Histograms *ph, Py_ssize_t colidx):
return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length
cdef inline np.int32_t tr_bl_colidx(Histograms *ph, np.int32_t colidx):
cdef inline Py_ssize_t tr_bl_colidx(Histograms *ph, Py_ssize_t colidx):
return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \
ph.stripe_length
cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx):
cdef inline Py_ssize_t leading_edge_colidx(Histograms *ph, Py_ssize_t colidx):
return (colidx + 5*ph.radius) % ph.stripe_length
cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx):
cdef inline Py_ssize_t trailing_edge_colidx(Histograms *ph, Py_ssize_t colidx):
return (colidx + 3*ph.radius - 1) % ph.stripe_length
#
# add16 - add 16 consecutive integers
#
# Add an array of 16 16-bit integers to an accumulator of 16 16-bit integers
#
# TO_DO - optimize using SIMD instructions
#
cdef inline void add16(np.uint16_t *dest, np.uint16_t *src):
cdef int i
for i in range(16):
dest[i] += src[i]
cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src):
cdef int i
for i in range(16):
dest[i] -= src[i]
############################################################################
#
@@ -360,9 +350,8 @@ cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src):
# colidx - the index of the column to add
#
############################################################################
cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx):
cdef:
int offset
cdef inline void accumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx):
cdef Py_ssize_t offset
offset = tr_bl_colidx(ph, colidx)
if ph.pixel_count[offset].top_right > 0:
@@ -383,9 +372,8 @@ cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx):
# for a given column
#
############################################################################
cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx):
cdef:
int offset
cdef inline void deaccumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx):
cdef Py_ssize_t offset
#
# The trailing diagonals don't appear until here
#
@@ -414,11 +402,11 @@ cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx
#
############################################################################
cdef inline void accumulate_fine_histogram(Histograms *ph,
np.int32_t colidx,
np.uint32_t fineidx):
Py_ssize_t colidx,
Py_ssize_t fineidx):
cdef:
int fineoffset = fineidx * 16
int offset
Py_ssize_t fineoffset = fineidx * 16
Py_ssize_t offset
offset = tr_bl_colidx(ph, colidx)
add16(ph.accumulator.fine + fineoffset,
@@ -438,11 +426,11 @@ cdef inline void accumulate_fine_histogram(Histograms *ph,
#
############################################################################
cdef inline void deaccumulate_fine_histogram(Histograms *ph,
np.int32_t colidx,
np.uint32_t fineidx):
Py_ssize_t colidx,
Py_ssize_t fineidx):
cdef:
int fineoffset = fineidx * 16
int offset
Py_ssize_t fineoffset = fineidx * 16
Py_ssize_t offset
#
# The trailing diagonals don't appear until here
@@ -470,10 +458,7 @@ cdef inline void deaccumulate_fine_histogram(Histograms *ph,
############################################################################
cdef inline void accumulate(Histograms *ph):
cdef:
int i
int j
np.int32_t accumulator
cdef cnp.int32_t accumulator
accumulate_coarse_histogram(ph, ph.current_column)
deaccumulate_coarse_histogram(ph, ph.current_column)
@@ -497,11 +482,11 @@ cdef inline void accumulate(Histograms *ph):
# to choose remains to be done.
############################################################################
cdef inline void update_fine(Histograms *ph, int fineidx):
cdef inline void update_fine(Histograms *ph, Py_ssize_t fineidx):
cdef:
int first_update_column = ph.last_update_column[fineidx]+1
int update_limit = ph.current_column+1
int i
Py_ssize_t first_update_column = ph.last_update_column[fineidx]+1
Py_ssize_t update_limit = ph.current_column+1
Py_ssize_t i
for i in range(first_update_column, update_limit):
accumulate_fine_histogram(ph, i, fineidx)
@@ -526,23 +511,23 @@ cdef inline void update_histogram(Histograms *ph,
SCoord *last_coord,
SCoord *coord):
cdef:
np.int32_t current_column = ph.current_column
np.int32_t current_row = ph.current_row
np.int32_t current_stride = ph.current_stride
np.int32_t column_count = ph.column_count
np.int32_t row_count = ph.row_count
np.uint8_t value
np.int32_t stride
np.int32_t x
np.int32_t y
Py_ssize_t current_column = ph.current_column
Py_ssize_t current_row = ph.current_row
Py_ssize_t current_stride = ph.current_stride
Py_ssize_t column_count = ph.column_count
Py_ssize_t row_count = ph.row_count
cnp.uint8_t value
Py_ssize_t stride
Py_ssize_t x
Py_ssize_t y
x = last_coord.x + current_column
y = last_coord.y + current_row
stride = current_stride+last_coord.stride
if (x >= 0 and x < column_count and
y >= 0 and y < row_count and
ph.mask[stride]):
if (x >= 0 and x < column_count and \
y >= 0 and y < row_count and \
ph.mask[stride]):
value = ph.data[stride]
pixel_count[0] -= 1
hist_piece.fine[value] -= 1
@@ -552,9 +537,9 @@ cdef inline void update_histogram(Histograms *ph,
y = coord.y + current_row
stride = current_stride + coord.stride
if (x >= 0 and x < column_count and
y >= 0 and y < row_count and
ph.mask[stride]):
if (x >= 0 and x < column_count and \
y >= 0 and y < row_count and \
ph.mask[stride]):
value = ph.data[stride]
pixel_count[0] += 1
hist_piece.fine[value] += 1
@@ -567,21 +552,21 @@ cdef inline void update_histogram(Histograms *ph,
############################################################################
cdef inline void update_current_location(Histograms *ph):
cdef:
np.int32_t current_column = ph.current_column
np.int32_t radius = ph.radius
np.int32_t top_left_off = tl_br_colidx(ph, current_column)
np.int32_t top_right_off = tr_bl_colidx(ph, current_column)
np.int32_t bottom_left_off = tr_bl_colidx(ph, current_column)
np.int32_t bottom_right_off = tl_br_colidx(ph, current_column)
np.int32_t leading_edge_off = leading_edge_colidx(ph, current_column)
np.int32_t *coarse_histogram
np.int32_t *fine_histogram
np.int32_t last_xoff
np.int32_t last_yoff
np.int32_t last_stride
np.int32_t xoff
np.int32_t yoff
np.int32_t stride
Py_ssize_t current_column = ph.current_column
Py_ssize_t radius = ph.radius
Py_ssize_t top_left_off = tl_br_colidx(ph, current_column)
Py_ssize_t top_right_off = tr_bl_colidx(ph, current_column)
Py_ssize_t bottom_left_off = tr_bl_colidx(ph, current_column)
Py_ssize_t bottom_right_off = tl_br_colidx(ph, current_column)
Py_ssize_t leading_edge_off = leading_edge_colidx(ph, current_column)
cnp.int32_t *coarse_histogram
cnp.int32_t *fine_histogram
Py_ssize_t last_xoff
Py_ssize_t last_yoff
Py_ssize_t last_stride
Py_ssize_t xoff
Py_ssize_t yoff
Py_ssize_t stride
update_histogram(ph, &ph.histogram[top_left_off].top_left,
&ph.pixel_count[top_left_off].top_left,
@@ -614,18 +599,20 @@ cdef inline void update_current_location(Histograms *ph):
#
############################################################################
cdef inline np.uint8_t find_median(Histograms *ph):
cdef inline cnp.uint8_t find_median(Histograms *ph):
cdef:
np.uint32_t pixels_below # of pixels below the median
int i
int j
int k
np.uint32_t accumulator
Py_ssize_t pixels_below # of pixels below the median
Py_ssize_t i
Py_ssize_t j
Py_ssize_t k
cnp.uint32_t accumulator
if ph.accumulator_count == 0:
return 0
pixels_below = ((ph.accumulator_count * ph.percent + 50)
/ 100) # +50 for roundoff
# +50 for roundoff
pixels_below = (ph.accumulator_count * ph.percent + 50) / 100
if pixels_below > 0:
pixels_below -= 1
@@ -637,10 +624,10 @@ cdef inline np.uint8_t find_median(Histograms *ph):
accumulator -= ph.accumulator.coarse[i]
update_fine(ph, i)
for j in range(i*16,(i+1)*16):
for j in range(i*16, (i + 1)*16):
accumulator += ph.accumulator.fine[j]
if accumulator > pixels_below:
return <np.uint8_t> j
return <cnp.uint8_t>j
return 0
@@ -659,30 +646,30 @@ cdef inline np.uint8_t find_median(Histograms *ph):
# output - array to be filled with filtered pixels
#
############################################################################
cdef int c_median_filter(np.int32_t rows,
np.int32_t columns,
np.int32_t row_stride,
np.int32_t col_stride,
np.int32_t radius,
np.int32_t percent,
np.uint8_t *data,
np.uint8_t *mask,
np.uint8_t *output):
cdef int c_median_filter(Py_ssize_t rows,
Py_ssize_t columns,
Py_ssize_t row_stride,
Py_ssize_t col_stride,
Py_ssize_t radius,
Py_ssize_t percent,
cnp.uint8_t *data,
cnp.uint8_t *mask,
cnp.uint8_t *output):
cdef:
Histograms *ph
Histogram *phistogram
int row
int col
int i
np.int32_t top_left_off
np.int32_t top_right_off
np.int32_t bottom_left_off
np.int32_t bottom_right_off
Py_ssize_t row
Py_ssize_t col
Py_ssize_t i
Py_ssize_t top_left_off
Py_ssize_t top_right_off
Py_ssize_t bottom_left_off
Py_ssize_t bottom_right_off
ph = allocate_histograms(rows, columns, row_stride, col_stride,
radius, percent, data, mask, output)
if not ph:
return 1
return 1
for row in range(-radius, rows):
#
@@ -721,7 +708,7 @@ cdef int c_median_filter(np.int32_t rows,
# Update locations and coarse accumulator for the octagon
# for points before 0
#
for col in range(-radius, 0 if row >=0 else columns+radius):
for col in range(-radius, 0 if row >= 0 else columns+radius):
ph.current_column = col
ph.current_stride = row * row_stride + col * col_stride
update_current_location(ph)
@@ -742,16 +729,18 @@ cdef int c_median_filter(np.int32_t rows,
ph.current_stride = row * row_stride + col * col_stride
update_current_location(ph)
free_histograms(ph)
return 0
def median_filter(
np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] data,
np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] mask,
np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] output,
int radius,
np.int32_t percent):
def median_filter(cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] data,
cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] mask,
cnp.ndarray[dtype=cnp.uint8_t, ndim=2,
negative_indices=False, mode='c'] output,
int radius,
cnp.int32_t percent):
"""Median filter with octagon shape and masking.
Parameters
@@ -773,10 +762,10 @@ def median_filter(
"""
if percent < 0:
raise ValueError('Median filter percent = %d is less than zero' % \
raise ValueError('Median filter percent = %d is less than zero' %
percent)
if percent > 100:
raise ValueError('Median filter percent = %d is greater than 100' % \
raise ValueError('Median filter percent = %d is greater than 100' %
percent)
if data.shape[0] != mask.shape[0] or data.shape[1] != mask.shape[1]:
raise ValueError('Data shape (%d, %d) is not mask shape (%d, %d)' %
@@ -786,10 +775,12 @@ def median_filter(
raise ValueError('Data shape (%d, %d) is not output shape (%d, %d)' %
(data.shape[0], data.shape[1],
output.shape[0], output.shape[1]))
if c_median_filter(data.shape[0], data.shape[1],
data.strides[0], data.strides[1],
if c_median_filter(<cnp.int32_t>data.shape[0],
<cnp.int32_t>data.shape[1],
<cnp.int32_t>data.strides[0],
<cnp.int32_t>data.strides[1],
radius, percent,
<np.uint8_t *>data.data,
<np.uint8_t *>mask.data,
<np.uint8_t *>output.data):
<cnp.uint8_t*>data.data,
<cnp.uint8_t*>mask.data,
<cnp.uint8_t*>output.data):
raise MemoryError('Failed to allocate scratchpad memory')
@@ -1,47 +1,46 @@
import numpy as np
from skimage import img_as_float
from skimage._shared.utils import deprecated
def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
"""
Perform total-variation denoising on 3-D arrays
def _denoise_tv_chambolle_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
"""Perform total-variation denoising on 3D images.
Parameters
----------
im: ndarray
3-D input data to be denoised
weight: float, optional
denoising weight. The greater ``weight``, the more denoising (at
the expense of fidelity to ``input``)
eps: float, optional
relative difference of the value of the cost function that determines
im : ndarray
3-D input data to be denoised.
weight : float, optional
Denoising weight. The greater `weight`, the more denoising (at
the expense of fidelity to `input`).
eps : float, optional
Relative difference of the value of the cost function that determines
the stop criterion. The algorithm stops when:
(E_(n-1) - E_n) < eps * E_0
n_iter_max: int, optional
maximal number of iterations used for the optimization.
n_iter_max : int, optional
Maximal number of iterations used for the optimization.
Returns
-------
out: ndarray
denoised array of floats
out : ndarray
Denoised array of floats.
Notes
-----
Rudin, Osher and Fatemi algorithm
Rudin, Osher and Fatemi algorithm.
Examples
---------
First build synthetic noisy data
--------
>>> x, y, z = np.ogrid[0:40, 0:40, 0:40]
>>> mask = (x -22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
>>> mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
>>> mask = mask.astype(np.float)
>>> mask += 0.2*np.random.randn(*mask.shape)
>>> res = tv_denoise_3d(mask, weight=100)
>>> mask += 0.2 * np.random.randn(*mask.shape)
>>> res = denoise_tv(mask, weight=100)
"""
px = np.zeros_like(im)
py = np.zeros_like(im)
pz = np.zeros_like(im)
@@ -85,57 +84,53 @@ def _tv_denoise_3d(im, weight=100, eps=2.e-4, n_iter_max=200):
return out
def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
"""
Perform total-variation denoising
def _denoise_tv_chambolle_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
"""Perform total-variation denoising on 2D images.
Parameters
----------
im: ndarray
input data to be denoised
weight: float, optional
denoising weight. The greater ``weight``, the more denoising (at
the expense of fidelity to ``input``)
eps: float, optional
relative difference of the value of the cost function that determines
im : ndarray
Input data to be denoised.
weight : float, optional
Denoising weight. The greater `weight`, the more denoising (at
the expense of fidelity to `input`)
eps : float, optional
Relative difference of the value of the cost function that determines
the stop criterion. The algorithm stops when:
(E_(n-1) - E_n) < eps * E_0
n_iter_max: int, optional
maximal number of iterations used for the optimization.
n_iter_max : int, optional
Maximal number of iterations used for the optimization.
Returns
-------
out: ndarray
denoised array of floats
out : ndarray
Denoised array of floats.
Notes
-----
The principle of total variation denoising is explained in
http://en.wikipedia.org/wiki/Total_variation_denoising
http://en.wikipedia.org/wiki/Total_variation_denoising.
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
that was proposed by Chambolle in [1]_.
References
----------
.. [1] A. Chambolle, An algorithm for total variation minimization and
applications, Journal of Mathematical Imaging and Vision,
Springer, 2004, 20, 89-97.
Examples
---------
>>> import scipy
>>> lena = scipy.lena()
>>> import scipy
>>> lena = scipy.lena().astype(np.float)
>>> lena += 0.5 * lena.std()*np.random.randn(*lena.shape)
>>> denoised_lena = tv_denoise(lena, weight=60.0)
--------
>>> 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)
"""
px = np.zeros_like(im)
py = np.zeros_like(im)
gx = np.zeros_like(im)
@@ -172,37 +167,41 @@ def _tv_denoise_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
return out
def tv_denoise(im, weight=50, eps=2.e-4, n_iter_max=200):
"""
Perform total-variation denoising on 2-d and 3-d images
def denoise_tv_chambolle(im, weight=50, eps=2.e-4, n_iter_max=200,
multichannel=False):
"""Perform total-variation denoising on 2D and 3D images.
Parameters
----------
im: ndarray (2d or 3d) of ints, uints or floats
input data to be denoised. `im` can be of any numeric type,
im : ndarray (2d or 3d) of ints, uints or floats
Input data to be denoised. `im` can be of any numeric type,
but it is cast into an ndarray of floats for the computation
of the denoised image.
weight: float, optional
denoising weight. The greater ``weight``, the more denoising (at
the expense of fidelity to ``input``)
eps: float, optional
relative difference of the value of the cost function that
weight : float, optional
Denoising weight. The greater `weight`, the more denoising (at
the expense of fidelity to `input`).
eps : float, optional
Relative difference of the value of the cost function that
determines the stop criterion. The algorithm stops when:
(E_(n-1) - E_n) < eps * E_0
n_iter_max: int, optional
maximal number of iterations used for the optimization.
n_iter_max : int, optional
Maximal number of iterations used for the optimization.
multichannel : bool, optional
Apply total-variation denoising separately for each channel. This
option should be true for color images, otherwise the denoising is
also applied in the 3rd dimension.
Returns
-------
out: ndarray
denoised array of floats
out : ndarray
Denoised image.
Notes
-----
Make sure to set the multichannel parameter appropriately for color images.
The principle of total variation denoising is explained in
http://en.wikipedia.org/wiki/Total_variation_denoising
@@ -217,36 +216,48 @@ def tv_denoise(im, weight=50, eps=2.e-4, n_iter_max=200):
References
----------
.. [1] A. Chambolle, An algorithm for total variation minimization and
applications, Journal of Mathematical Imaging and Vision,
Springer, 2004, 20, 89-97.
Examples
---------
>>> import scipy
>>> # 2D example using lena
>>> lena = scipy.lena()
>>> import scipy
>>> lena = scipy.lena().astype(np.float)
>>> lena += 0.5 * lena.std()*np.random.randn(*lena.shape)
>>> denoised_lena = tv_denoise(lena, weight=60)
>>> # 3D example on synthetic data
--------
2D example on Lena image:
>>> 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)
3D example on synthetic data:
>>> x, y, z = np.ogrid[0:40, 0:40, 0:40]
>>> mask = (x -22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
>>> mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
>>> mask = mask.astype(np.float)
>>> mask += 0.2*np.random.randn(*mask.shape)
>>> res = tv_denoise_3d(mask, weight=100)
>>> res = denoise_tv(mask, weight=100)
"""
im_type = im.dtype
if not im_type.kind == 'f':
im = img_as_float(im)
if im.ndim == 2:
out = _tv_denoise_2d(im, weight, eps, n_iter_max)
out = _denoise_tv_chambolle_2d(im, weight, eps, n_iter_max)
elif im.ndim == 3:
out = _tv_denoise_3d(im, weight, eps, n_iter_max)
if multichannel:
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)
else:
out = _denoise_tv_chambolle_3d(im, weight, eps, n_iter_max)
else:
raise ValueError('only 2-d and 3-d images may be denoised with this '
'function')
return out
tv_denoise = deprecated('skimage.filter.denoise_tv_chambolle')\
(denoise_tv_chambolle)
+320
View File
@@ -0,0 +1,320 @@
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
cimport numpy as cnp
import numpy as np
from libc.math cimport exp, fabs, sqrt
from libc.stdlib cimport malloc, free
from libc.float cimport DBL_MAX
from skimage._shared.interpolation cimport get_pixel3d
from skimage.util import img_as_float
from skimage._shared.utils import deprecated
cdef inline double _gaussian_weight(double sigma, double value):
return exp(-0.5 * (value / sigma)**2)
cdef double* _compute_color_lut(Py_ssize_t bins, double sigma, double max_value):
cdef:
double* color_lut = <double*>malloc(bins * sizeof(double))
Py_ssize_t b
for b in range(bins):
color_lut[b] = _gaussian_weight(sigma, b * max_value / bins)
return color_lut
cdef double* _compute_range_lut(Py_ssize_t win_size, double sigma):
cdef:
double* range_lut = <double*>malloc(win_size**2 * sizeof(double))
Py_ssize_t kr, kc
Py_ssize_t window_ext = (win_size - 1) / 2
double dist
for kr in range(win_size):
for kc in range(win_size):
dist = sqrt((kr - window_ext)**2 + (kc - window_ext)**2)
range_lut[kr * win_size + kc] = _gaussian_weight(sigma, dist)
return range_lut
def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None,
double sigma_spatial=1, Py_ssize_t bins=10000,
mode='constant', double cval=0):
"""Denoise image using bilateral filter.
This is an edge-preserving and noise reducing denoising filter. It averages
pixels based on their spatial closeness and radiometric similarity.
Spatial closeness is measured by the gaussian function of the euclidian
distance between two pixels and a certain standard deviation
(`sigma_spatial`).
Radiometric similarity is measured by the gaussian function of the euclidian
distance between two color values and a certain standard deviation
(`sigma_range`).
Parameters
----------
image : ndarray
Input image.
win_size : int
Window size for filtering.
sigma_range : float
Standard deviation for grayvalue/color distance (radiometric
similarity). A larger value results in averaging of pixels with larger
radiometric differences. Note, that the image will be converted using
the `img_as_float` function and thus the standard deviation is in
respect to the range `[0, 1]`.
sigma_spatial : float
Standard deviation for range distance. A larger value results in
averaging of pixels with larger spatial differences.
bins : int
Number of discrete values for gaussian weights of color filtering.
A larger value results in improved accuracy.
mode : string
How to handle values outside the image borders. See
`scipy.ndimage.map_coordinates` for detail.
cval : string
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Returns
-------
denoised : ndarray
Denoised image.
References
----------
.. [1] http://users.soe.ucsc.edu/~manduchi/Papers/ICCV98.pdf
"""
image = np.atleast_3d(img_as_float(image))
cdef:
Py_ssize_t rows = image.shape[0]
Py_ssize_t cols = image.shape[1]
Py_ssize_t dims = image.shape[2]
Py_ssize_t window_ext = (win_size - 1) / 2
double max_value = image.max()
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] cimage = \
np.ascontiguousarray(image)
cnp.ndarray[dtype=cnp.double_t, ndim=3, mode='c'] out = \
np.zeros((rows, cols, dims), dtype=np.double)
double* image_data = <double*>cimage.data
double* out_data = <double*>out.data
double* color_lut = _compute_color_lut(bins, sigma_range, max_value)
double* range_lut = _compute_range_lut(win_size, sigma_spatial)
Py_ssize_t r, c, d, wr, wc, kr, kc, rr, cc, pixel_addr
double value, weight, dist, total_weight, csigma_range, color_weight, \
range_weight
double dist_scale = bins / dims / max_value
double* values = <double*>malloc(dims * sizeof(double))
double* centres = <double*>malloc(dims * sizeof(double))
double* total_values = <double*>malloc(dims * sizeof(double))
if sigma_range is None:
csigma_range = image.std()
else:
csigma_range = sigma_range
if mode not in ('constant', 'wrap', 'reflect', 'nearest'):
raise ValueError("Invalid mode specified. Please use "
"`constant`, `nearest`, `wrap` or `reflect`.")
cdef char cmode = ord(mode[0].upper())
for r in range(rows):
for c in range(cols):
pixel_addr = r * cols * dims + c * dims
total_weight = 0
for d in range(dims):
total_values[d] = 0
centres[d] = image_data[pixel_addr + d]
for wr in range(-window_ext, window_ext + 1):
rr = wr + r
kr = wr + window_ext
for wc in range(-window_ext, window_ext + 1):
cc = wc + c
kc = wc + window_ext
# save pixel values for all dims and compute euclidian
# distance between centre stack and current position
dist = 0
for d in range(dims):
value = get_pixel3d(image_data, rows, cols, dims,
rr, cc, d, cmode, cval)
values[d] = value
dist += (centres[d] - value)**2
dist = sqrt(dist)
range_weight = range_lut[kr * win_size + kc]
color_weight = color_lut[<int>(dist * dist_scale)]
weight = range_weight * color_weight
for d in range(dims):
total_values[d] += values[d] * weight
total_weight += weight
for d in range(dims):
out_data[pixel_addr + d] = total_values[d] / total_weight
free(color_lut)
free(range_lut)
free(values)
free(centres)
free(total_values)
return np.squeeze(out)
def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3):
"""Perform total-variation denoising using split-Bregman optimization.
Total-variation denoising (also know as total-variation regularization)
tries to find an image with less total-variation under the constraint
of being similar to the input image, which is controlled by the
regularization parameter.
Parameters
----------
image : ndarray
Input data to be denoised (converted using img_as_float`).
weight : float, optional
Denoising weight. The smaller the `weight`, the more denoising (at
the expense of less similarity to the `input`). The regularization
parameter `lambda` is chosen as `2 * weight`.
eps : float, optional
Relative difference of the value of the cost function that determines
the stop criterion. The algorithm stops when::
SUM((u(n) - u(n-1))**2) < eps
max_iter: int, optional
Maximal number of iterations used for the optimization.
Returns
-------
u : ndarray
Denoised image.
References
----------
.. [1] http://en.wikipedia.org/wiki/Total_variation_denoising
.. [2] Tom Goldstein and Stanley Osher, "The Split Bregman Method For L1
Regularized Problems",
ftp://ftp.math.ucla.edu/pub/camreport/cam08-29.pdf
.. [3] Pascal Getreuer, "RudinOsherFatemi Total Variation Denoising
using Split Bregman" in Image Processing On Line on 20120519,
http://www.ipol.im/pub/art/2012/g-tvd/article_lr.pdf
"""
image = np.atleast_3d(img_as_float(image))
cdef:
Py_ssize_t rows = image.shape[0]
Py_ssize_t cols = image.shape[1]
Py_ssize_t dims = image.shape[2]
Py_ssize_t rows2 = rows + 2
Py_ssize_t cols2 = cols + 2
Py_ssize_t r, c, k
Py_ssize_t total = rows * cols * dims
shape_ext = (rows2, cols2, dims)
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)
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 ux, uy, uprev, unew, bxx, byy, dxx, dyy, s
int i = 0
double lam = 2 * weight
double rmse = DBL_MAX
double norm = (weight + 4 * lam)
u[1:-1, 1:-1] = image
# reflect image
u[0, 1:-1] = image[1, :]
u[1:-1, 0] = image[:, 1]
u[-1, 1:-1] = image[-2, :]
u[1:-1, -1] = image[:, -2]
while i < max_iter and rmse > eps:
rmse = 0
for k in range(dims):
for r in range(1, rows + 1):
for c in range(1, cols + 1):
uprev = u[r, c, k]
# forward derivatives
ux = u[r, c + 1, k] - uprev
uy = u[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]
+ dx[r, c - 1, k]
- dx[r, c, k]
+ dy[r - 1, c, k]
- dy[r, c, k]
- bx[r, c - 1, k]
+ bx[r, c, k]
- by[r - 1, c, k]
+ by[r, c, k]
) + weight * cimage[r - 1, c - 1, k]
) / norm
u[r, c, k] = unew
# update root mean square error
rmse += (unew - uprev)**2
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)
dx[r, c, k] = dxx
dy[r, c, k] = dyy
bx[r, c, k] += ux - dxx
by[r, c, k] += uy - dyy
rmse = sqrt(rmse / total)
i += 1
return np.squeeze(u[1:-1, 1:-1])
+136 -23
View File
@@ -1,6 +1,7 @@
"""edges.py - Sobel edge filter
"""edges.py - Edge filters
Originally part of CellProfiler, code licensed under both GPL and BSD licenses.
Sobel and Prewitt filters originally part of CellProfiler, code licensed under
both GPL and BSD licenses.
Website: http://www.cellprofiler.org
Copyright (c) 2003-2009 Massachusetts Institute of Technology
Copyright (c) 2009-2011 Broad Institute
@@ -34,13 +35,13 @@ def _mask_filter_result(result, mask):
def sobel(image, mask=None):
"""Calculate the absolute magnitude Sobel to find edges.
"""Find the edge magnitude using the Sobel transform.
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process.
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -48,7 +49,7 @@ def sobel(image, mask=None):
Returns
-------
output : ndarray
The Sobel edge map.
The Sobel edge map.
Notes
-----
@@ -67,9 +68,9 @@ def hsobel(image, mask=None):
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process.
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -77,7 +78,7 @@ def hsobel(image, mask=None):
Returns
-------
output : ndarray
The Sobel edge map.
The Sobel edge map.
Notes
-----
@@ -102,9 +103,9 @@ def vsobel(image, mask=None):
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -112,7 +113,7 @@ def vsobel(image, mask=None):
Returns
-------
output : ndarray
The Sobel edge map.
The Sobel edge map.
Notes
-----
@@ -132,14 +133,14 @@ def vsobel(image, mask=None):
return _mask_filter_result(result, mask)
def prewitt(image, mask=None):
"""Find the edge magnitude using the Prewitt transform.
def scharr(image, mask=None):
"""Find the edge magnitude using the Scharr transform.
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process.
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -147,7 +148,119 @@ def prewitt(image, mask=None):
Returns
-------
output : ndarray
The Prewitt edge map.
The Scharr edge map.
Notes
-----
Take the square root of the sum of the squares of the horizontal and
vertical Scharrs to get a magnitude that's somewhat insensitive to
direction.
References
----------
.. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization
of Kernel Based Image Derivatives.
"""
return np.sqrt(hscharr(image, mask)**2 + vscharr(image, mask)**2)
def hscharr(image, mask=None):
"""Find the horizontal edges of an image using the Scharr transform.
Parameters
----------
image : 2-D array
Image to process.
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
Returns
-------
output : ndarray
The Scharr edge map.
Notes
-----
We use the following kernel and return the absolute value of the
result at each point::
3 10 3
0 0 0
-3 -10 -3
References
----------
.. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization
of Kernel Based Image Derivatives.
"""
image = img_as_float(image)
result = np.abs(convolve(image,
np.array([[ 3, 10, 3],
[ 0, 0, 0],
[-3, -10, -3]]).astype(float) / 16.0))
return _mask_filter_result(result, mask)
def vscharr(image, mask=None):
"""Find the vertical edges of an image using the Scharr transform.
Parameters
----------
image : 2-D array
Image to process
mask : 2-D array, optional
An optional mask to limit the application to a certain area
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
Returns
-------
output : ndarray
The Scharr edge map.
Notes
-----
We use the following kernel and return the absolute value of the
result at each point::
3 0 -3
10 0 -10
3 0 -3
References
----------
.. [1] D. Kroon, 2009, Short Paper University Twente, Numerical Optimization
of Kernel Based Image Derivatives.
"""
image = img_as_float(image)
result = np.abs(convolve(image,
np.array([[ 3, 0, -3],
[10, 0, -10],
[ 3, 0, -3]]).astype(float) / 16.0))
return _mask_filter_result(result, mask)
def prewitt(image, mask=None):
"""Find the edge magnitude using the Prewitt transform.
Parameters
----------
image : 2-D array
Image to process.
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
Returns
-------
output : ndarray
The Prewitt edge map.
Notes
-----
@@ -162,9 +275,9 @@ def hprewitt(image, mask=None):
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process.
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -172,7 +285,7 @@ def hprewitt(image, mask=None):
Returns
-------
output : ndarray
The Prewitt edge map.
The Prewitt edge map.
Notes
-----
@@ -197,9 +310,9 @@ def vprewitt(image, mask=None):
Parameters
----------
image : array_like, dtype=float
image : 2-D array
Image to process.
mask : array_like, dtype=bool, optional
mask : 2-D array, optional
An optional mask to limit the application to a certain area.
Note that pixels surrounding masked regions are also masked to
prevent masked regions from affecting the result.
@@ -207,7 +320,7 @@ def vprewitt(image, mask=None):
Returns
-------
output : ndarray
The Prewitt edge map.
The Prewitt edge map.
Notes
-----
+1
View File
@@ -0,0 +1 @@
demo/
+32
View File
@@ -0,0 +1,32 @@
To do
-----
* add simple examples, adapt documentation on existing examples
* add/check existing doc
* adapting tests for each type of filter
General remarks
---------------
Basically these filters compute local histogram for each pixel. A histogram is
built using a moving window in order to limit redundant computation. The path
followed by the moving window is given hereunder
...-----------------------\
/--------------------------/
\-------------------------- ...
We compare cmorph.dilate to this histogram based method to show how
computational costs increase with respect to image size or structuring element
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.
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.
+3
View File
@@ -0,0 +1,3 @@
from .rank import *
from .percentile_rank import *
from .bilateral_rank import *
+20
View File
@@ -0,0 +1,20 @@
cimport numpy as cnp
ctypedef cnp.uint16_t dtype_t
cdef int int_max(int a, int b)
cdef int int_min(int a, int b)
# 16-bit core kernel receives extra information about data bitdepth
cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t,
Py_ssize_t, Py_ssize_t, Py_ssize_t, float,
float, Py_ssize_t, Py_ssize_t),
cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask,
cnp.ndarray[dtype_t, ndim=2] out,
char shift_x, char shift_y, Py_ssize_t bitdepth,
float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *
+255
View File
@@ -0,0 +1,255 @@
#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) == -1
t = np.hstack((np.zeros((selem.shape[0], 1)), selem))
t_w = np.diff(t, axis=1) == 1
t = np.vstack((selem, np.zeros((1, selem.shape[1]))))
t_s = np.diff(t, axis=0) == -1
t = np.vstack((np.zeros((1, selem.shape[1])), selem))
t_n = np.diff(t, axis=0) == 1
num_se_n = num_se_s = num_se_e = num_se_w = 0
for r in range(srows):
for c in range(scols):
if t_e[r, c]:
se_e_r[num_se_e] = r - centre_r
se_e_c[num_se_e] = c - centre_c
num_se_e += 1
if t_w[r, c]:
se_w_r[num_se_w] = r - centre_r
se_w_c[num_se_w] = c - centre_c
num_se_w += 1
if t_n[r, c]:
se_n_r[num_se_n] = r - centre_r
se_n_c[num_se_n] = c - centre_c
num_se_n += 1
if t_s[r, c]:
se_s_r[num_se_s] = r - centre_r
se_s_c[num_se_s] = c - centre_c
num_se_s += 1
# initial population and histogram
for i in range(maxbin):
histo[i] = 0
pop = 0
for r in range(srows):
for c in range(scols):
rr = r - centre_r
cc = c - centre_c
if selem[r, c]:
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
r = 0
c = 0
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# main loop
r = 0
for even_row in range(0, rows, 2):
# ---> west to east
for c in range(1, cols):
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s] - 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(
histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# ---> east to west
for c in range(cols - 2, -1, -1):
for s in range(num_se_w):
rr = r + se_w_r[s]
cc = c + se_w_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_e):
rr = r + se_e_r[s]
cc = c + se_e_c[s] + 1
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(
histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
r += 1 # pass to the next row
if r >= rows:
break
# ---> north to south
for s in range(num_se_s):
rr = r + se_s_r[s]
cc = c + se_s_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_increment(histo, &pop, image_data[rr * cols + cc])
for s in range(num_se_n):
rr = r + se_n_r[s] - 1
cc = c + se_n_c[s]
if is_in_mask(rows, cols, rr, cc, mask_data):
histogram_decrement(histo, &pop, image_data[rr * cols + cc])
# kernel -------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
bitdepth, maxbin, midbin, p0, p1, s0, s1)
# kernel -------------------------------------------
# release memory allocated by malloc
free(se_e_r)
free(se_e_c)
free(se_w_r)
free(se_w_c)
free(se_n_r)
free(se_n_c)
free(se_s_r)
free(se_s_c)
free(histo)
+25
View File
@@ -0,0 +1,25 @@
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 *
+257
View File
@@ -0,0 +1,257 @@
#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
cdef inline dtype_t uint8_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):
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 inline dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols,
Py_ssize_t r, Py_ssize_t c,
dtype_t * mask):
"""Check whether given coordinate is within image and mask is true."""
if r < 0 or r > rows - 1 or c < 0 or c > cols - 1:
return 0
else:
if mask[r * cols + c]:
return 1
else:
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 *:
"""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
# define pointers to the data
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
# 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(256 * 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) == -1
t = np.hstack((np.zeros((selem.shape[0], 1)), selem))
t_w = np.diff(t, axis=1) == 1
t = np.vstack((selem, np.zeros((1, selem.shape[1]))))
t_s = np.diff(t, axis=0) == -1
t = np.vstack((np.zeros((1, selem.shape[1])), selem))
t_n = np.diff(t, axis=0) == 1
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 (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])
r = 0
c = 0
# kernel -------------------------------------------------------------------
out_data[r * cols + c] = kernel(histo, pop, image_data[r * cols + c],
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], 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],
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
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 ---------------------------------------------------------------
# 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)
+422
View File
@@ -0,0 +1,422 @@
#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_meansubstraction(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
if pop:
for i in range(maxbin):
mean += histo[i] * i
return <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 meansubstraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8):
_core16(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y,
bitdepth, 0, 0, <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)
@@ -0,0 +1,82 @@
#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)
@@ -0,0 +1,330 @@
#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_substraction(Py_ssize_t * histo,
float pop,
dtype_t g,
Py_ssize_t bitdepth,
Py_ssize_t maxbin,
Py_ssize_t midbin,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
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_substraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[cnp.uint8_t, ndim=2] selem,
cnp.ndarray[cnp.uint8_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, int bitdepth=8,
float p0=0., float p1=0.):
"""return original - mean between [p0 and p1] percentiles *.5 +127
"""
_core16(
kernel_mean_substraction, 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., float p1=0.):
"""return p0 percentile
"""
_core16(kernel_percentile, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <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., float p1=0.):
"""return (maxbin-1) if g > percentile p0
"""
_core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y,
bitdepth, p0, p1, <Py_ssize_t>0, <Py_ssize_t>0)
+483
View File
@@ -0,0 +1,483 @@
#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_meansubstraction(Py_ssize_t * histo, float pop,
dtype_t g, float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef Py_ssize_t i
cdef float mean = 0.
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 meansubstraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def median(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_median, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def minimum(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def modal(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_modal, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def pop(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_pop, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def threshold(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0,
<Py_ssize_t>0, <Py_ssize_t>0)
def tophat(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def noise_filter(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def entropy(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
def otsu(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0):
_core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y,
0, 0, <Py_ssize_t>0, <Py_ssize_t>0)
+294
View File
@@ -0,0 +1,294 @@
#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_substraction(Py_ssize_t * histo,
float pop,
dtype_t g,
float p0, float p1,
Py_ssize_t s0, Py_ssize_t s1):
cdef int i, sum, mean, n
if pop:
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_substraction(cnp.ndarray[dtype_t, ndim=2] image,
cnp.ndarray[dtype_t, ndim=2] selem,
cnp.ndarray[dtype_t, ndim=2] mask=None,
cnp.ndarray[dtype_t, ndim=2] out=None,
char shift_x=0, char shift_y=0, float p0=0., float p1=0.):
"""return original - mean between [p0 and p1] percentiles *.5 +127
"""
_core8(kernel_mean_substraction, 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., float p1=0.):
"""return p0 percentile
"""
_core8(kernel_percentile, image, selem, mask, out, shift_x, shift_y,
p0, p1, <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., float p1=0.):
"""return 255 if g > percentile p0
"""
_core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, p0, p1,
<Py_ssize_t>0, <Py_ssize_t>0)
+192
View File
@@ -0,0 +1,192 @@
"""Approximate bilateral rank filter for local (custom kernel) mean.
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
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.
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 _crank16_bilateral
from skimage.filter.rank.generic import find_bitdepth
__all__ = ['bilateral_mean', 'bilateral_pop']
def _apply(func8, func16, image, selem, out, mask, shift_x, shift_y, s0, s1):
selem = img_as_ubyte(selem)
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, 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.")
return out
def bilateral_mean(image, selem, out=None, mask=None, shift_x=False,
shift_y=False, s0=10, s1=10):
"""Apply a flat kernel bilateral filter.
This is an edge-preserving and noise reducing denoising filter. It averages
pixels based on their spatial closeness and radiometric similarity.
Spatial closeness is measured by considering only the local pixel
neighborhood given by a structuring element (selem).
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
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).
s0, s1 : int
define the [s0, s1] interval to be considered for computing the value.
Returns
-------
out : uint16 array
The result of the local bilateral mean.
See also
--------
skimage.filter.denoise_bilateral() for a gaussian bilateral filter.
Notes
-----
* input image are 16-bit only
Examples
--------
>>> from skimage import data
>>> from skimage.morphology import disk
>>> from skimage.filter.rank import bilateral_mean
>>> # Load test image / cast to uint16
>>> ima = data.camera().astype(np.uint16)
>>> # bilateral filtering of cameraman image using a flat kernel
>>> bilat_ima = bilateral_mean(ima, disk(20), s0=10,s1=10)
"""
return _apply(None, _crank16_bilateral.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,
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
level inside the interval [g-s0, g+s1].
Parameters
----------
image : ndarray
Image array (uint16). As the algorithm uses max. 12bit histogram,
an exception will be raised if image has a value > 4095
selem : ndarray
The neighborhood expressed as a 2-D array of 1's and 0's.
out : ndarray
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).
s0, s1 : int
define the [s0, s1] interval 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
Examples
--------
>>> # Local mean
>>> 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)
>>> rank.bilateral_pop(ima16, square(3), s0=10,s1=10)
array([[3, 4, 3, 4, 3],
[4, 4, 6, 4, 4],
[3, 6, 9, 6, 3],
[4, 4, 6, 4, 4],
[3, 4, 3, 4, 3]], dtype=uint16)
"""
return _apply(None, _crank16_bilateral.pop, image, selem, out=out,
mask=mask, shift_x=shift_x, shift_y=shift_y, s0=s0, s1=s1)
+11
View File
@@ -0,0 +1,11 @@
import numpy as np
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))
else:
return 1
+396
View File
@@ -0,0 +1,396 @@
"""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]
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.
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.generic import find_bitdepth
from skimage.filter.rank import _crank16_percentiles, _crank8_percentiles
__all__ = ['percentile_autolevel', 'percentile_gradient',
'percentile_mean', 'percentile_mean_substraction',
'percentile_morph_contr_enh', 'percentile', 'percentile_pop',
'percentile_threshold']
def _apply(func8, func16, image, selem, out, mask, shift_x, shift_y, p0, p1):
selem = img_as_ubyte(selem)
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, 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.")
return out
def percentile_autolevel(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
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 autolevel : uint8 array or uint16
The result of the local autolevel.
"""
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,
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.):
"""Return greyscale local mean of an image.
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.
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 mean : uint8 array or uint16
The result of the local mean.
"""
return _apply(_crank8_percentiles.mean, _crank16_percentiles.mean,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_mean_substraction(image, selem, out=None, mask=None,
shift_x=False, shift_y=False, p0=.0, p1=1.):
"""Return greyscale local mean_substraction of an image.
mean_substraction 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 mean_substraction : uint8 array or uint16
The result of the local mean_substraction.
"""
return _apply(_crank8_percentiles.mean_substraction,
_crank16_percentiles.mean_substraction,
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.
morph_contr_enh 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 morph_contr_enh : uint8 array or uint16
The result of the local morph_contr_enh.
"""
return _apply(_crank8_percentiles.morph_contr_enh,
_crank16_percentiles.morph_contr_enh,
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, p1=1.):
"""Return greyscale local percentile of an image.
percentile 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 : uint8 array or uint16
The result of the local percentile.
"""
return _apply(_crank8_percentiles.percentile,
_crank16_percentiles.percentile,
image, selem, out=out, mask=mask, shift_x=shift_x,
shift_y=shift_y, p0=p0, p1=p1)
def percentile_pop(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
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 pop : uint8 array or uint16
The result of the local pop.
"""
return _apply(_crank8_percentiles.pop, _crank16_percentiles.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, p1=1.):
"""Return greyscale local threshold of an image.
threshold 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 threshold : uint8 array or uint16
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=p1)
+769
View File
@@ -0,0 +1,769 @@
"""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',
'meansubstraction', '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)
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 meansubstraction(image, selem, out=None, mask=None, shift_x=False,
shift_y=False):
"""Return image substracted 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 meansubstraction.
"""
return _apply(_crank8.meansubstraction, _crank16.meansubstraction, 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 [wiki_entropy]_ 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
----------
.. [wiki_entropy] 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)
+380
View File
@@ -0,0 +1,380 @@
import numpy as np
from numpy.testing import run_module_suite, assert_array_equal, assert_raises
from skimage import data
from skimage.morphology import cmorph, disk
from skimage.filter import rank
def test_random_sizes():
# make sure the size is not a problem
niter = 10
elem = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.uint8)
for m, n in np.random.random_integers(1, 100, size=(10, 2)):
mask = np.ones((m, n), dtype=np.uint8)
image8 = np.ones((m, n), dtype=np.uint8)
out8 = np.empty_like(image8)
rank.mean(image=image8, selem=elem, mask=mask, out=out8,
shift_x=0, shift_y=0)
assert_array_equal(image8.shape, out8.shape)
rank.mean(image=image8, selem=elem, mask=mask, out=out8,
shift_x=+1, shift_y=+1)
assert_array_equal(image8.shape, out8.shape)
image16 = np.ones((m, n), dtype=np.uint16)
out16 = np.empty_like(image8, dtype=np.uint16)
rank.mean(image=image16, selem=elem, mask=mask, out=out16,
shift_x=0, shift_y=0)
assert_array_equal(image16.shape, out16.shape)
rank.mean(image=image16, selem=elem, mask=mask, out=out16,
shift_x=+1, shift_y=+1)
assert_array_equal(image16.shape, out16.shape)
rank.percentile_mean(image=image16, mask=mask, out=out16,
selem=elem, shift_x=0, shift_y=0, p0=.1, p1=.9)
assert_array_equal(image16.shape, out16.shape)
rank.percentile_mean(image=image16, mask=mask, out=out16,
selem=elem, shift_x=+1, shift_y=+1, p0=.1, p1=.9)
assert_array_equal(image16.shape, out16.shape)
def test_compare_with_cmorph_dilate():
# compare the result of maximum filter with dilate
image = (np.random.random((100, 100)) * 256).astype(np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
for r in range(1, 20, 1):
elem = np.ones((r, r), dtype=np.uint8)
rank.maximum(image=image, selem=elem, out=out, mask=mask)
cm = cmorph.dilate(image=image, selem=elem)
assert_array_equal(out, cm)
def test_compare_with_cmorph_erode():
# compare the result of maximum filter with erode
image = (np.random.random((100, 100)) * 256).astype(np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
for r in range(1, 20, 1):
elem = np.ones((r, r), dtype=np.uint8)
rank.minimum(image=image, selem=elem, out=out, mask=mask)
cm = cmorph.erode(image=image, selem=elem)
assert_array_equal(out, cm)
def test_bitdepth():
# test the different bit depth for rank16
elem = np.ones((3, 3), dtype=np.uint8)
out = np.empty((100, 100), dtype=np.uint16)
mask = np.ones((100, 100), dtype=np.uint8)
for i in range(5):
image = np.ones((100, 100), dtype=np.uint16) * 255 * 2 ** i
r = rank.percentile_mean(image=image, selem=elem, mask=mask,
out=out, shift_x=0, shift_y=0, p0=.1, p1=.9)
def test_population():
# check the number of valid pixels in the neighborhood
image = np.zeros((5, 5), dtype=np.uint8)
elem = np.ones((3, 3), dtype=np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
rank.pop(image=image, selem=elem, out=out, mask=mask)
r = np.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]])
assert_array_equal(r, out)
def test_structuring_element8():
# check the output for a custom structuring element
r = np.array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 255, 0, 0, 0],
[0, 0, 255, 255, 255, 0],
[0, 0, 0, 255, 255, 0],
[0, 0, 0, 0, 0, 0]])
# 8-bit
image = np.zeros((6, 6), dtype=np.uint8)
image[2, 2] = 255
elem = np.asarray([[1, 1, 0], [1, 1, 1], [0, 0, 1]], dtype=np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=1, shift_y=1)
assert_array_equal(r, out)
# 16-bit
image = np.zeros((6, 6), dtype=np.uint16)
image[2, 2] = 255
out = np.empty_like(image)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=1, shift_y=1)
assert_array_equal(r, out)
def test_fail_on_bitdepth():
# should fail because data bitdepth is too high for the function
image = np.ones((100, 100), dtype=np.uint16) * 2 ** 12
elem = np.ones((3, 3), dtype=np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
assert_raises(ValueError, rank.percentile_mean, image=image,
selem=elem, out=out, mask=mask, shift_x=0, shift_y=0)
def test_pass_on_bitdepth():
# should pass because data bitdepth is not too high for the function
image = np.ones((100, 100), dtype=np.uint16) * 2 ** 11
elem = np.ones((3, 3), dtype=np.uint8)
out = np.empty_like(image)
mask = np.ones(image.shape, dtype=np.uint8)
def test_inplace_output():
# rank filters are not supposed to filter inplace
selem = disk(20)
image = (np.random.random((500, 500)) * 256).astype(np.uint8)
out = image
assert_raises(NotImplementedError, rank.mean, image, selem, out=out)
def test_compare_autolevels():
# compare autolevel and percentile autolevel with p0=0.0 and p1=1.0
# should returns the same arrays
image = data.camera()
selem = disk(20)
loc_autolevel = rank.autolevel(image, selem=selem)
loc_perc_autolevel = rank.percentile_autolevel(image, selem=selem,
p0=.0, p1=1.)
assert_array_equal(loc_autolevel, loc_perc_autolevel)
def test_compare_autolevels_16bit():
# compare autolevel(16-bit) and percentile autolevel(16-bit) with p0=0.0 and
# p1=1.0 should returns the same arrays
image = data.camera().astype(np.uint16) * 4
selem = disk(20)
loc_autolevel = rank.autolevel(image, selem=selem)
loc_perc_autolevel = rank.percentile_autolevel(image, selem=selem,
p0=.0, p1=1.)
assert_array_equal(loc_autolevel, loc_perc_autolevel)
def test_compare_8bit_vs_16bit():
# filters applied on 8-bit image ore 16-bit image (having only real 8-bit of
# dynamic) should be identical
image8 = data.camera()
image16 = image8.astype(np.uint16)
assert_array_equal(image8, image16)
methods = ['autolevel', 'bottomhat', 'equalize', 'gradient', 'maximum',
'mean', 'meansubstraction', 'median', 'minimum', 'modal',
'morph_contr_enh', 'pop', 'threshold', 'tophat']
for method in methods:
func = getattr(rank, method)
f8 = func(image8, disk(3))
f16 = func(image16, disk(3))
assert_array_equal(f8, f16)
def test_trivial_selem8():
# check that min, max and mean returns identity if structuring element
# contains only central pixel
image = np.zeros((5, 5), dtype=np.uint8)
out = np.zeros_like(image)
mask = np.ones_like(image, dtype=np.uint8)
image[2, 2] = 255
image[2, 3] = 128
image[1, 2] = 16
elem = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], dtype=np.uint8)
rank.mean(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.minimum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
def test_trivial_selem16():
# check that min, max and mean returns identity if structuring element
# contains only central pixel
image = np.zeros((5, 5), dtype=np.uint16)
out = np.zeros_like(image)
mask = np.ones_like(image, dtype=np.uint8)
image[2, 2] = 255
image[2, 3] = 128
image[1, 2] = 16
elem = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], dtype=np.uint8)
rank.mean(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.minimum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
def test_smallest_selem8():
# check that min, max and mean returns identity if structuring element
# contains only central pixel
image = np.zeros((5, 5), dtype=np.uint8)
out = np.zeros_like(image)
mask = np.ones_like(image, dtype=np.uint8)
image[2, 2] = 255
image[2, 3] = 128
image[1, 2] = 16
elem = np.array([[1]], dtype=np.uint8)
rank.mean(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.minimum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
def test_smallest_selem16():
# check that min, max and mean returns identity if structuring element
# contains only central pixel
image = np.zeros((5, 5), dtype=np.uint16)
out = np.zeros_like(image)
mask = np.ones_like(image, dtype=np.uint8)
image[2, 2] = 255
image[2, 3] = 128
image[1, 2] = 16
elem = np.array([[1]], dtype=np.uint8)
rank.mean(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.minimum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(image, out)
def test_empty_selem():
# check that min, max and mean returns zeros if structuring element is empty
image = np.zeros((5, 5), dtype=np.uint16)
out = np.zeros_like(image)
mask = np.ones_like(image, dtype=np.uint8)
res = np.zeros_like(image)
image[2, 2] = 255
image[2, 3] = 128
image[1, 2] = 16
elem = np.array([[0, 0, 0], [0, 0, 0]], dtype=np.uint8)
rank.mean(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(res, out)
rank.minimum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(res, out)
rank.maximum(image=image, selem=elem, out=out, mask=mask,
shift_x=0, shift_y=0)
assert_array_equal(res, out)
def test_otsu():
# test the local Otsu segmentation on a synthetic image
# (left to right ramp * sinus)
test = np.tile(
[128, 145, 103, 127, 165, 83, 127, 185, 63, 127, 205, 43,
127, 225, 23, 127],
(16, 1))
test = test.astype(np.uint8)
res = np.tile([1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1],
(16, 1))
selem = np.ones((6, 6), dtype=np.uint8)
th = 1 * (test >= rank.otsu(test, selem))
assert_array_equal(th, res)
def test_entropy():
# verify that entropy is coherent with bitdepth of the input data
selem = np.ones((16, 16), dtype=np.uint8)
# 1 bit per pixel
data = np.tile(np.asarray([0, 1]), (100, 100)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 10)
# 2 bit per pixel
data = np.tile(np.asarray([[0, 1], [2, 3]]), (10, 10)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 20)
# 3 bit per pixel
data = np.tile(
np.asarray([[0, 1, 2, 3], [4, 5, 6, 7]]), (10, 10)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 30)
# 4 bit per pixel
data = np.tile(
np.reshape(np.arange(16), (4, 4)), (10, 10)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 40)
# 6 bit per pixel
data = np.tile(
np.reshape(np.arange(64), (8, 8)), (10, 10)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 60)
# 8-bit per pixel
data = np.tile(
np.reshape(np.arange(256), (16, 16)), (10, 10)).astype(np.uint8)
assert(np.max(rank.entropy(data, selem)) == 80)
# 12 bit per pixel
selem = np.ones((64, 64), dtype=np.uint8)
data = np.tile(
np.reshape(np.arange(4096), (64, 64)), (2, 2)).astype(np.uint16)
assert(np.max(rank.entropy(data, selem)) == 12000)
if __name__ == "__main__":
run_module_suite()
+40 -1
View File
@@ -13,9 +13,48 @@ def configuration(parent_package='', top_path=None):
config.add_data_dir('tests')
cython(['_ctmf.pyx'], working_path=base_path)
cython(['_denoise_cy.pyx'], working_path=base_path)
cython(['rank/_core8.pyx'], working_path=base_path)
cython(['rank/_core16.pyx'], working_path=base_path)
cython(['rank/_crank8.pyx'], working_path=base_path)
cython(['rank/_crank8_percentiles.pyx'], working_path=base_path)
cython(['rank/_crank16.pyx'], working_path=base_path)
cython(['rank/_crank16_percentiles.pyx'], working_path=base_path)
cython(['rank/_crank16_bilateral.pyx'], working_path=base_path)
cython(['rank/rank.pyx'], working_path=base_path)
cython(['rank/percentile_rank.pyx'], working_path=base_path)
cython(['rank/bilateral_rank.pyx'], working_path=base_path)
config.add_extension('_ctmf', sources=['_ctmf.c'],
include_dirs=[get_numpy_include_dirs()])
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_denoise_cy', sources=['_denoise_cy.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
config.add_extension('rank._core8', sources=['rank/_core8.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('rank._core16', sources=['rank/_core16.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('rank._crank8', sources=['rank/_crank8.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank._crank8_percentiles', sources=['rank/_crank8_percentiles.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('rank._crank16', sources=['rank/_crank16.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank._crank16_percentiles', sources=['rank/_crank16_percentiles.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank._crank16_bilateral', sources=['rank/_crank16_bilateral.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank.rank', sources=['rank/rank.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank.percentile_rank', sources=['rank/percentile_rank.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension(
'rank.bilateral_rank', sources=['rank/bilateral_rank.c'],
include_dirs=[get_numpy_include_dirs()])
return config
+140
View File
@@ -0,0 +1,140 @@
import numpy as np
from numpy.testing import run_module_suite, assert_raises, assert_equal
from skimage import filter, data, color, img_as_float
np.random.seed(1234)
lena = img_as_float(data.lena()[:256, :256])
lena_gray = color.rgb2gray(lena)
def test_denoise_tv_chambolle_2d():
# lena image
img = lena_gray
# add noise to lena
img += 0.5 * img.std() * np.random.random(img.shape)
# clip noise so that it does not exceed allowed range for float images.
img = np.clip(img, 0, 1)
# denoise
denoised_lena = filter.denoise_tv_chambolle(img, weight=60.0)
# which dtype?
assert denoised_lena.dtype in [np.float, np.float32, np.float64]
from scipy import ndimage
grad = ndimage.morphological_gradient(img, size=((3, 3)))
grad_denoised = ndimage.morphological_gradient(
denoised_lena, size=((3, 3)))
# test if the total variation has decreased
assert grad_denoised.dtype == np.float
assert (np.sqrt((grad_denoised**2).sum())
< np.sqrt((grad**2).sum()) / 2)
def test_denoise_tv_chambolle_multichannel():
denoised0 = filter.denoise_tv_chambolle(lena[..., 0], weight=60.0)
denoised = filter.denoise_tv_chambolle(lena, weight=60.0, multichannel=True)
assert_equal(denoised[..., 0], denoised0)
def test_denoise_tv_chambolle_float_result_range():
# lena image
img = lena_gray
int_lena = np.multiply(img, 255).astype(np.uint8)
assert np.max(int_lena) > 1
denoised_int_lena = filter.denoise_tv_chambolle(int_lena, weight=60.0)
# test if the value range of output float data is within [0.0:1.0]
assert denoised_int_lena.dtype == np.float
assert np.max(denoised_int_lena) <= 1.0
assert np.min(denoised_int_lena) >= 0.0
def test_denoise_tv_chambolle_3d():
"""Apply the TV denoising algorithm on a 3D image representing a sphere."""
x, y, z = np.ogrid[0:40, 0:40, 0:40]
mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
mask = 100 * mask.astype(np.float)
mask += 60
mask += 20 * np.random.random(mask.shape)
mask[mask < 0] = 0
mask[mask > 255] = 255
res = filter.denoise_tv_chambolle(mask.astype(np.uint8), weight=100)
assert res.dtype == np.float
assert res.std() * 255 < mask.std()
# test wrong number of dimensions
assert_raises(ValueError, filter.denoise_tv_chambolle,
np.random.random((8, 8, 8, 8)))
def test_denoise_tv_bregman_2d():
img = lena_gray
# add some random noise
img += 0.5 * img.std() * np.random.random(img.shape)
img = np.clip(img, 0, 1)
out1 = filter.denoise_tv_bregman(img, weight=10)
out2 = filter.denoise_tv_bregman(img, weight=5)
# make sure noise is reduced
assert img.std() > out1.std()
assert out1.std() > out2.std()
def test_denoise_tv_bregman_float_result_range():
# lena image
img = lena_gray
int_lena = np.multiply(img, 255).astype(np.uint8)
assert np.max(int_lena) > 1
denoised_int_lena = filter.denoise_tv_bregman(int_lena, weight=60.0)
# test if the value range of output float data is within [0.0:1.0]
assert denoised_int_lena.dtype == np.float
assert np.max(denoised_int_lena) <= 1.0
assert np.min(denoised_int_lena) >= 0.0
def test_denoise_tv_bregman_3d():
img = lena
# add some random noise
img += 0.5 * img.std() * np.random.random(img.shape)
img = np.clip(img, 0, 1)
out1 = filter.denoise_tv_bregman(img, weight=10)
out2 = filter.denoise_tv_bregman(img, weight=5)
# make sure noise is reduced
assert img.std() > out1.std()
assert out1.std() > out2.std()
def test_denoise_bilateral_2d():
img = lena_gray
# add some random noise
img += 0.5 * img.std() * np.random.random(img.shape)
img = np.clip(img, 0, 1)
out1 = filter.denoise_bilateral(img, sigma_range=0.1, sigma_spatial=20)
out2 = filter.denoise_bilateral(img, sigma_range=0.2, sigma_spatial=30)
# make sure noise is reduced
assert img.std() > out1.std()
assert out1.std() > out2.std()
def test_denoise_bilateral_3d():
img = lena
# add some random noise
img += 0.5 * img.std() * np.random.random(img.shape)
img = np.clip(img, 0, 1)
out1 = filter.denoise_bilateral(img, sigma_range=0.1, sigma_spatial=20)
out2 = filter.denoise_bilateral(img, sigma_range=0.2, sigma_spatial=30)
# make sure noise is reduced
assert img.std() > out1.std()
assert out1.std() > out2.std()
if __name__ == "__main__":
run_module_suite()
+122 -2
View File
@@ -9,6 +9,7 @@ def test_sobel_zeros():
result = F.sobel(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_sobel_mask():
"""Sobel on a masked array should be zero"""
np.random.seed(0)
@@ -16,6 +17,7 @@ def test_sobel_mask():
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_sobel_horizontal():
"""Sobel on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -26,6 +28,7 @@ def test_sobel_horizontal():
assert (np.all(result[i == 0] == 1))
assert (np.all(result[np.abs(i) > 1] == 0))
def test_sobel_vertical():
"""Sobel on a vertical edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -41,6 +44,7 @@ def test_hsobel_zeros():
result = F.hsobel(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_hsobel_mask():
"""Horizontal Sobel on a masked array should be zero"""
np.random.seed(0)
@@ -48,6 +52,7 @@ def test_hsobel_mask():
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_hsobel_horizontal():
"""Horizontal Sobel on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -58,6 +63,7 @@ def test_hsobel_horizontal():
assert (np.all(result[i == 0] == 1))
assert (np.all(result[np.abs(i) > 1] == 0))
def test_hsobel_vertical():
"""Horizontal Sobel on a vertical edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
@@ -71,6 +77,7 @@ def test_vsobel_zeros():
result = F.vsobel(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_vsobel_mask():
"""Vertical Sobel on a masked array should be zero"""
np.random.seed(0)
@@ -78,6 +85,7 @@ def test_vsobel_mask():
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_vsobel_vertical():
"""Vertical Sobel on an edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -88,6 +96,7 @@ def test_vsobel_vertical():
assert (np.all(result[j == 0] == 1))
assert (np.all(result[np.abs(j) > 1] == 0))
def test_vsobel_horizontal():
"""vertical Sobel on a horizontal edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
@@ -97,11 +106,114 @@ def test_vsobel_horizontal():
assert (np.all(np.abs(result) < eps))
def test_scharr_zeros():
"""Scharr on an array of all zeros"""
result = F.scharr(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_scharr_mask():
"""Scharr on a masked array should be zero"""
np.random.seed(0)
result = F.scharr(np.random.uniform(size=(10, 10)),
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_scharr_horizontal():
"""Scharr on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
image = (i >= 0).astype(float)
result = F.scharr(image)
# Fudge the eroded points
i[np.abs(j) == 5] = 10000
assert (np.all(result[i == 0] == 1))
assert (np.all(result[np.abs(i) > 1] == 0))
def test_scharr_vertical():
"""Scharr on a vertical edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
image = (j >= 0).astype(float)
result = F.scharr(image)
j[np.abs(i) == 5] = 10000
assert (np.all(result[j == 0] == 1))
assert (np.all(result[np.abs(j) > 1] == 0))
def test_hscharr_zeros():
"""Horizontal Scharr on an array of all zeros"""
result = F.hscharr(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_hscharr_mask():
"""Horizontal Scharr on a masked array should be zero"""
np.random.seed(0)
result = F.hscharr(np.random.uniform(size=(10, 10)),
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_hscharr_horizontal():
"""Horizontal Scharr on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
image = (i >= 0).astype(float)
result = F.hscharr(image)
# Fudge the eroded points
i[np.abs(j) == 5] = 10000
assert (np.all(result[i == 0] == 1))
assert (np.all(result[np.abs(i) > 1] == 0))
def test_hscharr_vertical():
"""Horizontal Scharr on a vertical edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
image = (j >= 0).astype(float)
result = F.hscharr(image)
assert (np.all(result == 0))
def test_vscharr_zeros():
"""Vertical Scharr on an array of all zeros"""
result = F.vscharr(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_vscharr_mask():
"""Vertical Scharr on a masked array should be zero"""
np.random.seed(0)
result = F.vscharr(np.random.uniform(size=(10, 10)),
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_vscharr_vertical():
"""Vertical Scharr on an edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
image = (j >= 0).astype(float)
result = F.vscharr(image)
# Fudge the eroded points
j[np.abs(i) == 5] = 10000
assert (np.all(result[j == 0] == 1))
assert (np.all(result[np.abs(j) > 1] == 0))
def test_vscharr_horizontal():
"""vertical Scharr on a horizontal edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
image = (i >= 0).astype(float)
result = F.vscharr(image)
eps = .000001
assert (np.all(np.abs(result) < eps))
def test_prewitt_zeros():
"""Prewitt on an array of all zeros"""
result = F.prewitt(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_prewitt_mask():
"""Prewitt on a masked array should be zero"""
np.random.seed(0)
@@ -110,6 +222,7 @@ def test_prewitt_mask():
eps = .000001
assert (np.all(np.abs(result) < eps))
def test_prewitt_horizontal():
"""Prewitt on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -121,6 +234,7 @@ def test_prewitt_horizontal():
assert (np.all(result[i == 0] == 1))
assert (np.all(np.abs(result[np.abs(i) > 1]) < eps))
def test_prewitt_vertical():
"""Prewitt on a vertical edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -137,6 +251,7 @@ def test_hprewitt_zeros():
result = F.hprewitt(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_hprewitt_mask():
"""Horizontal prewitt on a masked array should be zero"""
np.random.seed(0)
@@ -145,6 +260,7 @@ def test_hprewitt_mask():
eps = .000001
assert (np.all(np.abs(result) < eps))
def test_hprewitt_horizontal():
"""Horizontal prewitt on an edge should be a horizontal line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -156,6 +272,7 @@ def test_hprewitt_horizontal():
assert (np.all(result[i == 0] == 1))
assert (np.all(np.abs(result[np.abs(i) > 1]) < eps))
def test_hprewitt_vertical():
"""Horizontal prewitt on a vertical edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
@@ -170,6 +287,7 @@ def test_vprewitt_zeros():
result = F.vprewitt(np.zeros((10, 10)), np.ones((10, 10), bool))
assert (np.all(result == 0))
def test_vprewitt_mask():
"""Vertical prewitt on a masked array should be zero"""
np.random.seed(0)
@@ -177,6 +295,7 @@ def test_vprewitt_mask():
np.zeros((10, 10), bool))
assert (np.all(result == 0))
def test_vprewitt_vertical():
"""Vertical prewitt on an edge should be a vertical line"""
i, j = np.mgrid[-5:6, -5:6]
@@ -188,6 +307,7 @@ def test_vprewitt_vertical():
eps = .000001
assert (np.all(np.abs(result[np.abs(j) > 1]) < eps))
def test_vprewitt_horizontal():
"""Vertical prewitt on a horizontal edge should be zero"""
i, j = np.mgrid[-5:6, -5:6]
@@ -209,7 +329,7 @@ def test_horizontal_mask_line():
expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image,
expected[4:7, 1:-1] = 0 # but line and neighbors masked
for grad_func in (F.hprewitt, F.hsobel):
for grad_func in (F.hprewitt, F.hsobel, F.hscharr):
result = grad_func(vgrad, mask)
yield assert_close, result, expected
@@ -226,7 +346,7 @@ def test_vertical_mask_line():
expected[1:-1, 1:-1] = 0.2 # constant gradient for most of image,
expected[1:-1, 4:7] = 0 # but line and neighbors masked
for grad_func in (F.vprewitt, F.vsobel):
for grad_func in (F.vprewitt, F.vsobel, F.vscharr):
result = grad_func(hgrad, mask)
yield assert_close, result, expected
-69
View File
@@ -1,69 +0,0 @@
import numpy as np
from numpy.testing import run_module_suite
from skimage import filter, data, color
class TestTvDenoise():
def test_tv_denoise_2d(self):
"""
Apply the TV denoising algorithm on the lena image provided
by scipy
"""
# lena image
lena = color.rgb2gray(data.lena())[:256, :256]
# add noise to lena
lena += 0.5 * lena.std() * np.random.randn(*lena.shape)
# clip noise so that it does not exceed allowed range for float images.
lena = np.clip(lena, 0, 1)
# denoise
denoised_lena = filter.tv_denoise(lena, weight=60.0)
# which dtype?
assert denoised_lena.dtype in [np.float, np.float32, np.float64]
from scipy import ndimage
grad = ndimage.morphological_gradient(lena, size=((3, 3)))
grad_denoised = ndimage.morphological_gradient(
denoised_lena, size=((3, 3)))
# test if the total variation has decreased
assert grad_denoised.dtype == np.float
assert (np.sqrt((grad_denoised**2).sum())
< np.sqrt((grad**2).sum()) / 2)
def test_tv_denoise_float_result_range(self):
# lena image
lena = color.rgb2gray(data.lena())[:256, :256]
int_lena = np.multiply(lena, 255).astype(np.uint8)
assert np.max(int_lena) > 1
denoised_int_lena = filter.tv_denoise(int_lena, weight=60.0)
# test if the value range of output float data is within [0.0:1.0]
assert denoised_int_lena.dtype == np.float
assert np.max(denoised_int_lena) <= 1.0
assert np.min(denoised_int_lena) >= 0.0
def test_tv_denoise_3d(self):
"""
Apply the TV denoising algorithm on a 3D image representing
a sphere.
"""
x, y, z = np.ogrid[0:40, 0:40, 0:40]
mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2
mask = 100 * mask.astype(np.float)
mask += 60
mask += 20 * np.random.randn(*mask.shape)
mask[mask < 0] = 0
mask[mask > 255] = 255
res = filter.tv_denoise(mask.astype(np.uint8), weight=100)
assert res.dtype == np.float
assert res.std() * 255 < mask.std()
# test wrong number of dimensions
a = np.random.random((8, 8, 8, 8))
try:
res = filter.tv_denoise(a)
except ValueError:
pass
if __name__ == "__main__":
run_module_suite()
+4 -4
View File
@@ -4,11 +4,11 @@ other cython modules can "cimport mcp" and subclass it.
"""
cimport heap
cimport numpy as np
cimport numpy as cnp
ctypedef heap.BOOL_T BOOL_T
ctypedef unsigned char DIM_T
ctypedef np.float64_t FLOAT_T
ctypedef unsigned char DIM_T
ctypedef cnp.float64_t FLOAT_T
cdef class MCP:
cdef heap.FastUpdateBinaryHeap costs_heap
@@ -23,7 +23,7 @@ cdef class MCP:
cdef object flat_offsets
cdef object offset_lengths
cdef BOOL_T dirty
cdef BOOL_T use_start_cost
cdef BOOL_T use_start_cost
# if use_start_cost is true, the cost of the starting element is added to
# the cost of the path. Set to true by default in the base class...

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