mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-02 09:21:20 +08:00
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:
+2
-1
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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>`_.
|
||||
|
||||
@@ -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
@@ -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!
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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, '_')
|
||||
|
||||
@@ -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,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>'
|
||||
|
||||
@@ -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
|
||||
============
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 |
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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, "Rudin–Osher–Fatemi Total Variation Denoising
|
||||
using Split Bregman" in Image Processing On Line on 2012–05–19,
|
||||
http://www.ipol.im/pub/art/2012/g-tvd/article_lr.pdf
|
||||
|
||||
"""
|
||||
|
||||
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
@@ -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
|
||||
-----
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
demo/
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
from .rank import *
|
||||
from .percentile_rank import *
|
||||
from .bilateral_rank import *
|
||||
@@ -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 *
|
||||
@@ -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)
|
||||
@@ -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 *
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user