Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -0,0 +1,136 @@
"""Image Processing for Python
``scikit-image`` (a.k.a. ``skimage``) is a collection of algorithms for image
processing and computer vision.
The main package of ``skimage`` only provides a few utilities for converting
between image data types; for most features, you need to import one of the
following subpackages:
Subpackages
-----------
color
Color space conversion.
data
Test images and example data.
draw
Drawing primitives (lines, text, etc.) that operate on NumPy arrays.
exposure
Image intensity adjustment, e.g., histogram equalization, etc.
feature
Feature detection and extraction, e.g., texture analysis corners, etc.
filters
Sharpening, edge finding, rank filters, thresholding, etc.
graph
Graph-theoretic operations, e.g., shortest paths.
io
Reading, saving, and displaying images and video.
measure
Measurement of image properties, e.g., similarity and contours.
morphology
Morphological operations, e.g., opening or skeletonization.
restoration
Restoration algorithms, e.g., deconvolution algorithms, denoising, etc.
segmentation
Partitioning an image into multiple regions.
transform
Geometric and other transforms, e.g., rotation or the Radon transform.
util
Generic utilities.
viewer
A simple graphical user interface for visualizing results and exploring
parameters.
Utility Functions
-----------------
img_as_float
Convert an image to floating point format, with values in [0, 1].
Is similar to `img_as_float64`, but will not convert lower-precision
floating point arrays to `float64`.
img_as_float32
Convert an image to single-precision (32-bit) floating point format,
with values in [0, 1].
img_as_float64
Convert an image to double-precision (64-bit) floating point format,
with values in [0, 1].
img_as_uint
Convert an image to unsigned integer format, with values in [0, 65535].
img_as_int
Convert an image to signed integer format, with values in [-32768, 32767].
img_as_ubyte
Convert an image to unsigned byte format, with values in [0, 255].
img_as_bool
Convert an image to boolean format, with values either True or False.
dtype_limits
Return intensity limits, i.e. (min, max) tuple, of the image's dtype.
"""
import sys
__version__ = '0.17.2'
from ._shared.version_requirements import ensure_python_version
ensure_python_version((3, 5))
# Logic for checking for improper install and importing while in the source
# tree when package has not been installed inplace.
# Code adapted from scikit-learn's __check_build module.
_INPLACE_MSG = """
It appears that you are importing a local scikit-image source tree. For
this, you need to have an inplace install. Maybe you are in the source
directory and you need to try from another location."""
_STANDARD_MSG = """
Your install of scikit-image appears to be broken.
Try re-installing the package following the instructions at:
https://scikit-image.org/docs/stable/install.html """
def _raise_build_error(e):
# Raise a comprehensible error
import os.path as osp
local_dir = osp.split(__file__)[0]
msg = _STANDARD_MSG
if local_dir == "skimage":
# Picking up the local install: this will work only if the
# install is an 'inplace build'
msg = _INPLACE_MSG
raise ImportError("""%s
It seems that scikit-image has not been built correctly.
%s""" % (e, msg))
try:
# This variable is injected in the __builtins__ by the build
# process. It used to enable importing subpackages of skimage when
# the binaries are not built
__SKIMAGE_SETUP__
except NameError:
__SKIMAGE_SETUP__ = False
if __SKIMAGE_SETUP__:
sys.stderr.write('Partial import of skimage during the build process.\n')
# We are not importing the rest of the scikit during the build
# process, as it may not be compiled yet
else:
try:
from ._shared import geometry
del geometry
except ImportError as e:
_raise_build_error(e)
# All skimage root imports go here
from .util.dtype import (img_as_float32,
img_as_float64,
img_as_float,
img_as_int,
img_as_uint,
img_as_ubyte,
img_as_bool,
dtype_limits)
from .data import data_dir
from .util.lookfor import lookfor
del sys

View file

@ -0,0 +1,86 @@
import sys
import os
from distutils.version import LooseVersion
from multiprocessing import cpu_count
CYTHON_VERSION = '0.23.4'
# WindowsError is not defined on unix systems
try:
WindowsError
except NameError:
class WindowsError(Exception):
pass
def _compiled_filename(f):
"""Check for the presence of a .pyx[.in] file as a .c or .cpp."""
basename = f.replace('.in', '').replace('.pyx', '')
for ext in ('.c', '.cpp'):
filename = basename + ext
if os.path.exists(filename):
return filename
else:
raise RuntimeError('Cython >= %s is required to build '
'scikit-image from git checkout' %
CYTHON_VERSION)
def cython(pyx_files, working_path=''):
"""Use Cython to convert the given files to C.
Parameters
----------
pyx_files : list of str
The input .pyx files.
"""
# Do not build cython files if target is clean
if len(sys.argv) >= 2 and sys.argv[1] == 'clean':
return
try:
from Cython import __version__
if LooseVersion(__version__) < CYTHON_VERSION:
raise RuntimeError('Cython >= %s needed to build scikit-image' % CYTHON_VERSION)
from Cython.Build import cythonize
except ImportError:
# If cython is not found, the build will make use of
# the distributed .c or .cpp files if present
c_files_used = [_compiled_filename(os.path.join(working_path, f))
for f in pyx_files]
print("Cython >= %s not found; falling back to pre-built %s" \
% (CYTHON_VERSION, " ".join(c_files_used)))
else:
pyx_files = [os.path.join(working_path, f) for f in pyx_files]
for i, pyxfile in enumerate(pyx_files):
if pyxfile.endswith('.pyx.in'):
process_tempita_pyx(pyxfile)
pyx_files[i] = pyxfile.replace('.pyx.in', '.pyx')
# Cython doesn't automatically choose a number of threads > 1
# https://github.com/cython/cython/blob/a0bbb940c847dfe92cac446c8784c34c28c92836/Cython/Build/Dependencies.py#L923-L925
cythonize(pyx_files, nthreads=cpu_count(),
compiler_directives={'language_level': 3})
def process_tempita_pyx(fromfile):
try:
try:
from Cython import Tempita as tempita
except ImportError:
import tempita
except ImportError:
raise Exception('Building requires Tempita: '
'pip install --user Tempita')
template = tempita.Template.from_filename(fromfile,
encoding=sys.getdefaultencoding())
pyxcontent = template.substitute()
if not fromfile.endswith('.pyx.in'):
raise ValueError("Unexpected extension of %s." % fromfile)
pyxfile = os.path.splitext(fromfile)[0] # split off the .in ending
with open(pyxfile, "w") as f:
f.write(pyxcontent)

View file

@ -0,0 +1,55 @@
__all__ = ['polygon_clip', 'polygon_area']
import numpy as np
def polygon_clip(rp, cp, r0, c0, r1, c1):
"""Clip a polygon to the given bounding box.
Parameters
----------
rp, cp : (N,) ndarray of double
Row and column coordinates of the polygon.
(r0, c0), (r1, c1) : double
Top-left and bottom-right coordinates of the bounding box.
Returns
-------
r_clipped, c_clipped : (M,) ndarray of double
Coordinates of clipped polygon.
Notes
-----
This makes use of Sutherland-Hodgman clipping as implemented in
AGG 2.4 and exposed in Matplotlib.
"""
from matplotlib import path, transforms
poly = path.Path(np.vstack((rp, cp)).T, closed=True)
clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
# This should be fixed in matplotlib >1.5
if np.all(poly_clipped[-1] == poly_clipped[-2]):
poly_clipped = poly_clipped[:-1]
return poly_clipped[:, 0], poly_clipped[:, 1]
def polygon_area(pr, pc):
"""Compute the area of a polygon.
Parameters
----------
pr, pc : (N,) array of float
Polygon row and column coordinates.
Returns
-------
a : float
Area of the polygon.
"""
pr = np.asarray(pr)
pc = np.asarray(pc)
return 0.5 * np.abs(np.sum((pc[:-1] * pr[1:]) - (pc[1:] * pr[:-1])))

View file

@ -0,0 +1,27 @@
from tempfile import NamedTemporaryFile
from contextlib import contextmanager
import os
@contextmanager
def temporary_file(suffix=''):
"""Yield a writeable temporary filename that is deleted on context exit.
Parameters
----------
suffix : string, optional
The suffix for the file.
Examples
--------
>>> import numpy as np
>>> from skimage import io
>>> with temporary_file('.tif') as tempfile:
... im = np.arange(25, dtype=np.uint8).reshape((5, 5))
... io.imsave(tempfile, im)
... assert np.all(io.imread(tempfile) == im)
"""
tempfile_stream = NamedTemporaryFile(suffix=suffix, delete=False)
tempfile = tempfile_stream.name
tempfile_stream.close()
yield tempfile
os.remove(tempfile)

View file

@ -0,0 +1,145 @@
from contextlib import contextmanager
import sys
import warnings
import re
import functools
import os
__all__ = ['all_warnings', 'expected_warnings', 'warn']
# A version of `warnings.warn` with a default stacklevel of 2.
# functool is used so as not to increase the call stack accidentally
warn = functools.partial(warnings.warn, stacklevel=2)
@contextmanager
def all_warnings():
"""
Context for use in testing to ensure that all warnings are raised.
Examples
--------
>>> import warnings
>>> def foo():
... warnings.warn(RuntimeWarning("bar"), stacklevel=2)
We raise the warning once, while the warning filter is set to "once".
Hereafter, the warning is invisible, even with custom filters:
>>> with warnings.catch_warnings():
... warnings.simplefilter('once')
... foo() # doctest: +SKIP
We can now run ``foo()`` without a warning being raised:
>>> from numpy.testing import assert_warns
>>> foo() # doctest: +SKIP
To catch the warning, we call in the help of ``all_warnings``:
>>> with all_warnings():
... assert_warns(RuntimeWarning, foo)
"""
# _warnings.py is on the critical import path.
# Since this is a testing only function, we lazy import inspect.
import inspect
# Whenever a warning is triggered, Python adds a __warningregistry__
# member to the *calling* module. The exercize here is to find
# and eradicate all those breadcrumbs that were left lying around.
#
# We proceed by first searching all parent calling frames and explicitly
# clearing their warning registries (necessary for the doctests above to
# pass). Then, we search for all submodules of skimage and clear theirs
# as well (necessary for the skimage test suite to pass).
frame = inspect.currentframe()
if frame:
for f in inspect.getouterframes(frame):
f[0].f_locals['__warningregistry__'] = {}
del frame
for mod_name, mod in list(sys.modules.items()):
try:
mod.__warningregistry__.clear()
except AttributeError:
pass
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
yield w
@contextmanager
def expected_warnings(matching):
r"""Context for use in testing to catch known warnings matching regexes
Parameters
----------
matching : None or a list of strings or compiled regexes
Regexes for the desired warning to catch
If matching is None, this behaves as a no-op.
Examples
--------
>>> import numpy as np
>>> image = np.random.randint(0, 2**16, size=(100, 100), dtype=np.uint16)
>>> # rank filters are slow when bit-depth exceeds 10 bits
>>> from skimage import filters
>>> with expected_warnings(['Bad rank filter performance']):
... median_filtered = filters.rank.median(image)
Notes
-----
Uses `all_warnings` to ensure all warnings are raised.
Upon exiting, it checks the recorded warnings for the desired matching
pattern(s).
Raises a ValueError if any match was not found or an unexpected
warning was raised.
Allows for three types of behaviors: `and`, `or`, and `optional` matches.
This is done to accommodate different build environments or loop conditions
that may produce different warnings. The behaviors can be combined.
If you pass multiple patterns, you get an orderless `and`, where all of the
warnings must be raised.
If you use the `|` operator in a pattern, you can catch one of several
warnings.
Finally, you can use `|\A\Z` in a pattern to signify it as optional.
"""
if isinstance(matching, str):
raise ValueError('``matching`` should be a list of strings and not '
'a string itself.')
# Special case for disabling the context manager
if matching is None:
yield None
return
strict_warnings = os.environ.get('SKIMAGE_TEST_STRICT_WARNINGS', '1')
if strict_warnings.lower() == 'true':
strict_warnings = True
elif strict_warnings.lower() == 'false':
strict_warnings = False
else:
strict_warnings = bool(int(strict_warnings))
with all_warnings() as w:
# enter context
yield w
# exited user context, check the recorded warnings
# Allow users to provide None
while None in matching:
matching.remove(None)
remaining = [m for m in matching if r'\A\Z' not in m.split('|')]
for warn in w:
found = False
for match in matching:
if re.search(match, str(warn.message)) is not None:
found = True
if match in remaining:
remaining.remove(match)
if strict_warnings and not found:
raise ValueError('Unexpected warning: %s' % str(warn.message))
if strict_warnings and (len(remaining) > 0):
msg = 'No warning raised matching:\n%s' % '\n'.join(remaining)
raise ValueError(msg)

View file

@ -0,0 +1,17 @@
"""Prefer FFTs via the new scipy.fft module when available (SciPy 1.4+)
Otherwise fall back to numpy.fft.
Like numpy 1.15+ scipy 1.3+ is also using pocketfft, but a newer
C++/pybind11 version called pypocketfft
"""
try:
import scipy.fft
from scipy.fft import next_fast_len
fftmodule = scipy.fft
except ImportError:
import numpy.fft
fftmodule = numpy.fft
from scipy.fftpack import next_fast_len
__all__ = ['fftmodule', 'next_fast_len']

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python
import os
from skimage._build import cython
base_path = os.path.abspath(os.path.dirname(__file__))
def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs
config = Configuration('_shared', parent_package, top_path)
cython(['geometry.pyx',
'transform.pyx',
'interpolation.pyx',
'fast_exp.pyx'], working_path=base_path)
config.add_extension('geometry', sources=['geometry.c'])
config.add_extension('transform', sources=['transform.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('interpolation', sources=['interpolation.c'])
config.add_extension('fast_exp', sources=['fast_exp.c'])
return config
if __name__ == '__main__':
from numpy.distutils.core import setup
setup(maintainer='scikit-image Developers',
author='scikit-image Developers',
maintainer_email='scikit-image@python.org',
description='Transforms',
url='https://github.com/scikit-image/scikit-image',
license='SciPy License (BSD Style)',
**(configuration(top_path='').todict())
)

View file

@ -0,0 +1,270 @@
"""
Testing utilities.
"""
import os
import re
import struct
import threading
import functools
from tempfile import NamedTemporaryFile
import numpy as np
from numpy import testing
from numpy.testing import (assert_array_equal, assert_array_almost_equal,
assert_array_less, assert_array_almost_equal_nulp,
assert_equal, TestCase, assert_allclose,
assert_almost_equal, assert_, assert_warns,
assert_no_warnings)
import warnings
from .. import data, io
from ..util import img_as_uint, img_as_float, img_as_int, img_as_ubyte
import pytest
from ._warnings import expected_warnings
SKIP_RE = re.compile(r"(\s*>>>.*?)(\s*)#\s*skip\s+if\s+(.*)$")
skipif = pytest.mark.skipif
xfail = pytest.mark.xfail
parametrize = pytest.mark.parametrize
raises = pytest.raises
fixture = pytest.fixture
# true if python is running in 32bit mode
# Calculate the size of a void * pointer in bits
# https://docs.python.org/3/library/struct.html
arch32 = struct.calcsize("P") * 8 == 32
def assert_less(a, b, msg=None):
message = "%r is not lower than %r" % (a, b)
if msg is not None:
message += ": " + msg
assert a < b, message
def assert_greater(a, b, msg=None):
message = "%r is not greater than %r" % (a, b)
if msg is not None:
message += ": " + msg
assert a > b, message
def doctest_skip_parser(func):
""" Decorator replaces custom skip test markup in doctests
Say a function has a docstring::
>>> something, HAVE_AMODULE, HAVE_BMODULE = 0, False, False
>>> something # skip if not HAVE_AMODULE
0
>>> something # skip if HAVE_BMODULE
0
This decorator will evaluate the expression after ``skip if``. If this
evaluates to True, then the comment is replaced by ``# doctest: +SKIP``. If
False, then the comment is just removed. The expression is evaluated in the
``globals`` scope of `func`.
For example, if the module global ``HAVE_AMODULE`` is False, and module
global ``HAVE_BMODULE`` is False, the returned function will have docstring::
>>> something # doctest: +SKIP
>>> something + else # doctest: +SKIP
>>> something # doctest: +SKIP
"""
lines = func.__doc__.split('\n')
new_lines = []
for line in lines:
match = SKIP_RE.match(line)
if match is None:
new_lines.append(line)
continue
code, space, expr = match.groups()
try:
# Works as a function decorator
if eval(expr, func.__globals__):
code = code + space + "# doctest: +SKIP"
except AttributeError:
# Works as a class decorator
if eval(expr, func.__init__.__globals__):
code = code + space + "# doctest: +SKIP"
new_lines.append(code)
func.__doc__ = "\n".join(new_lines)
return func
def roundtrip(image, plugin, suffix):
"""Save and read an image using a specified plugin"""
if '.' not in suffix:
suffix = '.' + suffix
temp_file = NamedTemporaryFile(suffix=suffix, delete=False)
fname = temp_file.name
temp_file.close()
io.imsave(fname, image, plugin=plugin)
new = io.imread(fname, plugin=plugin)
try:
os.remove(fname)
except Exception:
pass
return new
def color_check(plugin, fmt='png'):
"""Check roundtrip behavior for color images.
All major input types should be handled as ubytes and read
back correctly.
"""
img = img_as_ubyte(data.chelsea())
r1 = roundtrip(img, plugin, fmt)
testing.assert_allclose(img, r1)
img2 = img > 128
r2 = roundtrip(img2, plugin, fmt)
testing.assert_allclose(img2, r2.astype(bool))
img3 = img_as_float(img)
r3 = roundtrip(img3, plugin, fmt)
testing.assert_allclose(r3, img)
img4 = img_as_int(img)
if fmt.lower() in (('tif', 'tiff')):
img4 -= 100
r4 = roundtrip(img4, plugin, fmt)
testing.assert_allclose(r4, img4)
else:
r4 = roundtrip(img4, plugin, fmt)
testing.assert_allclose(r4, img_as_ubyte(img4))
img5 = img_as_uint(img)
r5 = roundtrip(img5, plugin, fmt)
testing.assert_allclose(r5, img)
def mono_check(plugin, fmt='png'):
"""Check the roundtrip behavior for images that support most types.
All major input types should be handled.
"""
img = img_as_ubyte(data.moon())
r1 = roundtrip(img, plugin, fmt)
testing.assert_allclose(img, r1)
img2 = img > 128
r2 = roundtrip(img2, plugin, fmt)
testing.assert_allclose(img2, r2.astype(bool))
img3 = img_as_float(img)
r3 = roundtrip(img3, plugin, fmt)
if r3.dtype.kind == 'f':
testing.assert_allclose(img3, r3)
else:
testing.assert_allclose(r3, img_as_uint(img))
img4 = img_as_int(img)
if fmt.lower() in (('tif', 'tiff')):
img4 -= 100
r4 = roundtrip(img4, plugin, fmt)
testing.assert_allclose(r4, img4)
else:
r4 = roundtrip(img4, plugin, fmt)
testing.assert_allclose(r4, img_as_uint(img4))
img5 = img_as_uint(img)
r5 = roundtrip(img5, plugin, fmt)
testing.assert_allclose(r5, img5)
def setup_test():
"""Default package level setup routine for skimage tests.
Import packages known to raise warnings, and then
force warnings to raise errors.
Also set the random seed to zero.
"""
warnings.simplefilter('default')
from scipy import signal, ndimage, special, optimize, linalg
from scipy.io import loadmat
from skimage import viewer
np.random.seed(0)
warnings.simplefilter('error')
def teardown_test():
"""Default package level teardown routine for skimage tests.
Restore warnings to default behavior
"""
warnings.simplefilter('default')
def fetch(data_filename):
"""Attempt to fetch data, but if unavailable, skip the tests."""
try:
return data._fetch(data_filename)
except (ConnectionError, ModuleNotFoundError):
pytest.skip(f'Unable to download {data_filename}')
def test_parallel(num_threads=2, warnings_matching=None):
"""Decorator to run the same function multiple times in parallel.
This decorator is useful to ensure that separate threads execute
concurrently and correctly while releasing the GIL.
Parameters
----------
num_threads : int, optional
The number of times the function is run in parallel.
warnings_matching: list or None
This parameter is passed on to `expected_warnings` so as not to have
race conditions with the warnings filters. A single
`expected_warnings` context manager is used for all threads.
If None, then no warnings are checked.
"""
assert num_threads > 0
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
with expected_warnings(warnings_matching):
threads = []
for i in range(num_threads - 1):
thread = threading.Thread(target=func, args=args,
kwargs=kwargs)
threads.append(thread)
for thread in threads:
thread.start()
result = func(*args, **kwargs)
for thread in threads:
thread.join()
return result
return inner
return wrapper
if __name__ == '__main__':
color_check('pil')
mono_check('pil')
mono_check('pil', 'bmp')
mono_check('pil', 'tiff')

View file

@ -0,0 +1,9 @@
from ..testing import setup_test, teardown_test
def setup():
setup_test()
def teardown():
teardown_test()

View file

@ -0,0 +1,21 @@
from ..fast_exp import fast_exp
import numpy as np
def test_fast_exp():
X = np.linspace(-5, 0, 5000, endpoint=True)
# Ground truth
Y = np.exp(X)
# Approximation at double precision
_y_f64 = np.array([fast_exp['float64_t'](x) for x in X])
# Approximation at single precision
_y_f32 = np.array([fast_exp['float32_t'](x) for x in X.astype('float32')],
dtype='float32')
for _y in [_y_f64, _y_f32]:
assert np.abs(Y - _y).mean() < 3e-3

View file

@ -0,0 +1,75 @@
from skimage._shared._geometry import polygon_clip, polygon_area
import numpy as np
from numpy.testing import assert_equal, assert_almost_equal
hand = np.array(
[[ 1.64516129, 1.16145833 ],
[ 1.64516129, 1.59375 ],
[ 1.35080645, 1.921875 ],
[ 1.375 , 2.18229167 ],
[ 1.68548387, 1.9375 ],
[ 1.60887097, 2.55208333 ],
[ 1.68548387, 2.69791667 ],
[ 1.76209677, 2.56770833 ],
[ 1.83064516, 1.97395833 ],
[ 1.89516129, 2.75 ],
[ 1.9516129 , 2.84895833 ],
[ 2.01209677, 2.76041667 ],
[ 1.99193548, 1.99479167 ],
[ 2.11290323, 2.63020833 ],
[ 2.2016129 , 2.734375 ],
[ 2.25403226, 2.60416667 ],
[ 2.14919355, 1.953125 ],
[ 2.30645161, 2.36979167 ],
[ 2.39112903, 2.36979167 ],
[ 2.41532258, 2.1875 ],
[ 2.1733871 , 1.703125 ],
[ 2.07782258, 1.16666667 ]])
def test_polygon_area():
x = [0, 0, 1, 1]
y = [0, 1, 1, 0]
assert_almost_equal(polygon_area(y, x), 1)
x = [0, 0, 1]
y = [0, 1, 1]
assert_almost_equal(polygon_area(y, x), 0.5)
x = [0, 0, 0.5, 1, 1, 0.5]
y = [0, 1, 0.5, 1, 0, 0.5]
assert_almost_equal(polygon_area(y, x), 0.5)
def test_poly_clip():
x = [0, 1, 2, 1]
y = [0, -1, 0, 1]
yc, xc = polygon_clip(y, x, 0, 0, 1, 1)
assert_equal(polygon_area(yc, xc), 0.5)
x = [-1, 1.5, 1.5, -1]
y = [.5, 0.5, 1.5, 1.5]
yc, xc = polygon_clip(y, x, 0, 0, 1, 1)
assert_equal(polygon_area(yc, xc), 0.5)
def test_hand_clip():
(r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
clip_r, clip_c = polygon_clip(hand[:, 1], hand[:, 0], r0, c0, r1, c1)
assert_equal(clip_r.size, 19)
assert_equal(clip_r[0], clip_r[-1])
assert_equal(clip_c[0], clip_c[-1])
(r0, c0, r1, c1) = (1.0, 1.5, 1.7, 2.5)
clip_r, clip_c = polygon_clip(hand[:, 1], hand[:, 0], r0, c0, r1, c1)
assert_equal(clip_r.size, 6)
(r0, c0, r1, c1) = (1.0, 1.5, 1.5, 2.5)
clip_r, clip_c = polygon_clip(hand[:, 1], hand[:, 0], r0, c0, r1, c1)
assert_equal(clip_r.size, 5)

View file

@ -0,0 +1,27 @@
from skimage._shared.interpolation import coord_map_py
from skimage._shared.testing import assert_array_equal
def test_coord_map():
symmetric = [coord_map_py(4, n, 'S') for n in range(-6, 6)]
expected_symmetric = [2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2]
assert_array_equal(symmetric, expected_symmetric)
wrap = [coord_map_py(4, n, 'W') for n in range(-6, 6)]
expected_wrap = [2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1]
assert_array_equal(wrap, expected_wrap)
edge = [coord_map_py(4, n, 'E') for n in range(-6, 6)]
expected_edge = [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3]
assert_array_equal(edge, expected_edge)
reflect = [coord_map_py(4, n, 'R') for n in range(-6, 6)]
expected_reflect = [0, 1, 2, 3, 2, 1, 0, 1, 2, 3, 2, 1]
assert_array_equal(reflect, expected_reflect)
reflect = [coord_map_py(1, n, 'R') for n in range(-6, 6)]
expected_reflect = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
assert_array_equal(reflect, expected_reflect)
other = [coord_map_py(4, n, 'undefined') for n in range(-6, 6)]
expected_other = list(range(-6, 6))
assert_array_equal(other, expected_other)

View file

@ -0,0 +1,42 @@
import numpy as np
from skimage._shared.utils import safe_as_int
from skimage._shared import testing
def test_int_cast_not_possible():
with testing.raises(ValueError):
safe_as_int(7.1)
with testing.raises(ValueError):
safe_as_int([7.1, 0.9])
with testing.raises(ValueError):
safe_as_int(np.r_[7.1, 0.9])
with testing.raises(ValueError):
safe_as_int((7.1, 0.9))
with testing.raises(ValueError):
safe_as_int(((3, 4, 1),
(2, 7.6, 289)))
with testing.raises(ValueError):
safe_as_int(7.1, 0.09)
with testing.raises(ValueError):
safe_as_int([7.1, 0.9], 0.09)
with testing.raises(ValueError):
safe_as_int(np.r_[7.1, 0.9], 0.09)
with testing.raises(ValueError):
safe_as_int((7.1, 0.9), 0.09)
with testing.raises(ValueError):
safe_as_int(((3, 4, 1),
(2, 7.6, 289)), 0.25)
def test_int_cast_possible():
testing.assert_equal(safe_as_int(7.1, atol=0.11), 7)
testing.assert_equal(safe_as_int(-7.1, atol=0.11), -7)
testing.assert_equal(safe_as_int(41.9, atol=0.11), 42)
testing.assert_array_equal(safe_as_int([2, 42, 5789234.0, 87, 4]),
np.r_[2, 42, 5789234, 87, 4])
testing.assert_array_equal(safe_as_int(np.r_[[[3, 4, 1.000000001],
[7, 2, -8.999999999],
[6, 9, -4234918347.]]]),
np.r_[[[3, 4, 1],
[7, 2, -9],
[6, 9, -4234918347]]])

View file

@ -0,0 +1,129 @@
""" Testing decorators module
"""
import numpy as np
from numpy.testing import assert_equal
from skimage._shared.testing import doctest_skip_parser, test_parallel
from skimage._shared import testing
import pytest
from skimage._shared._warnings import expected_warnings
from warnings import warn
def test_skipper():
def f():
pass
class c():
def __init__(self):
self.me = "I think, therefore..."
docstring = \
""" Header
>>> something # skip if not HAVE_AMODULE
>>> something + else
>>> a = 1 # skip if not HAVE_BMODULE
>>> something2 # skip if HAVE_AMODULE
"""
f.__doc__ = docstring
c.__doc__ = docstring
global HAVE_AMODULE, HAVE_BMODULE
HAVE_AMODULE = False
HAVE_BMODULE = True
f2 = doctest_skip_parser(f)
c2 = doctest_skip_parser(c)
assert f is f2
assert c is c2
expected = \
""" Header
>>> something # doctest: +SKIP
>>> something + else
>>> a = 1
>>> something2
"""
assert_equal(f2.__doc__, expected)
assert_equal(c2.__doc__, expected)
HAVE_AMODULE = True
HAVE_BMODULE = False
f.__doc__ = docstring
c.__doc__ = docstring
f2 = doctest_skip_parser(f)
c2 = doctest_skip_parser(c)
assert f is f2
expected = \
""" Header
>>> something
>>> something + else
>>> a = 1 # doctest: +SKIP
>>> something2 # doctest: +SKIP
"""
assert_equal(f2.__doc__, expected)
assert_equal(c2.__doc__, expected)
del HAVE_AMODULE
f.__doc__ = docstring
c.__doc__ = docstring
with testing.raises(NameError):
doctest_skip_parser(f)
with testing.raises(NameError):
doctest_skip_parser(c)
def test_test_parallel():
state = []
@test_parallel()
def change_state1():
state.append(None)
change_state1()
assert len(state) == 2
@test_parallel(num_threads=1)
def change_state2():
state.append(None)
change_state2()
assert len(state) == 3
@test_parallel(num_threads=3)
def change_state3():
state.append(None)
change_state3()
assert len(state) == 6
def test_parallel_warning():
@test_parallel()
def change_state_warns_fails():
warn("Test warning for test parallel", stacklevel=2)
with expected_warnings(['Test warning for test parallel']):
change_state_warns_fails()
@test_parallel(warnings_matching=['Test warning for test parallel'])
def change_state_warns_passes():
warn("Test warning for test parallel", stacklevel=2)
change_state_warns_passes()
def test_expected_warnings_noop():
# This will ensure the line beolow it behaves like a no-op
with expected_warnings(['Expected warnings test']):
# This should behave as a no-op
with expected_warnings(None):
warn('Expected warnings test')
if __name__ == '__main__':
np.testing.run_module_suite()

View file

@ -0,0 +1,129 @@
import sys
import pytest
import numpy as np
import numpy.testing as npt
from skimage._shared.utils import (check_nD, deprecate_kwarg,
_validate_interpolation_order,
change_default_value)
from skimage._shared import testing
from skimage._shared._warnings import expected_warnings
def test_change_default_value():
@change_default_value('arg1', new_value=-1, changed_version='0.12')
def foo(arg0, arg1=0, arg2=1):
"""Expected docstring"""
return arg0, arg1, arg2
@change_default_value('arg1', new_value=-1, changed_version='0.12',
warning_msg="Custom warning message")
def bar(arg0, arg1=0, arg2=1):
"""Expected docstring"""
return arg0, arg1, arg2
# Assert warning messages
with pytest.warns(FutureWarning) as record:
assert foo(0) == (0, 0, 1)
assert bar(0) == (0, 0, 1)
expected_msg = ("The new recommended value for arg1 is -1. Until "
"version 0.12, the default arg1 value is 0. From "
"version 0.12, the arg1 default value will be -1. "
"To avoid this warning, please explicitly set arg1 value.")
assert str(record[0].message) == expected_msg
assert str(record[1].message) == "Custom warning message"
# Assert that nothing happens if arg1 is set
with pytest.warns(None) as record:
# No kwargs
assert foo(0, 2) == (0, 2, 1)
assert foo(0, arg1=0) == (0, 0, 1)
# Function name and doc is preserved
assert foo.__name__ == 'foo'
if sys.flags.optimize < 2:
# if PYTHONOPTIMIZE is set to 2, docstrings are stripped
assert foo.__doc__ == 'Expected docstring'
# Assert no warning was raised
assert not record.list
def test_deprecated_kwarg():
@deprecate_kwarg({'old_arg1': 'new_arg1'})
def foo(arg0, new_arg1=1, arg2=None):
"""Expected docstring"""
return arg0, new_arg1, arg2
@deprecate_kwarg({'old_arg1': 'new_arg1'},
warning_msg="Custom warning message")
def bar(arg0, new_arg1=1, arg2=None):
"""Expected docstring"""
return arg0, new_arg1, arg2
# Assert that the DeprecationWarning is raised when the deprecated
# argument name is used and that the reasult is valid
with pytest.warns(FutureWarning) as record:
assert foo(0, old_arg1=1) == (0, 1, None)
assert bar(0, old_arg1=1) == (0, 1, None)
msg = ("'old_arg1' is a deprecated argument name "
"for `foo`. Please use 'new_arg1' instead.")
assert str(record[0].message) == msg
assert str(record[1].message) == "Custom warning message"
# Assert that nothing happens when the function is called with the
# new API
with pytest.warns(None) as record:
# No kwargs
assert foo(0) == (0, 1, None)
assert foo(0, 2) == (0, 2, None)
assert foo(0, 1, 2) == (0, 1, 2)
# Kwargs without deprecated argument
assert foo(0, new_arg1=1, arg2=2) == (0, 1, 2)
assert foo(0, new_arg1=2) == (0, 2, None)
assert foo(0, arg2=2) == (0, 1, 2)
assert foo(0, 1, arg2=2) == (0, 1, 2)
# Function name and doc is preserved
assert foo.__name__ == 'foo'
if sys.flags.optimize < 2:
# if PYTHONOPTIMIZE is set to 2, docstrings are stripped
assert foo.__doc__ == 'Expected docstring'
# Assert no warning was raised
assert not record.list
def test_check_nD():
z = np.random.random(200**2).reshape((200, 200))
x = z[10:30, 30:10]
with testing.raises(ValueError):
check_nD(x, 2)
@pytest.mark.parametrize('dtype', [bool, int, np.uint8, np.uint16,
float, np.float32, np.float64])
@pytest.mark.parametrize('order', [None, -1, 0, 1, 2, 3, 4, 5, 6])
def test_validate_interpolation_order(dtype, order):
if order is None:
# Default order
assert (_validate_interpolation_order(dtype, None) == 0
if dtype == bool else 1)
elif order < 0 or order > 5:
# Order not in valid range
with testing.raises(ValueError):
_validate_interpolation_order(dtype, order)
elif dtype == bool and order != 0:
# Deprecated order for bool array
with expected_warnings(["Input image dtype is bool"]):
assert _validate_interpolation_order(bool, order) == order
else:
# Valid use case
assert _validate_interpolation_order(dtype, order) == order
if __name__ == "__main__":
npt.run_module_suite()

View file

@ -0,0 +1,41 @@
"""Tests for the version requirement functions.
"""
import numpy as np
from numpy.testing import assert_equal
from skimage._shared import version_requirements as version_req
from skimage._shared import testing
def test_get_module_version():
assert version_req.get_module_version('numpy')
assert version_req.get_module_version('scipy')
with testing.raises(ImportError):
version_req.get_module_version('fakenumpy')
def test_is_installed():
assert version_req.is_installed('python', '>=2.7')
assert not version_req.is_installed('numpy', '<1.0')
def test_require():
# A function that only runs on Python >2.7 and numpy > 1.5 (should pass)
@version_req.require('python', '>2.7')
@version_req.require('numpy', '>1.5')
def foo():
return 1
assert_equal(foo(), 1)
# function that requires scipy < 0.1 (should fail)
@version_req.require('scipy', '<0.1')
def bar():
return 0
with testing.raises(ImportError):
bar()
def test_get_module():
assert version_req.get_module("numpy") is np

View file

@ -0,0 +1,37 @@
import os
from skimage._shared._warnings import expected_warnings
import pytest
@pytest.fixture(scope='function')
def setup():
# Remove any environment variable if it exists
old_strictness = os.environ.pop('SKIMAGE_TEST_STRICT_WARNINGS', None)
yield
# Add the user's desired strictness
if old_strictness is not None:
os.environ['SKIMAGE_TEST_STRICT_WARNINGS'] = old_strictness
def test_strict_warnigns_default(setup):
# By default we should fail on missing expected warnings
with pytest.raises(ValueError):
with expected_warnings(['some warnings']):
pass
@pytest.mark.parametrize('strictness', ['1', 'true', 'True', 'TRUE'])
def test_strict_warning_true(setup, strictness):
os.environ['SKIMAGE_TEST_STRICT_WARNINGS'] = strictness
with pytest.raises(ValueError):
with expected_warnings(['some warnings']):
pass
@pytest.mark.parametrize('strictness', ['0', 'false', 'False', 'FALSE'])
def test_strict_warning_false(setup, strictness):
# If the user doesnn't wish to be strict about warnigns
# the following shouldn't raise any error
os.environ['SKIMAGE_TEST_STRICT_WARNINGS'] = strictness
with expected_warnings(['some warnings']):
pass

View file

@ -0,0 +1,375 @@
import inspect
import warnings
import functools
import sys
import numpy as np
import numbers
from ..util import img_as_float
from ._warnings import all_warnings, warn
__all__ = ['deprecated', 'get_bound_method_class', 'all_warnings',
'safe_as_int', 'check_nD', 'check_shape_equality', 'warn']
class skimage_deprecation(Warning):
"""Create our own deprecation class, since Python >= 2.7
silences deprecations by default.
"""
pass
class change_default_value:
"""Decorator for changing the default value of an argument.
Parameters
----------
arg_name: str
The name of the argument to be updated.
new_value: any
The argument new value.
changed_version : str
The package version in which the change will be introduced.
warning_msg: str
Optional warning message. If None, a generic warning message
is used.
"""
def __init__(self, arg_name, *, new_value, changed_version,
warning_msg=None):
self.arg_name = arg_name
self.new_value = new_value
self.warning_msg = warning_msg
self.changed_version = changed_version
def __call__(self, func):
parameters = inspect.signature(func).parameters
arg_idx = list(parameters.keys()).index(self.arg_name)
old_value = parameters[self.arg_name].default
if self.warning_msg is None:
self.warning_msg = (
f"The new recommended value for {self.arg_name} is "
f"{self.new_value}. Until version {self.changed_version}, "
f"the default {self.arg_name} value is {old_value}. "
f"From version {self.changed_version}, the {self.arg_name} "
f"default value will be {self.new_value}. To avoid "
f"this warning, please explicitly set {self.arg_name} value.")
@functools.wraps(func)
def fixed_func(*args, **kwargs):
if len(args) < arg_idx + 1 and self.arg_name not in kwargs.keys():
# warn that arg_name default value changed:
warnings.warn(self.warning_msg, FutureWarning, stacklevel=2)
return func(*args, **kwargs)
return fixed_func
class deprecate_kwarg:
"""Decorator ensuring backward compatibility when argument names are
modified in a function definition.
Parameters
----------
arg_mapping: dict
Mapping between the function's old argument names and the new
ones.
warning_msg: str
Optional warning message. If None, a generic warning message
is used.
removed_version : str
The package version in which the deprecated argument will be
removed.
"""
def __init__(self, kwarg_mapping, warning_msg=None, removed_version=None):
self.kwarg_mapping = kwarg_mapping
if warning_msg is None:
self.warning_msg = ("'{old_arg}' is a deprecated argument name "
"for `{func_name}`. ")
if removed_version is not None:
self.warning_msg += ("It will be removed in version {}. "
.format(removed_version))
self.warning_msg += "Please use '{new_arg}' instead."
else:
self.warning_msg = warning_msg
def __call__(self, func):
@functools.wraps(func)
def fixed_func(*args, **kwargs):
for old_arg, new_arg in self.kwarg_mapping.items():
if old_arg in kwargs:
# warn that the function interface has changed:
warnings.warn(self.warning_msg.format(
old_arg=old_arg, func_name=func.__name__,
new_arg=new_arg), FutureWarning, stacklevel=2)
# Substitute new_arg to old_arg
kwargs[new_arg] = kwargs.pop(old_arg)
# Call the function with the fixed arguments
return func(*args, **kwargs)
return fixed_func
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.
removed_version : str
The package version in which the deprecated function will be removed.
"""
def __init__(self, alt_func=None, behavior='warn', removed_version=None):
self.alt_func = alt_func
self.behavior = behavior
self.removed_version = removed_version
def __call__(self, func):
alt_msg = ''
if self.alt_func is not None:
alt_msg = ' Use ``%s`` instead.' % self.alt_func
rmv_msg = ''
if self.removed_version is not None:
rmv_msg = (' and will be removed in version %s' %
self.removed_version)
msg = ('Function ``%s`` is deprecated' % func.__name__ +
rmv_msg + '.' + alt_msg)
@functools.wraps(func)
def wrapped(*args, **kwargs):
if self.behavior == 'warn':
func_code = func.__code__
warnings.simplefilter('always', skimage_deprecation)
warnings.warn_explicit(msg,
category=skimage_deprecation,
filename=func_code.co_filename,
lineno=func_code.co_firstlineno + 1)
elif self.behavior == 'raise':
raise skimage_deprecation(msg)
return func(*args, **kwargs)
# modify doc string to display deprecation warning
doc = '**Deprecated function**.' + alt_msg
if wrapped.__doc__ is None:
wrapped.__doc__ = doc
else:
wrapped.__doc__ = doc + '\n\n ' + wrapped.__doc__
return wrapped
def get_bound_method_class(m):
"""Return the class for a bound method.
"""
return m.im_class if sys.version < '3' else m.__self__.__class__
def safe_as_int(val, atol=1e-3):
"""
Attempt to safely cast values to integer format.
Parameters
----------
val : scalar or iterable of scalars
Number or container of numbers which are intended to be interpreted as
integers, e.g., for indexing purposes, but which may not carry integer
type.
atol : float
Absolute tolerance away from nearest integer to consider values in
``val`` functionally integers.
Returns
-------
val_int : NumPy scalar or ndarray of dtype `np.int64`
Returns the input value(s) coerced to dtype `np.int64` assuming all
were within ``atol`` of the nearest integer.
Notes
-----
This operation calculates ``val`` modulo 1, which returns the mantissa of
all values. Then all mantissas greater than 0.5 are subtracted from one.
Finally, the absolute tolerance from zero is calculated. If it is less
than ``atol`` for all value(s) in ``val``, they are rounded and returned
in an integer array. Or, if ``val`` was a scalar, a NumPy scalar type is
returned.
If any value(s) are outside the specified tolerance, an informative error
is raised.
Examples
--------
>>> safe_as_int(7.0)
7
>>> safe_as_int([9, 4, 2.9999999999])
array([9, 4, 3])
>>> safe_as_int(53.1)
Traceback (most recent call last):
...
ValueError: Integer argument required but received 53.1, check inputs.
>>> safe_as_int(53.01, atol=0.01)
53
"""
mod = np.asarray(val) % 1 # Extract mantissa
# Check for and subtract any mod values > 0.5 from 1
if mod.ndim == 0: # Scalar input, cannot be indexed
if mod > 0.5:
mod = 1 - mod
else: # Iterable input, now ndarray
mod[mod > 0.5] = 1 - mod[mod > 0.5] # Test on each side of nearest int
try:
np.testing.assert_allclose(mod, 0, atol=atol)
except AssertionError:
raise ValueError("Integer argument required but received "
"{0}, check inputs.".format(val))
return np.round(val).astype(np.int64)
def check_shape_equality(im1, im2):
"""Raise an error if the shape do not match."""
if not im1.shape == im2.shape:
raise ValueError('Input images must have the same dimensions.')
return
def check_nD(array, ndim, arg_name='image'):
"""
Verify an array meets the desired ndims and array isn't empty.
Parameters
----------
array : array-like
Input array to be validated
ndim : int or iterable of ints
Allowable ndim or ndims for the array.
arg_name : str, optional
The name of the array in the original function.
"""
array = np.asanyarray(array)
msg_incorrect_dim = "The parameter `%s` must be a %s-dimensional array"
msg_empty_array = "The parameter `%s` cannot be an empty array"
if isinstance(ndim, int):
ndim = [ndim]
if array.size == 0:
raise ValueError(msg_empty_array % (arg_name))
if not array.ndim in ndim:
raise ValueError(msg_incorrect_dim % (arg_name, '-or-'.join([str(n) for n in ndim])))
def check_random_state(seed):
"""Turn seed into a `np.random.RandomState` instance.
Parameters
----------
seed : None, int or np.random.RandomState
If `seed` is None, return the RandomState singleton used by `np.random`.
If `seed` is an int, return a new RandomState instance seeded with `seed`.
If `seed` is already a RandomState instance, return it.
Raises
------
ValueError
If `seed` is of the wrong type.
"""
# Function originally from scikit-learn's module sklearn.utils.validation
if seed is None or seed is np.random:
return np.random.mtrand._rand
if isinstance(seed, (numbers.Integral, np.integer)):
return np.random.RandomState(seed)
if isinstance(seed, np.random.RandomState):
return seed
raise ValueError('%r cannot be used to seed a numpy.random.RandomState'
' instance' % seed)
def convert_to_float(image, preserve_range):
"""Convert input image to float image with the appropriate range.
Parameters
----------
image : ndarray
Input image.
preserve_range : bool
Determines if the range of the image should be kept or transformed
using img_as_float. Also see
https://scikit-image.org/docs/dev/user_guide/data_types.html
Notes:
------
* Input images with `float32` data type are not upcast.
Returns
-------
image : ndarray
Transformed version of the input.
"""
if preserve_range:
# Convert image to double only if it is not single or double
# precision float
if image.dtype.char not in 'df':
image = image.astype(float)
else:
image = img_as_float(image)
return image
def _validate_interpolation_order(image_dtype, order):
"""Validate and return spline interpolation's order.
Parameters
----------
image_dtype : dtype
Image dtype.
order : int, optional
The order of the spline interpolation. The order has to be in
the range 0-5. See `skimage.transform.warp` for detail.
Returns
-------
order : int
if input order is None, returns 0 if image_dtype is bool and 1
otherwise. Otherwise, image_dtype is checked and input order
is validated accordingly (order > 0 is not supported for bool
image dtype)
"""
if order is None:
return 0 if image_dtype == bool else 1
if order < 0 or order > 5:
raise ValueError("Spline interpolation order has to be in the "
"range 0-5.")
if image_dtype == bool and order != 0:
warn("Input image dtype is bool. Interpolation is not defined "
"with bool data type. Please set order to 0 or explicitely "
"cast input image to another data type. Starting from version "
"0.19 a ValueError will be raised instead of this warning.",
FutureWarning, stacklevel=2)
return order

View file

@ -0,0 +1,180 @@
import sys
def ensure_python_version(min_version):
if not isinstance(min_version, tuple):
min_version = (min_version, )
if sys.version_info < min_version:
# since ensure_python_version is in the critical import path,
# we lazy import it.
from platform import python_version
raise ImportError("""
You are running scikit-image on an unsupported version of Python.
Unfortunately, scikit-image 0.15 and above no longer work with your installed
version of Python (%s). You therefore have two options: either upgrade to
Python %s, or install an older version of scikit-image.
For Python 2.7 or Python 3.4, use
$ pip install 'scikit-image<0.15'
Please also consider updating `pip` and `setuptools`:
$ pip install pip setuptools --upgrade
Newer versions of these tools avoid installing packages incompatible
with your version of Python.
""" % (python_version(), '.'.join([str(v) for v in min_version])))
def _check_version(actver, version, cmp_op):
"""
Check version string of an active module against a required version.
If dev/prerelease tags result in TypeError for string-number comparison,
it is assumed that the dependency is satisfied.
Users on dev branches are responsible for keeping their own packages up to
date.
Copyright (C) 2013 The IPython Development Team
Distributed under the terms of the BSD License.
"""
# since version_requirements.py is in the critical import path, we
# lazy import it
from distutils.version import LooseVersion
try:
if cmp_op == '>':
return LooseVersion(actver) > LooseVersion(version)
elif cmp_op == '>=':
return LooseVersion(actver) >= LooseVersion(version)
elif cmp_op == '=':
return LooseVersion(actver) == LooseVersion(version)
elif cmp_op == '<':
return LooseVersion(actver) < LooseVersion(version)
else:
return False
except TypeError:
return True
def get_module_version(module_name):
"""Return module version or None if version can't be retrieved."""
mod = __import__(module_name,
fromlist=[module_name.rpartition('.')[-1]])
return getattr(mod, '__version__', getattr(mod, 'VERSION', None))
def is_installed(name, version=None):
"""Test if *name* is installed.
Parameters
----------
name : str
Name of module or "python"
version : str, optional
Version string to test against.
If version is not None, checking version
(must have an attribute named '__version__' or 'VERSION')
Version may start with =, >=, > or < to specify the exact requirement
Returns
-------
out : bool
True if `name` is installed matching the optional version.
Notes
-----
Original Copyright (C) 2009-2011 Pierre Raybaut
Licensed under the terms of the MIT License.
"""
if name.lower() == 'python':
actver = sys.version[:6]
else:
try:
actver = get_module_version(name)
except ImportError:
return False
if version is None:
return True
else:
# since version_requirements is in the critical import path,
# we lazy import re
import re
match = re.search('[0-9]', version)
assert match is not None, "Invalid version number"
symb = version[:match.start()]
if not symb:
symb = '='
assert symb in ('>=', '>', '=', '<'),\
"Invalid version condition '%s'" % symb
version = version[match.start():]
return _check_version(actver, version, symb)
def require(name, version=None):
"""Return decorator that forces a requirement for a function or class.
Parameters
----------
name : str
Name of module or "python".
version : str, optional
Version string to test against.
If version is not None, checking version
(must have an attribute named '__version__' or 'VERSION')
Version may start with =, >=, > or < to specify the exact requirement
Returns
-------
func : function
A decorator that raises an ImportError if a function is run
in the absence of the input dependency.
"""
# since version_requirements is in the critical import path, we lazy import
# functools
import functools
def decorator(obj):
@functools.wraps(obj)
def func_wrapped(*args, **kwargs):
if is_installed(name, version):
return obj(*args, **kwargs)
else:
msg = '"%s" in "%s" requires "%s'
msg = msg % (obj, obj.__module__, name)
if not version is None:
msg += " %s" % version
raise ImportError(msg + '"')
return func_wrapped
return decorator
def get_module(module_name, version=None):
"""Return a module object of name *module_name* if installed.
Parameters
----------
module_name : str
Name of module.
version : str, optional
Version string to test against.
If version is not None, checking version
(must have an attribute named '__version__' or 'VERSION')
Version may start with =, >=, > or < to specify the exact requirement
Returns
-------
mod : module or None
Module if *module_name* is installed matching the optional version
or None otherwise.
"""
if not is_installed(module_name, version):
return None
return __import__(module_name,
fromlist=[module_name.rpartition('.')[-1]])

View file

@ -0,0 +1,133 @@
from .colorconv import (convert_colorspace,
guess_spatial_dimensions,
rgba2rgb,
rgb2hsv,
hsv2rgb,
rgb2xyz,
xyz2rgb,
rgb2rgbcie,
rgbcie2rgb,
rgb2grey,
rgb2gray,
gray2rgb,
gray2rgba,
grey2rgb,
xyz2lab,
lab2xyz,
lab2rgb,
rgb2lab,
xyz2luv,
luv2xyz,
luv2rgb,
rgb2luv,
rgb2hed,
hed2rgb,
lab2lch,
lch2lab,
rgb2yuv,
yuv2rgb,
rgb2yiq,
yiq2rgb,
rgb2ypbpr,
ypbpr2rgb,
rgb2ycbcr,
ycbcr2rgb,
rgb2ydbdr,
ydbdr2rgb,
separate_stains,
combine_stains,
rgb_from_hed,
hed_from_rgb,
rgb_from_hdx,
hdx_from_rgb,
rgb_from_fgx,
fgx_from_rgb,
rgb_from_bex,
bex_from_rgb,
rgb_from_rbd,
rbd_from_rgb,
rgb_from_gdx,
gdx_from_rgb,
rgb_from_hax,
hax_from_rgb,
rgb_from_bro,
bro_from_rgb,
rgb_from_bpx,
bpx_from_rgb,
rgb_from_ahx,
ahx_from_rgb,
rgb_from_hpx,
hpx_from_rgb)
from .colorlabel import color_dict, label2rgb
from .delta_e import (deltaE_cie76,
deltaE_ciede94,
deltaE_ciede2000,
deltaE_cmc,
)
__all__ = ['convert_colorspace',
'guess_spatial_dimensions',
'rgba2rgb',
'rgb2hsv',
'hsv2rgb',
'rgb2xyz',
'xyz2rgb',
'rgb2rgbcie',
'rgbcie2rgb',
'rgb2grey',
'rgb2gray',
'gray2rgb',
'gray2rgba',
'grey2rgb',
'xyz2lab',
'lab2xyz',
'lab2rgb',
'rgb2lab',
'rgb2hed',
'hed2rgb',
'lab2lch',
'lch2lab',
'rgb2yuv',
'yuv2rgb',
'rgb2yiq',
'yiq2rgb',
'rgb2ypbpr',
'ypbpr2rgb',
'rgb2ycbcr',
'ycbcr2rgb',
'rgb2ydbdr',
'ydbdr2rgb',
'separate_stains',
'combine_stains',
'rgb_from_hed',
'hed_from_rgb',
'rgb_from_hdx',
'hdx_from_rgb',
'rgb_from_fgx',
'fgx_from_rgb',
'rgb_from_bex',
'bex_from_rgb',
'rgb_from_rbd',
'rbd_from_rgb',
'rgb_from_gdx',
'gdx_from_rgb',
'rgb_from_hax',
'hax_from_rgb',
'rgb_from_bro',
'bro_from_rgb',
'rgb_from_bpx',
'bpx_from_rgb',
'rgb_from_ahx',
'ahx_from_rgb',
'rgb_from_hpx',
'hpx_from_rgb',
'color_dict',
'label2rgb',
'deltaE_cie76',
'deltaE_ciede94',
'deltaE_ciede2000',
'deltaE_cmc',
]

View file

@ -0,0 +1,79 @@
import functools
import numpy as np
from .. import color
from ..util.dtype import _convert
__all__ = ['adapt_rgb', 'hsv_value', 'each_channel']
def is_rgb_like(image):
"""Return True if the image *looks* like it's RGB.
This function should not be public because it is only intended to be used
for functions that don't accept volumes as input, since checking an image's
shape is fragile.
"""
return (image.ndim == 3) and (image.shape[2] in (3, 4))
def adapt_rgb(apply_to_rgb):
"""Return decorator that adapts to RGB images to a gray-scale filter.
This function is only intended to be used for functions that don't accept
volumes as input, since checking an image's shape is fragile.
Parameters
----------
apply_to_rgb : function
Function that returns a filtered image from an image-filter and RGB
image. This will only be called if the image is RGB-like.
"""
def decorator(image_filter):
@functools.wraps(image_filter)
def image_filter_adapted(image, *args, **kwargs):
if is_rgb_like(image):
return apply_to_rgb(image_filter, image, *args, **kwargs)
else:
return image_filter(image, *args, **kwargs)
return image_filter_adapted
return decorator
def hsv_value(image_filter, image, *args, **kwargs):
"""Return color image by applying `image_filter` on HSV-value of `image`.
Note that this function is intended for use with `adapt_rgb`.
Parameters
----------
image_filter : function
Function that filters a gray-scale image.
image : array
Input image. Note that RGBA images are treated as RGB.
"""
# Slice the first three channels so that we remove any alpha channels.
hsv = color.rgb2hsv(image[:, :, :3])
value = hsv[:, :, 2].copy()
value = image_filter(value, *args, **kwargs)
hsv[:, :, 2] = _convert(value, hsv.dtype)
return color.hsv2rgb(hsv)
def each_channel(image_filter, image, *args, **kwargs):
"""Return color image by applying `image_filter` on channels of `image`.
Note that this function is intended for use with `adapt_rgb`.
Parameters
----------
image_filter : function
Function that filters a gray-scale image.
image : array
Input image.
"""
c_new = [image_filter(c, *args, **kwargs)
for c in np.moveaxis(image, -1, 0)]
return np.moveaxis(np.array(c_new), 0, -1)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,237 @@
import itertools
import numpy as np
from .._shared.utils import warn, change_default_value
from ..util import img_as_float
from . import rgb_colors
from .colorconv import rgb2gray, gray2rgb
__all__ = ['color_dict', 'label2rgb', 'DEFAULT_COLORS']
DEFAULT_COLORS = ('red', 'blue', 'yellow', 'magenta', 'green',
'indigo', 'darkorange', 'cyan', 'pink', 'yellowgreen')
color_dict = {k: v for k, v in rgb_colors.__dict__.items()
if isinstance(v, tuple)}
def _rgb_vector(color):
"""Return RGB color as (1, 3) array.
This RGB array gets multiplied by masked regions of an RGB image, which are
partially flattened by masking (i.e. dimensions 2D + RGB -> 1D + RGB).
Parameters
----------
color : str or array
Color name in `color_dict` or RGB float values between [0, 1].
"""
if isinstance(color, str):
color = color_dict[color]
# Slice to handle RGBA colors.
return np.array(color[:3])
def _match_label_with_color(label, colors, bg_label, bg_color):
"""Return `unique_labels` and `color_cycle` for label array and color list.
Colors are cycled for normal labels, but the background color should only
be used for the background.
"""
# Temporarily set background color; it will be removed later.
if bg_color is None:
bg_color = (0, 0, 0)
bg_color = _rgb_vector([bg_color])
# map labels to their ranks among all labels from small to large
unique_labels, mapped_labels = np.unique(label, return_inverse=True)
# get rank of bg_label
bg_label_rank_list = mapped_labels[label.flat == bg_label]
# The rank of each label is the index of the color it is matched to in
# color cycle. bg_label should always be mapped to the first color, so
# its rank must be 0. Other labels should be ranked from small to large
# from 1.
if len(bg_label_rank_list) > 0:
bg_label_rank = bg_label_rank_list[0]
mapped_labels[mapped_labels < bg_label_rank] += 1
mapped_labels[label.flat == bg_label] = 0
else:
mapped_labels += 1
# Modify labels and color cycle so background color is used only once.
color_cycle = itertools.cycle(colors)
color_cycle = itertools.chain(bg_color, color_cycle)
return mapped_labels, color_cycle
@change_default_value("bg_label", new_value=0, changed_version="0.19")
def label2rgb(label, image=None, colors=None, alpha=0.3,
bg_label=-1, bg_color=(0, 0, 0), image_alpha=1, kind='overlay'):
"""Return an RGB image where color-coded labels are painted over the image.
Parameters
----------
label : array, shape (M, N)
Integer array of labels with the same shape as `image`.
image : array, shape (M, N, 3), optional
Image used as underlay for labels. If the input is an RGB image, it's
converted to grayscale before coloring.
colors : list, optional
List of colors. If the number of labels exceeds the number of colors,
then the colors are cycled.
alpha : float [0, 1], optional
Opacity of colorized labels. Ignored if image is `None`.
bg_label : int, optional
Label that's treated as the background. If `bg_label` is specified,
`bg_color` is `None`, and `kind` is `overlay`,
background is not painted by any colors.
bg_color : str or array, optional
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1].
image_alpha : float [0, 1], optional
Opacity of the image.
kind : string, one of {'overlay', 'avg'}
The kind of color image desired. 'overlay' cycles over defined colors
and overlays the colored labels over the original image. 'avg' replaces
each labeled segment with its average color, for a stained-class or
pastel painting appearance.
Returns
-------
result : array of float, shape (M, N, 3)
The result of blending a cycling colormap (`colors`) for each distinct
value in `label` with the image, at a certain alpha value.
"""
if kind == 'overlay':
return _label2rgb_overlay(label, image, colors, alpha, bg_label,
bg_color, image_alpha)
elif kind == 'avg':
return _label2rgb_avg(label, image, bg_label, bg_color)
else:
raise ValueError("`kind` must be either 'overlay' or 'avg'.")
def _label2rgb_overlay(label, image=None, colors=None, alpha=0.3,
bg_label=-1, bg_color=None, image_alpha=1):
"""Return an RGB image where color-coded labels are painted over the image.
Parameters
----------
label : array, shape (M, N)
Integer array of labels with the same shape as `image`.
image : array, shape (M, N, 3), optional
Image used as underlay for labels. If the input is an RGB image, it's
converted to grayscale before coloring.
colors : list, optional
List of colors. If the number of labels exceeds the number of colors,
then the colors are cycled.
alpha : float [0, 1], optional
Opacity of colorized labels. Ignored if image is `None`.
bg_label : int, optional
Label that's treated as the background. If `bg_label` is specified and
`bg_color` is `None`, background is not painted by any colors.
bg_color : str or array, optional
Background color. Must be a name in `color_dict` or RGB float values
between [0, 1].
image_alpha : float [0, 1], optional
Opacity of the image.
Returns
-------
result : array of float, shape (M, N, 3)
The result of blending a cycling colormap (`colors`) for each distinct
value in `label` with the image, at a certain alpha value.
"""
if colors is None:
colors = DEFAULT_COLORS
colors = [_rgb_vector(c) for c in colors]
if image is None:
image = np.zeros(label.shape + (3,), dtype=np.float64)
# Opacity doesn't make sense if no image exists.
alpha = 1
else:
if not image.shape[:2] == label.shape:
raise ValueError("`image` and `label` must be the same shape")
if image.min() < 0:
warn("Negative intensities in `image` are not supported")
if image.ndim > label.ndim:
image = img_as_float(rgb2gray(image))
else:
image = img_as_float(image)
image = gray2rgb(image) * image_alpha + (1 - image_alpha)
# Ensure that all labels are non-negative so we can index into
# `label_to_color` correctly.
offset = min(label.min(), bg_label)
if offset != 0:
label = label - offset # Make sure you don't modify the input array.
bg_label -= offset
new_type = np.min_scalar_type(int(label.max()))
if new_type == np.bool:
new_type = np.uint8
label = label.astype(new_type)
mapped_labels_flat, color_cycle = _match_label_with_color(label, colors,
bg_label, bg_color)
if len(mapped_labels_flat) == 0:
return image
dense_labels = range(max(mapped_labels_flat) + 1)
label_to_color = np.array([c for i, c in zip(dense_labels, color_cycle)])
mapped_labels = label
mapped_labels.flat = mapped_labels_flat
result = label_to_color[mapped_labels] * alpha + image * (1 - alpha)
# Remove background label if its color was not specified.
remove_background = 0 in mapped_labels_flat and bg_color is None
if remove_background:
result[label == bg_label] = image[label == bg_label]
return result
def _label2rgb_avg(label_field, image, bg_label=0, bg_color=(0, 0, 0)):
"""Visualise each segment in `label_field` with its mean color in `image`.
Parameters
----------
label_field : array of int
A segmentation of an image.
image : array, shape ``label_field.shape + (3,)``
A color image of the same spatial shape as `label_field`.
bg_label : int, optional
A value in `label_field` to be treated as background.
bg_color : 3-tuple of int, optional
The color for the background label
Returns
-------
out : array, same shape and type as `image`
The output visualization.
"""
out = np.zeros_like(image)
labels = np.unique(label_field)
bg = (labels == bg_label)
if bg.any():
labels = labels[labels != bg_label]
mask = (label_field == bg_label).nonzero()
out[mask] = bg_color
for label in labels:
mask = (label_field == label).nonzero()
color = image[mask].mean(axis=0)
out[mask] = color
return out

View file

@ -0,0 +1,340 @@
"""
Functions for calculating the "distance" between colors.
Implicit in these definitions of "distance" is the notion of "Just Noticeable
Distance" (JND). This represents the distance between colors where a human can
perceive different colors. Humans are more sensitive to certain colors than
others, which different deltaE metrics correct for with varying degrees of
sophistication.
The literature often mentions 1 as the minimum distance for visual
differentiation, but more recent studies (Mahy 1994) peg JND at 2.3
The delta-E notation comes from the German word for "Sensation" (Empfindung).
Reference
---------
https://en.wikipedia.org/wiki/Color_difference
"""
import numpy as np
from .colorconv import lab2lch, _cart2polar_2pi
def deltaE_cie76(lab1, lab2):
"""Euclidean distance between two points in Lab color space
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
Returns
-------
dE : array_like
distance between colors `lab1` and `lab2`
References
----------
.. [1] https://en.wikipedia.org/wiki/Color_difference
.. [2] A. R. Robertson, "The CIE 1976 color-difference formulae,"
Color Res. Appl. 2, 7-11 (1977).
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
L1, a1, b1 = np.rollaxis(lab1, -1)[:3]
L2, a2, b2 = np.rollaxis(lab2, -1)[:3]
return np.sqrt((L2 - L1) ** 2 + (a2 - a1) ** 2 + (b2 - b1) ** 2)
def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015):
"""Color difference according to CIEDE 94 standard
Accommodates perceptual non-uniformities through the use of application
specific scale factors (`kH`, `kC`, `kL`, `k1`, and `k2`).
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
kH : float, optional
Hue scale
kC : float, optional
Chroma scale
kL : float, optional
Lightness scale
k1 : float, optional
first scale parameter
k2 : float, optional
second scale parameter
Returns
-------
dE : array_like
color difference between `lab1` and `lab2`
Notes
-----
deltaE_ciede94 is not symmetric with respect to lab1 and lab2. CIEDE94
defines the scales for the lightness, hue, and chroma in terms of the first
color. Consequently, the first color should be regarded as the "reference"
color.
`kL`, `k1`, `k2` depend on the application and default to the values
suggested for graphic arts
========== ============== ==========
Parameter Graphic Arts Textiles
========== ============== ==========
`kL` 1.000 2.000
`k1` 0.045 0.048
`k2` 0.015 0.014
========== ============== ==========
References
----------
.. [1] https://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
"""
L1, C1 = np.rollaxis(lab2lch(lab1), -1)[:2]
L2, C2 = np.rollaxis(lab2lch(lab2), -1)[:2]
dL = L1 - L2
dC = C1 - C2
dH2 = get_dH2(lab1, lab2)
SL = 1
SC = 1 + k1 * C1
SH = 1 + k2 * C1
dE2 = (dL / (kL * SL)) ** 2
dE2 += (dC / (kC * SC)) ** 2
dE2 += dH2 / (kH * SH) ** 2
return np.sqrt(np.maximum(dE2, 0))
def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1):
"""Color difference as given by the CIEDE 2000 standard.
CIEDE 2000 is a major revision of CIDE94. The perceptual calibration is
largely based on experience with automotive paint on smooth surfaces.
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
kL : float (range), optional
lightness scale factor, 1 for "acceptably close"; 2 for "imperceptible"
see deltaE_cmc
kC : float (range), optional
chroma scale factor, usually 1
kH : float (range), optional
hue scale factor, usually 1
Returns
-------
deltaE : array_like
The distance between `lab1` and `lab2`
Notes
-----
CIEDE 2000 assumes parametric weighting factors for the lightness, chroma,
and hue (`kL`, `kC`, `kH` respectively). These default to 1.
References
----------
.. [1] https://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
:DOI:`10.1364/AO.33.008069`
.. [3] M. Melgosa, J. Quesada, and E. Hita, "Uniformity of some recent
color metrics tested with an accurate color-difference tolerance
dataset," Appl. Opt. 33, 8069-8077 (1994).
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
unroll = False
if lab1.ndim == 1 and lab2.ndim == 1:
unroll = True
if lab1.ndim == 1:
lab1 = lab1[None, :]
if lab2.ndim == 1:
lab2 = lab2[None, :]
L1, a1, b1 = np.rollaxis(lab1, -1)[:3]
L2, a2, b2 = np.rollaxis(lab2, -1)[:3]
# distort `a` based on average chroma
# then convert to lch coordines from distorted `a`
# all subsequence calculations are in the new coordiantes
# (often denoted "prime" in the literature)
Cbar = 0.5 * (np.hypot(a1, b1) + np.hypot(a2, b2))
c7 = Cbar ** 7
G = 0.5 * (1 - np.sqrt(c7 / (c7 + 25 ** 7)))
scale = 1 + G
C1, h1 = _cart2polar_2pi(a1 * scale, b1)
C2, h2 = _cart2polar_2pi(a2 * scale, b2)
# recall that c, h are polar coordiantes. c==r, h==theta
# cide2000 has four terms to delta_e:
# 1) Luminance term
# 2) Hue term
# 3) Chroma term
# 4) hue Rotation term
# lightness term
Lbar = 0.5 * (L1 + L2)
tmp = (Lbar - 50) ** 2
SL = 1 + 0.015 * tmp / np.sqrt(20 + tmp)
L_term = (L2 - L1) / (kL * SL)
# chroma term
Cbar = 0.5 * (C1 + C2) # new coordiantes
SC = 1 + 0.045 * Cbar
C_term = (C2 - C1) / (kC * SC)
# hue term
h_diff = h2 - h1
h_sum = h1 + h2
CC = C1 * C2
dH = h_diff.copy()
dH[h_diff > np.pi] -= 2 * np.pi
dH[h_diff < -np.pi] += 2 * np.pi
dH[CC == 0.] = 0. # if r == 0, dtheta == 0
dH_term = 2 * np.sqrt(CC) * np.sin(dH / 2)
Hbar = h_sum.copy()
mask = np.logical_and(CC != 0., np.abs(h_diff) > np.pi)
Hbar[mask * (h_sum < 2 * np.pi)] += 2 * np.pi
Hbar[mask * (h_sum >= 2 * np.pi)] -= 2 * np.pi
Hbar[CC == 0.] *= 2
Hbar *= 0.5
T = (1 -
0.17 * np.cos(Hbar - np.deg2rad(30)) +
0.24 * np.cos(2 * Hbar) +
0.32 * np.cos(3 * Hbar + np.deg2rad(6)) -
0.20 * np.cos(4 * Hbar - np.deg2rad(63))
)
SH = 1 + 0.015 * Cbar * T
H_term = dH_term / (kH * SH)
# hue rotation
c7 = Cbar ** 7
Rc = 2 * np.sqrt(c7 / (c7 + 25 ** 7))
dtheta = np.deg2rad(30) * np.exp(-((np.rad2deg(Hbar) - 275) / 25) ** 2)
R_term = -np.sin(2 * dtheta) * Rc * C_term * H_term
# put it all together
dE2 = L_term ** 2
dE2 += C_term ** 2
dE2 += H_term ** 2
dE2 += R_term
ans = np.sqrt(np.maximum(dE2, 0))
if unroll:
ans = ans[0]
return ans
def deltaE_cmc(lab1, lab2, kL=1, kC=1):
"""Color difference from the CMC l:c standard.
This color difference was developed by the Colour Measurement Committee
(CMC) of the Society of Dyers and Colourists (United Kingdom). It is
intended for use in the textile industry.
The scale factors `kL`, `kC` set the weight given to differences in
lightness and chroma relative to differences in hue. The usual values are
``kL=2``, ``kC=1`` for "acceptability" and ``kL=1``, ``kC=1`` for
"imperceptibility". Colors with ``dE > 1`` are "different" for the given
scale factors.
Parameters
----------
lab1 : array_like
reference color (Lab colorspace)
lab2 : array_like
comparison color (Lab colorspace)
Returns
-------
dE : array_like
distance between colors `lab1` and `lab2`
Notes
-----
deltaE_cmc the defines the scales for the lightness, hue, and chroma
in terms of the first color. Consequently
``deltaE_cmc(lab1, lab2) != deltaE_cmc(lab2, lab1)``
References
----------
.. [1] https://en.wikipedia.org/wiki/Color_difference
.. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
.. [3] F. J. J. Clarke, R. McDonald, and B. Rigg, "Modification to the
JPC79 colour-difference formula," J. Soc. Dyers Colour. 100, 128-132
(1984).
"""
L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3]
L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3]
dC = C1 - C2
dL = L1 - L2
dH2 = get_dH2(lab1, lab2)
T = np.where(np.logical_and(np.rad2deg(h1) >= 164, np.rad2deg(h1) <= 345),
0.56 + 0.2 * np.abs(np.cos(h1 + np.deg2rad(168))),
0.36 + 0.4 * np.abs(np.cos(h1 + np.deg2rad(35)))
)
c1_4 = C1 ** 4
F = np.sqrt(c1_4 / (c1_4 + 1900))
SL = np.where(L1 < 16, 0.511, 0.040975 * L1 / (1. + 0.01765 * L1))
SC = 0.638 + 0.0638 * C1 / (1. + 0.0131 * C1)
SH = SC * (F * T + 1 - F)
dE2 = (dL / (kL * SL)) ** 2
dE2 += (dC / (kC * SC)) ** 2
dE2 += dH2 / (SH ** 2)
return np.sqrt(np.maximum(dE2, 0))
def get_dH2(lab1, lab2):
"""squared hue difference term occurring in deltaE_cmc and deltaE_ciede94
Despite its name, "dH" is not a simple difference of hue values. We avoid
working directly with the hue value, since differencing angles is
troublesome. The hue term is usually written as:
c1 = sqrt(a1**2 + b1**2)
c2 = sqrt(a2**2 + b2**2)
term = (a1-a2)**2 + (b1-b2)**2 - (c1-c2)**2
dH = sqrt(term)
However, this has poor roundoff properties when a or b is dominant.
Instead, ab is a vector with elements a and b. The same dH term can be
re-written as:
|ab1-ab2|**2 - (|ab1| - |ab2|)**2
and then simplified to:
2*|ab1|*|ab2| - 2*dot(ab1, ab2)
"""
lab1 = np.asarray(lab1)
lab2 = np.asarray(lab2)
a1, b1 = np.rollaxis(lab1, -1)[1:3]
a2, b2 = np.rollaxis(lab2, -1)[1:3]
# magnitude of (a, b) is the chroma
C1 = np.hypot(a1, b1)
C2 = np.hypot(a2, b2)
term = (C1 * C2) - (a1 * a2 + b1 * b2)
return 2 * term

View file

@ -0,0 +1,146 @@
aliceblue = (0.941, 0.973, 1)
antiquewhite = (0.98, 0.922, 0.843)
aqua = (0, 1, 1)
aquamarine = (0.498, 1, 0.831)
azure = (0.941, 1, 1)
beige = (0.961, 0.961, 0.863)
bisque = (1, 0.894, 0.769)
black = (0, 0, 0)
blanchedalmond = (1, 0.922, 0.804)
blue = (0, 0, 1)
blueviolet = (0.541, 0.169, 0.886)
brown = (0.647, 0.165, 0.165)
burlywood = (0.871, 0.722, 0.529)
cadetblue = (0.373, 0.62, 0.627)
chartreuse = (0.498, 1, 0)
chocolate = (0.824, 0.412, 0.118)
coral = (1, 0.498, 0.314)
cornflowerblue = (0.392, 0.584, 0.929)
cornsilk = (1, 0.973, 0.863)
crimson = (0.863, 0.0784, 0.235)
cyan = (0, 1, 1)
darkblue = (0, 0, 0.545)
darkcyan = (0, 0.545, 0.545)
darkgoldenrod = (0.722, 0.525, 0.0431)
darkgray = (0.663, 0.663, 0.663)
darkgreen = (0, 0.392, 0)
darkgrey = (0.663, 0.663, 0.663)
darkkhaki = (0.741, 0.718, 0.42)
darkmagenta = (0.545, 0, 0.545)
darkolivegreen = (0.333, 0.42, 0.184)
darkorange = (1, 0.549, 0)
darkorchid = (0.6, 0.196, 0.8)
darkred = (0.545, 0, 0)
darksalmon = (0.914, 0.588, 0.478)
darkseagreen = (0.561, 0.737, 0.561)
darkslateblue = (0.282, 0.239, 0.545)
darkslategray = (0.184, 0.31, 0.31)
darkslategrey = (0.184, 0.31, 0.31)
darkturquoise = (0, 0.808, 0.82)
darkviolet = (0.58, 0, 0.827)
deeppink = (1, 0.0784, 0.576)
deepskyblue = (0, 0.749, 1)
dimgray = (0.412, 0.412, 0.412)
dimgrey = (0.412, 0.412, 0.412)
dodgerblue = (0.118, 0.565, 1)
firebrick = (0.698, 0.133, 0.133)
floralwhite = (1, 0.98, 0.941)
forestgreen = (0.133, 0.545, 0.133)
fuchsia = (1, 0, 1)
gainsboro = (0.863, 0.863, 0.863)
ghostwhite = (0.973, 0.973, 1)
gold = (1, 0.843, 0)
goldenrod = (0.855, 0.647, 0.125)
gray = (0.502, 0.502, 0.502)
green = (0, 0.502, 0)
greenyellow = (0.678, 1, 0.184)
grey = (0.502, 0.502, 0.502)
honeydew = (0.941, 1, 0.941)
hotpink = (1, 0.412, 0.706)
indianred = (0.804, 0.361, 0.361)
indigo = (0.294, 0, 0.51)
ivory = (1, 1, 0.941)
khaki = (0.941, 0.902, 0.549)
lavender = (0.902, 0.902, 0.98)
lavenderblush = (1, 0.941, 0.961)
lawngreen = (0.486, 0.988, 0)
lemonchiffon = (1, 0.98, 0.804)
lightblue = (0.678, 0.847, 0.902)
lightcoral = (0.941, 0.502, 0.502)
lightcyan = (0.878, 1, 1)
lightgoldenrodyellow = (0.98, 0.98, 0.824)
lightgray = (0.827, 0.827, 0.827)
lightgreen = (0.565, 0.933, 0.565)
lightgrey = (0.827, 0.827, 0.827)
lightpink = (1, 0.714, 0.757)
lightsalmon = (1, 0.627, 0.478)
lightseagreen = (0.125, 0.698, 0.667)
lightskyblue = (0.529, 0.808, 0.98)
lightslategray = (0.467, 0.533, 0.6)
lightslategrey = (0.467, 0.533, 0.6)
lightsteelblue = (0.69, 0.769, 0.871)
lightyellow = (1, 1, 0.878)
lime = (0, 1, 0)
limegreen = (0.196, 0.804, 0.196)
linen = (0.98, 0.941, 0.902)
magenta = (1, 0, 1)
maroon = (0.502, 0, 0)
mediumaquamarine = (0.4, 0.804, 0.667)
mediumblue = (0, 0, 0.804)
mediumorchid = (0.729, 0.333, 0.827)
mediumpurple = (0.576, 0.439, 0.859)
mediumseagreen = (0.235, 0.702, 0.443)
mediumslateblue = (0.482, 0.408, 0.933)
mediumspringgreen = (0, 0.98, 0.604)
mediumturquoise = (0.282, 0.82, 0.8)
mediumvioletred = (0.78, 0.0824, 0.522)
midnightblue = (0.098, 0.098, 0.439)
mintcream = (0.961, 1, 0.98)
mistyrose = (1, 0.894, 0.882)
moccasin = (1, 0.894, 0.71)
navajowhite = (1, 0.871, 0.678)
navy = (0, 0, 0.502)
oldlace = (0.992, 0.961, 0.902)
olive = (0.502, 0.502, 0)
olivedrab = (0.42, 0.557, 0.137)
orange = (1, 0.647, 0)
orangered = (1, 0.271, 0)
orchid = (0.855, 0.439, 0.839)
palegoldenrod = (0.933, 0.91, 0.667)
palegreen = (0.596, 0.984, 0.596)
palevioletred = (0.686, 0.933, 0.933)
papayawhip = (1, 0.937, 0.835)
peachpuff = (1, 0.855, 0.725)
peru = (0.804, 0.522, 0.247)
pink = (1, 0.753, 0.796)
plum = (0.867, 0.627, 0.867)
powderblue = (0.69, 0.878, 0.902)
purple = (0.502, 0, 0.502)
red = (1, 0, 0)
rosybrown = (0.737, 0.561, 0.561)
royalblue = (0.255, 0.412, 0.882)
saddlebrown = (0.545, 0.271, 0.0745)
salmon = (0.98, 0.502, 0.447)
sandybrown = (0.98, 0.643, 0.376)
seagreen = (0.18, 0.545, 0.341)
seashell = (1, 0.961, 0.933)
sienna = (0.627, 0.322, 0.176)
silver = (0.753, 0.753, 0.753)
skyblue = (0.529, 0.808, 0.922)
slateblue = (0.416, 0.353, 0.804)
slategray = (0.439, 0.502, 0.565)
slategrey = (0.439, 0.502, 0.565)
snow = (1, 0.98, 0.98)
springgreen = (0, 1, 0.498)
steelblue = (0.275, 0.51, 0.706)
tan = (0.824, 0.706, 0.549)
teal = (0, 0.502, 0.502)
thistle = (0.847, 0.749, 0.847)
tomato = (1, 0.388, 0.278)
turquoise = (0.251, 0.878, 0.816)
violet = (0.933, 0.51, 0.933)
wheat = (0.961, 0.871, 0.702)
white = (1, 1, 1)
whitesmoke = (0.961, 0.961, 0.961)
yellow = (1, 1, 0)
yellowgreen = (0.604, 0.804, 0.196)

View file

@ -0,0 +1,9 @@
from ..._shared.testing import setup_test, teardown_test
def setup():
setup_test()
def teardown():
teardown_test()

View file

@ -0,0 +1,94 @@
from functools import partial
import numpy as np
from skimage import img_as_float, img_as_uint
from skimage import color, data, filters
from skimage.color.adapt_rgb import adapt_rgb, each_channel, hsv_value
# Down-sample image for quicker testing.
COLOR_IMAGE = data.astronaut()[::5, ::6]
GRAY_IMAGE = data.camera()[::5, ::5]
SIGMA = 3
smooth = partial(filters.gaussian, sigma=SIGMA)
assert_allclose = partial(np.testing.assert_allclose, atol=1e-8)
@adapt_rgb(each_channel)
def edges_each(image):
return filters.sobel(image)
@adapt_rgb(each_channel)
def smooth_each(image, sigma):
return filters.gaussian(image, sigma)
@adapt_rgb(each_channel)
def mask_each(image, mask):
result = image.copy()
result[mask] = 0
return result
@adapt_rgb(hsv_value)
def edges_hsv(image):
return filters.sobel(image)
@adapt_rgb(hsv_value)
def smooth_hsv(image, sigma):
return filters.gaussian(image, sigma)
@adapt_rgb(hsv_value)
def edges_hsv_uint(image):
return img_as_uint(filters.sobel(image))
def test_gray_scale_image():
# We don't need to test both `hsv_value` and `each_channel` since
# `adapt_rgb` is handling gray-scale inputs.
assert_allclose(edges_each(GRAY_IMAGE), filters.sobel(GRAY_IMAGE))
def test_each_channel():
filtered = edges_each(COLOR_IMAGE)
for i, channel in enumerate(np.rollaxis(filtered, axis=-1)):
expected = img_as_float(filters.sobel(COLOR_IMAGE[:, :, i]))
assert_allclose(channel, expected)
def test_each_channel_with_filter_argument():
filtered = smooth_each(COLOR_IMAGE, SIGMA)
for i, channel in enumerate(np.rollaxis(filtered, axis=-1)):
assert_allclose(channel, smooth(COLOR_IMAGE[:, :, i]))
def test_each_channel_with_asymmetric_kernel():
mask = np.triu(np.ones(COLOR_IMAGE.shape[:2], dtype=np.bool_))
mask_each(COLOR_IMAGE, mask)
def test_hsv_value():
filtered = edges_hsv(COLOR_IMAGE)
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
assert_allclose(color.rgb2hsv(filtered)[:, :, 2], filters.sobel(value))
def test_hsv_value_with_filter_argument():
filtered = smooth_hsv(COLOR_IMAGE, SIGMA)
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
assert_allclose(color.rgb2hsv(filtered)[:, :, 2], smooth(value))
def test_hsv_value_with_non_float_output():
# Since `rgb2hsv` returns a float image and the result of the filtered
# result is inserted into the HSV image, we want to make sure there isn't
# a dtype mismatch.
filtered = edges_hsv_uint(COLOR_IMAGE)
filtered_value = color.rgb2hsv(filtered)[:, :, 2]
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
# Reduce tolerance because dtype conversion.
assert_allclose(filtered_value, filters.sobel(value), rtol=1e-5, atol=1e-5)

View file

@ -0,0 +1,778 @@
"""Tests for color conversion functions.
Authors
-------
- the rgb2hsv test was written by Nicolas Pinto, 2009
- other tests written by Ralf Gommers, 2009
:license: modified BSD
"""
import numpy as np
import pytest
from skimage._shared.testing import assert_equal, assert_almost_equal
from skimage._shared.testing import assert_array_almost_equal, fetch
from skimage._shared.testing import TestCase
from skimage.util import img_as_float, img_as_ubyte, img_as_float32
from skimage.color import (rgb2hsv, hsv2rgb,
rgb2xyz, xyz2rgb,
rgb2hed, hed2rgb,
separate_stains,
combine_stains,
rgb2rgbcie, rgbcie2rgb,
convert_colorspace,
rgb2gray, gray2rgb,
xyz2lab, lab2xyz,
lab2rgb, rgb2lab,
xyz2luv, luv2xyz,
luv2rgb, rgb2luv,
lab2lch, lch2lab,
rgb2yuv, yuv2rgb,
rgb2yiq, yiq2rgb,
rgb2ypbpr, ypbpr2rgb,
rgb2ycbcr, ycbcr2rgb,
rgb2ydbdr, ydbdr2rgb,
rgba2rgb, gray2rgba)
from skimage._shared._warnings import expected_warnings
from skimage import data
import colorsys
class TestColorconv(TestCase):
img_rgb = data.colorwheel()
img_grayscale = data.camera()
img_rgba = np.array([[[0, 0.5, 1, 0],
[0, 0.5, 1, 1],
[0, 0.5, 1, 0.5]]]).astype(np.float)
colbars = np.array([[1, 1, 0, 0, 1, 1, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 0, 1, 0]]).astype(np.float)
colbars_array = np.swapaxes(colbars.reshape(3, 4, 2), 0, 2)
colbars_point75 = colbars * 0.75
colbars_point75_array = np.swapaxes(colbars_point75.reshape(3, 4, 2), 0, 2)
xyz_array = np.array([[[0.4124, 0.21260, 0.01930]], # red
[[0, 0, 0]], # black
[[.9505, 1., 1.089]], # white
[[.1805, .0722, .9505]], # blue
[[.07719, .15438, .02573]], # green
])
lab_array = np.array([[[53.233, 80.109, 67.220]], # red
[[0., 0., 0.]], # black
[[100.0, 0.005, -0.010]], # white
[[32.303, 79.197, -107.864]], # blue
[[46.229, -51.7, 49.898]], # green
])
luv_array = np.array([[[53.233, 175.053, 37.751]], # red
[[0., 0., 0.]], # black
[[100., 0.001, -0.017]], # white
[[32.303, -9.400, -130.358]], # blue
[[46.228, -43.774, 56.589]], # green
])
# RGBA to RGB
def test_rgba2rgb_conversion(self):
rgba = self.img_rgba
rgb = rgba2rgb(rgba)
expected = np.array([[[1, 1, 1],
[0, 0.5, 1],
[0.5, 0.75, 1]]]).astype(np.float)
self.assertEqual(rgb.shape, expected.shape)
assert_almost_equal(rgb, expected)
def test_rgba2rgb_error_grayscale(self):
self.assertRaises(ValueError, rgba2rgb, self.img_grayscale)
def test_rgba2rgb_error_rgb(self):
self.assertRaises(ValueError, rgba2rgb, self.img_rgb)
def test_rgba2rgb_dtype(self):
rgba = self.img_rgba.astype('float64')
rgba32 = img_as_float32(rgba)
assert rgba2rgb(rgba).dtype == rgba.dtype
assert rgba2rgb(rgba32).dtype == rgba32.dtype
# RGB to HSV
def test_rgb2hsv_conversion(self):
rgb = img_as_float(self.img_rgb)[::16, ::16]
hsv = rgb2hsv(rgb).reshape(-1, 3)
# ground truth from colorsys
gt = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2])
for pt in rgb.reshape(-1, 3)]
)
assert_almost_equal(hsv, gt)
def test_rgb2hsv_error_grayscale(self):
self.assertRaises(ValueError, rgb2hsv, self.img_grayscale)
def test_rgb2hsv_dtype(self):
rgb = img_as_float(self.img_rgb)
rgb32 = img_as_float32(self.img_rgb)
assert rgb2hsv(rgb).dtype == rgb.dtype
assert rgb2hsv(rgb32).dtype == rgb32.dtype
# HSV to RGB
def test_hsv2rgb_conversion(self):
rgb = self.img_rgb.astype("float32")[::16, ::16]
# create HSV image with colorsys
hsv = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2])
for pt in rgb.reshape(-1, 3)]).reshape(rgb.shape)
# convert back to RGB and compare with original.
# relative precision for RGB -> HSV roundtrip is about 1e-6
assert_almost_equal(rgb, hsv2rgb(hsv), decimal=4)
def test_hsv2rgb_error_grayscale(self):
self.assertRaises(ValueError, hsv2rgb, self.img_grayscale)
def test_hsv2rgb_dtype(self):
rgb = self.img_rgb.astype("float32")[::16, ::16]
# create HSV image with colorsys
hsv = np.array([colorsys.rgb_to_hsv(pt[0], pt[1], pt[2])
for pt in rgb.reshape(-1, 3)],
dtype='float64').reshape(rgb.shape)
hsv32 = hsv.astype('float32')
assert hsv2rgb(hsv).dtype == hsv.dtype
assert hsv2rgb(hsv32).dtype == hsv32.dtype
# RGB to XYZ
def test_rgb2xyz_conversion(self):
gt = np.array([[[0.950456, 1. , 1.088754],
[0.538003, 0.787329, 1.06942 ],
[0.592876, 0.28484 , 0.969561],
[0.180423, 0.072169, 0.950227]],
[[0.770033, 0.927831, 0.138527],
[0.35758 , 0.71516 , 0.119193],
[0.412453, 0.212671, 0.019334],
[0. , 0. , 0. ]]])
assert_almost_equal(rgb2xyz(self.colbars_array), gt)
# stop repeating the "raises" checks for all other functions that are
# implemented with color._convert()
def test_rgb2xyz_error_grayscale(self):
self.assertRaises(ValueError, rgb2xyz, self.img_grayscale)
def test_rgb2xyz_dtype(self):
img = self.colbars_array
img32 = img.astype('float32')
assert rgb2xyz(img).dtype == img.dtype
assert rgb2xyz(img32).dtype == img32.dtype
# XYZ to RGB
def test_xyz2rgb_conversion(self):
assert_almost_equal(xyz2rgb(rgb2xyz(self.colbars_array)),
self.colbars_array)
def test_xyz2rgb_dtype(self):
img = rgb2xyz(self.colbars_array)
img32 = img.astype('float32')
assert xyz2rgb(img).dtype == img.dtype
assert xyz2rgb(img32).dtype == img32.dtype
# 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<->HED roundtrip with ubyte image
def test_hed_rgb_roundtrip(self):
img_rgb = img_as_ubyte(self.img_rgb)
new = img_as_ubyte(hed2rgb(rgb2hed(img_rgb)))
assert_equal(new, img_rgb)
# RGB<->HED roundtrip with float image
def test_hed_rgb_float_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb)
# RGB<->HDX roundtrip with ubyte image
def test_hdx_rgb_roundtrip(self):
from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx
img_rgb = self.img_rgb
conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb),
rgb_from_hdx)
assert_equal(img_as_ubyte(conv), img_rgb)
# RGB<->HDX roundtrip with float image
def test_hdx_rgb_roundtrip_float(self):
from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx
img_rgb = img_as_float(self.img_rgb)
conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb),
rgb_from_hdx)
assert_array_almost_equal(conv, img_rgb)
# RGB to RGB CIE
def test_rgb2rgbcie_conversion(self):
gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574],
[ 0.01163224, 0.16649536, 0.18948516],
[ 0.12259182, 0.03308008, 0.17298223],
[-0.01466154, 0.01669446, 0.16969164]],
[[ 0.16354714, 0.16618652, 0.0230841 ],
[ 0.02629378, 0.1498009 , 0.01979351],
[ 0.13725336, 0.01638562, 0.00329059],
[ 0. , 0. , 0. ]]])
assert_almost_equal(rgb2rgbcie(self.colbars_array), gt)
def test_rgb2rgbcie_dtype(self):
img = self.colbars_array.astype('float64')
img32 = img.astype('float32')
assert rgb2rgbcie(img).dtype == img.dtype
assert rgb2rgbcie(img32).dtype == img32.dtype
# RGB CIE to RGB
def test_rgbcie2rgb_conversion(self):
# only roundtrip test, we checked rgb2rgbcie above already
assert_almost_equal(rgbcie2rgb(rgb2rgbcie(self.colbars_array)),
self.colbars_array)
def test_rgbcie2rgb_dtype(self):
img = rgb2rgbcie(self.colbars_array).astype('float64')
img32 = img.astype('float32')
assert rgbcie2rgb(img).dtype == img.dtype
assert rgbcie2rgb(img32).dtype == img32.dtype
def test_convert_colorspace(self):
colspaces = ['HSV', 'RGB CIE', 'XYZ', 'YCbCr', 'YPbPr', 'YDbDr']
colfuncs_from = [
hsv2rgb, rgbcie2rgb, xyz2rgb,
ycbcr2rgb, ypbpr2rgb, ydbdr2rgb
]
colfuncs_to = [
rgb2hsv, rgb2rgbcie, rgb2xyz,
rgb2ycbcr, rgb2ypbpr, rgb2ydbdr
]
assert_almost_equal(
convert_colorspace(self.colbars_array, 'RGB', 'RGB'),
self.colbars_array)
for i, space in enumerate(colspaces):
gt = colfuncs_from[i](self.colbars_array)
assert_almost_equal(
convert_colorspace(self.colbars_array, space, 'RGB'), gt)
gt = colfuncs_to[i](self.colbars_array)
assert_almost_equal(
convert_colorspace(self.colbars_array, 'RGB', space), gt)
self.assertRaises(ValueError, convert_colorspace,
self.colbars_array, 'nokey', 'XYZ')
self.assertRaises(ValueError, convert_colorspace,
self.colbars_array, 'RGB', 'nokey')
def test_rgb2gray(self):
x = np.array([1, 1, 1]).reshape((1, 1, 3)).astype(np.float)
g = rgb2gray(x)
assert_array_almost_equal(g, 1)
assert_equal(g.shape, (1, 1))
def test_rgb2gray_contiguous(self):
x = np.random.rand(10, 10, 3)
assert rgb2gray(x).flags["C_CONTIGUOUS"]
assert rgb2gray(x[:5, :5]).flags["C_CONTIGUOUS"]
def test_rgb2gray_alpha(self):
x = np.random.rand(10, 10, 4)
with expected_warnings(['Non RGB image conversion']):
assert rgb2gray(x).ndim == 2
def test_rgb2gray_on_gray(self):
with expected_warnings(['The behavior of rgb2gray will change']):
rgb2gray(np.random.rand(5, 5))
def test_rgb2gray_dtype(self):
img = np.random.rand(10, 10, 3).astype('float64')
img32 = img.astype('float32')
assert rgb2gray(img).dtype == img.dtype
assert rgb2gray(img32).dtype == img32.dtype
# test matrices for xyz2lab and lab2xyz generated using
# http://www.easyrgb.com/index.php?X=CALC
# Note: easyrgb website displays xyz*100
def test_xyz2lab(self):
assert_array_almost_equal(xyz2lab(self.xyz_array),
self.lab_array, decimal=3)
# Test the conversion with the rest of the illuminants.
for I in ["d50", "d55", "d65", "d75"]:
for obs in ["2", "10"]:
fname = "color/tests/data/lab_array_{0}_{1}.npy".format(I, obs)
lab_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(lab_array_I_obs,
xyz2lab(self.xyz_array, I, obs),
decimal=2)
for I in ["a", "e"]:
fname = "color/tests/data/lab_array_{0}_2.npy".format(I)
lab_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(lab_array_I_obs,
xyz2lab(self.xyz_array, I, "2"),
decimal=2)
def test_xyz2lab_dtype(self):
img = self.xyz_array.astype('float64')
img32 = img.astype('float32')
assert xyz2lab(img).dtype == img.dtype
assert xyz2lab(img32).dtype == img32.dtype
def test_lab2xyz(self):
assert_array_almost_equal(lab2xyz(self.lab_array),
self.xyz_array, decimal=3)
# Test the conversion with the rest of the illuminants.
for I in ["d50", "d55", "d65", "d75"]:
for obs in ["2", "10"]:
fname = "color/tests/data/lab_array_{0}_{1}.npy".format(I, obs)
lab_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(lab2xyz(lab_array_I_obs, I, obs),
self.xyz_array, decimal=3)
for I in ["a", "e"]:
fname = "lab_array_{0}_2.npy".format(I, obs)
lab_array_I_obs = np.load(fetch('color/tests/data/' + fname))
assert_array_almost_equal(lab2xyz(lab_array_I_obs, I, "2"),
self.xyz_array, decimal=3)
# And we include a call to test the exception handling in the code.
try:
xs = lab2xyz(lab_array_I_obs, "NaI", "2") # Not an illuminant
except ValueError:
pass
try:
xs = lab2xyz(lab_array_I_obs, "d50", "42") # Not a degree
except ValueError:
pass
def test_lab2xyz_dtype(self):
img = self.lab_array.astype('float64')
img32 = img.astype('float32')
assert lab2xyz(img).dtype == img.dtype
assert lab2xyz(img32).dtype == img32.dtype
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_rgb2lab_dtype(self):
img = self.colbars_array.astype('float64')
img32 = img.astype('float32')
assert rgb2lab(img).dtype == img.dtype
assert rgb2lab(img32).dtype == img32.dtype
def test_lab2rgb_dtype(self):
img = self.lab_array.astype('float64')
img32 = img.astype('float32')
assert lab2rgb(img).dtype == img.dtype
assert lab2rgb(img32).dtype == img32.dtype
# test matrices for xyz2luv and luv2xyz generated using
# http://www.easyrgb.com/index.php?X=CALC
# Note: easyrgb website displays xyz*100
def test_xyz2luv(self):
assert_array_almost_equal(xyz2luv(self.xyz_array),
self.luv_array, decimal=3)
# Test the conversion with the rest of the illuminants.
for I in ["d50", "d55", "d65", "d75"]:
for obs in ["2", "10"]:
fname = "color/tests/data/luv_array_{0}_{1}.npy".format(I, obs)
luv_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(luv_array_I_obs,
xyz2luv(self.xyz_array, I, obs),
decimal=2)
for I in ["a", "e"]:
fname = "color/tests/data/luv_array_{0}_2.npy".format(I)
luv_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(luv_array_I_obs,
xyz2luv(self.xyz_array, I, "2"),
decimal=2)
def test_xyz2luv_dtype(self):
img = self.xyz_array.astype('float64')
img32 = img.astype('float32')
assert xyz2luv(img).dtype == img.dtype
assert xyz2luv(img32).dtype == img32.dtype
def test_luv2xyz(self):
assert_array_almost_equal(luv2xyz(self.luv_array),
self.xyz_array, decimal=3)
# Test the conversion with the rest of the illuminants.
for I in ["d50", "d55", "d65", "d75"]:
for obs in ["2", "10"]:
fname = "color/tests/data/luv_array_{0}_{1}.npy".format(I, obs)
luv_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(luv2xyz(luv_array_I_obs, I, obs),
self.xyz_array, decimal=3)
for I in ["a", "e"]:
fname = "color/tests/data/luv_array_{0}_2.npy".format(I, obs)
luv_array_I_obs = np.load(fetch(fname))
assert_array_almost_equal(luv2xyz(luv_array_I_obs, I, "2"),
self.xyz_array, decimal=3)
def test_luv2xyz_dtype(self):
img = self.luv_array.astype('float64')
img32 = img.astype('float32')
assert luv2xyz(img).dtype == img.dtype
assert luv2xyz(img32).dtype == img32.dtype
def test_rgb2luv_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, 7.7056, 106.7866],
[91.1132, -70.4773, -15.2042],
[87.7347, -83.0776, 107.3985],
[60.3242, 84.0714, -108.6834],
[53.2408, 175.0151, 37.7564],
[32.2970, -9.4054, -130.3423],
[0, 0, 0]]).T
gt_array = np.swapaxes(gt_for_colbars.reshape(3, 4, 2), 0, 2)
assert_array_almost_equal(rgb2luv(self.colbars_array),
gt_array, decimal=2)
def test_rgb2luv_dtype(self):
img = self.colbars_array.astype('float64')
img32 = img.astype('float32')
assert rgb2luv(img).dtype == img.dtype
assert rgb2luv(img32).dtype == img32.dtype
def test_luv2rgb_dtype(self):
img = self.luv_array.astype('float64')
img32 = img.astype('float32')
assert luv2rgb(img).dtype == img.dtype
assert luv2rgb(img32).dtype == img32.dtype
def test_luv_rgb_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)
assert_array_almost_equal(luv2rgb(rgb2luv(img_rgb)), img_rgb)
def test_lab_rgb_outlier(self):
lab_array = np.ones((3, 1, 3))
lab_array[0] = [50, -12, 85]
lab_array[1] = [50, 12, -85]
lab_array[2] = [90, -4, -47]
rgb_array = np.array([[[0.501, 0.481, 0]],
[[0, 0.482, 1.]],
[[0.578, 0.914, 1.]],
])
assert_almost_equal(lab2rgb(lab_array), rgb_array, decimal=3)
def test_lab_full_gamut(self):
a, b = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100))
L = np.ones(a.shape)
lab = np.dstack((L, a, b))
for value in [0, 10, 20]:
lab[:, :, 0] = value
with expected_warnings(['Color data out of range']):
lab2xyz(lab)
def test_lab_lch_roundtrip(self):
rgb = img_as_float(self.img_rgb)
lab = rgb2lab(rgb)
lab2 = lch2lab(lab2lch(lab))
assert_array_almost_equal(lab2, lab)
def test_rgb_lch_roundtrip(self):
rgb = img_as_float(self.img_rgb)
lab = rgb2lab(rgb)
lch = lab2lch(lab)
lab2 = lch2lab(lch)
rgb2 = lab2rgb(lab2)
assert_array_almost_equal(rgb, rgb2)
def test_lab_lch_0d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch2 = lab2lch(lab0[None, None, :])
assert_array_almost_equal(lch0, lch2[0, 0, :])
def test_lab_lch_1d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch1 = lab2lch(lab0[None, :])
assert_array_almost_equal(lch0, lch1[0, :])
def test_lab_lch_3d(self):
lab0 = self._get_lab0()
lch0 = lab2lch(lab0)
lch3 = lab2lch(lab0[None, None, None, :])
assert_array_almost_equal(lch0, lch3[0, 0, 0, :])
def _get_lab0(self):
rgb = img_as_float(self.img_rgb[:1, :1, :])
return rgb2lab(rgb)[0, 0, :]
def test_yuv(self):
rgb = np.array([[[1.0, 1.0, 1.0]]])
assert_array_almost_equal(rgb2yuv(rgb), np.array([[[1, 0, 0]]]))
assert_array_almost_equal(rgb2yiq(rgb), np.array([[[1, 0, 0]]]))
assert_array_almost_equal(rgb2ypbpr(rgb), np.array([[[1, 0, 0]]]))
assert_array_almost_equal(rgb2ycbcr(rgb), np.array([[[235, 128, 128]]]))
assert_array_almost_equal(rgb2ydbdr(rgb), np.array([[[1, 0, 0]]]))
rgb = np.array([[[0.0, 1.0, 0.0]]])
assert_array_almost_equal(rgb2yuv(rgb), np.array([[[0.587, -0.28886916, -0.51496512]]]))
assert_array_almost_equal(rgb2yiq(rgb), np.array([[[0.587, -0.27455667, -0.52273617]]]))
assert_array_almost_equal(rgb2ypbpr(rgb), np.array([[[0.587, -0.331264, -0.418688]]]))
assert_array_almost_equal(rgb2ycbcr(rgb), np.array([[[144.553, 53.797, 34.214]]]))
assert_array_almost_equal(rgb2ydbdr(rgb), np.array([[[0.587, -0.883, 1.116]]]))
def test_yuv_roundtrip(self):
img_rgb = img_as_float(self.img_rgb)[::16, ::16]
assert_array_almost_equal(yuv2rgb(rgb2yuv(img_rgb)), img_rgb)
assert_array_almost_equal(yiq2rgb(rgb2yiq(img_rgb)), img_rgb)
assert_array_almost_equal(ypbpr2rgb(rgb2ypbpr(img_rgb)), img_rgb)
assert_array_almost_equal(ycbcr2rgb(rgb2ycbcr(img_rgb)), img_rgb)
assert_array_almost_equal(ydbdr2rgb(rgb2ydbdr(img_rgb)), img_rgb)
def test_rgb2yuv_dtype(self):
img = self.colbars_array.astype('float64')
img32 = img.astype('float32')
assert rgb2yuv(img).dtype == img.dtype
assert rgb2yuv(img32).dtype == img32.dtype
def test_yuv2rgb_dtype(self):
img = rgb2yuv(self.colbars_array).astype('float64')
img32 = img.astype('float32')
assert yuv2rgb(img).dtype == img.dtype
assert yuv2rgb(img32).dtype == img32.dtype
def test_rgb2yiq_conversion(self):
rgb = img_as_float(self.img_rgb)[::16, ::16]
yiq = rgb2yiq(rgb).reshape(-1, 3)
gt = np.array([colorsys.rgb_to_yiq(pt[0], pt[1], pt[2])
for pt in rgb.reshape(-1, 3)]
)
assert_almost_equal(yiq, gt, decimal=2)
def test_gray2rgb():
x = np.array([0, 0.5, 1])
w = gray2rgb(x)
expected_output = np.array([[ 0, 0, 0 ],
[ 0.5, 0.5, 0.5, ],
[ 1, 1, 1 ]])
assert_equal(w, expected_output)
x = x.reshape((3, 1))
y = gray2rgb(x)
assert_equal(y.shape, (3, 1, 3))
assert_equal(y.dtype, x.dtype)
assert_equal(y[..., 0], x)
assert_equal(y[0, 0, :], [0, 0, 0])
x = np.array([[0, 128, 255]], dtype=np.uint8)
z = gray2rgb(x)
assert_equal(z.shape, (1, 3, 3))
assert_equal(z[..., 0], x)
assert_equal(z[0, 1, :], [128, 128, 128])
def test_gray2rgb_rgb():
x = np.random.rand(5, 5, 4)
with expected_warnings(['Pass-through of possibly RGB images']):
y = gray2rgb(x)
assert_equal(x, y)
def test_gray2rgb_alpha():
x = np.random.random((5, 5, 4))
with expected_warnings(['Pass-through of possibly RGB images']):
assert_equal(gray2rgb(x, alpha=None).shape, (5, 5, 4))
with expected_warnings(['Pass-through of possibly RGB images',
'alpha argument is deprecated']):
assert_equal(gray2rgb(x, alpha=False).shape, (5, 5, 3))
with expected_warnings(['Pass-through of possibly RGB images',
'alpha argument is deprecated']):
assert_equal(gray2rgb(x, alpha=True).shape, (5, 5, 4))
x = np.random.random((5, 5, 3))
with expected_warnings(['Pass-through of possibly RGB images']):
assert_equal(gray2rgb(x, alpha=None).shape, (5, 5, 3))
with expected_warnings(['Pass-through of possibly RGB images',
'alpha argument is deprecated']):
assert_equal(gray2rgb(x, alpha=False).shape, (5, 5, 3))
with expected_warnings(['Pass-through of possibly RGB images',
'alpha argument is deprecated']):
assert_equal(gray2rgb(x, alpha=True).shape, (5, 5, 4))
with expected_warnings(['alpha argument is deprecated']):
assert_equal(gray2rgb(np.array([[1, 2], [3, 4.]]),
alpha=True)[0, 0, 3], 1)
with expected_warnings(['alpha argument is deprecated']):
assert_equal(gray2rgb(np.array([[1, 2], [3, 4]], dtype=np.uint8),
alpha=True)[0, 0, 3], 255)
@pytest.mark.parametrize("shape", [(5, 5), (5, 5, 4), (5, 4, 5, 4)])
def test_gray2rgba(shape):
# nD case
img = np.random.random(shape)
rgba = gray2rgba(img)
# Shape check
assert_equal(rgba.shape, shape + (4, ))
# dtype check
assert rgba.dtype == img.dtype
# RGB channels check
for channel in range(3):
assert_equal(rgba[..., channel], img)
# Alpha channel check
assert_equal(rgba[..., 3], 1.0)
def test_gray2rgba_dtype():
img_f64 = np.random.random((5, 5))
img_f32 = img_f64.astype('float32')
img_u8 = img_as_ubyte(img_f64)
img_int = img_u8.astype(int)
for img in [img_f64, img_f32, img_u8, img_int]:
assert gray2rgba(img).dtype == img.dtype
def test_gray2rgba_alpha():
img = np.random.random((5, 5))
img_u8 = img_as_ubyte(img)
# Default
alpha = None
rgba = gray2rgba(img, alpha)
assert_equal(rgba[..., :3], gray2rgb(img))
assert_equal(rgba[..., 3], 1.0)
# Scalar
alpha = 0.5
rgba = gray2rgba(img, alpha)
assert_equal(rgba[..., :3], gray2rgb(img))
assert_equal(rgba[..., 3], alpha)
# Array
alpha = np.random.random((5, 5))
rgba = gray2rgba(img, alpha)
assert_equal(rgba[..., :3], gray2rgb(img))
assert_equal(rgba[..., 3], alpha)
# Warning about alpha cast
alpha = 0.5
with expected_warnings(["alpha can't be safely cast to image dtype"]):
rgba = gray2rgba(img_u8, alpha)
assert_equal(rgba[..., :3], gray2rgb(img_u8))
# Invalid shape
alpha = np.random.random((5, 5, 1))
expected_err_msg = ("could not broadcast input array from shape (5,5,1) "
"into shape (5,5)")
with pytest.raises(ValueError) as err:
rgba = gray2rgba(img, alpha)
assert expected_err_msg == str(err.value)
@pytest.mark.parametrize("func", [rgb2gray, gray2rgb, gray2rgba])
@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3),
(4, 5, 4, 5, 3)]))
def test_nD_gray_conversion(func, shape):
img = np.random.rand(*shape)
out = func(img)
msg_list = []
if img.ndim == 3 and func == gray2rgb:
msg_list.append('Pass-through of possibly RGB images in gray2rgb')
elif img.ndim == 2 and func == rgb2gray:
msg_list.append('The behavior of rgb2gray will change')
with expected_warnings(msg_list):
out = func(img)
common_ndim = min(out.ndim, len(shape))
assert out.shape[:common_ndim] == shape[:common_ndim]
@pytest.mark.parametrize("func", [rgb2hsv, hsv2rgb,
rgb2xyz, xyz2rgb,
rgb2hed, hed2rgb,
rgb2rgbcie, rgbcie2rgb,
xyz2lab, lab2xyz,
lab2rgb, rgb2lab,
xyz2luv, luv2xyz,
luv2rgb, rgb2luv,
lab2lch, lch2lab,
rgb2yuv, yuv2rgb,
rgb2yiq, yiq2rgb,
rgb2ypbpr, ypbpr2rgb,
rgb2ycbcr, ycbcr2rgb,
rgb2ydbdr, ydbdr2rgb])
@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3),
(4, 5, 4, 5, 3)]))
def test_nD_color_conversion(func, shape):
img = np.random.rand(*shape)
out = func(img)
assert out.shape == img.shape
@pytest.mark.parametrize("shape", ([(4, ), (2, 4), (4, 5, 4), (5, 4, 5, 4),
(4, 5, 4, 5, 4)]))
def test_rgba2rgb_nD(shape):
img = np.random.rand(*shape)
out = rgba2rgb(img)
expected_shape = shape[:-1] + (3, )
assert out.shape == expected_shape

View file

@ -0,0 +1,200 @@
import itertools
import pytest
import numpy as np
from skimage.color.colorlabel import label2rgb
from skimage._shared import testing
from skimage._shared.testing import (assert_array_almost_equal,
assert_array_equal, assert_warns)
def test_deprecation_warning():
image = np.ones((3, 3))
label = np.ones((3, 3))
with pytest.warns(FutureWarning) as record:
label2rgb(image, label)
expected_msg = "The new recommended value"
assert str(record[0].message).startswith(expected_msg)
def test_shape_mismatch():
image = np.ones((3, 3))
label = np.ones((2, 2))
with testing.raises(ValueError):
label2rgb(image, label, bg_label=-1)
def test_wrong_kind():
label = np.ones((3, 3))
# Must not raise an error.
label2rgb(label, bg_label=-1)
# kind='foo' is wrong.
with testing.raises(ValueError):
label2rgb(label, kind='foo', bg_label=-1)
def test_uint_image():
img = np.random.randint(0, 255, (10, 10), dtype=np.uint8)
labels = np.zeros((10, 10), dtype=np.int64)
labels[1:3, 1:3]=1
labels[6:9, 6:9] = 2
output = label2rgb(labels, image=img, bg_label=0)
# Make sure that the output is made of floats and in the correct range
assert np.issubdtype(output.dtype, np.floating)
assert output.max() <= 1
def test_rgb():
image = np.ones((1, 3))
label = np.arange(3).reshape(1, -1)
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
# Set alphas just in case the defaults change
rgb = label2rgb(label, image=image, colors=colors, alpha=1,
image_alpha=1, bg_label=-1)
assert_array_almost_equal(rgb, [colors])
def test_alpha():
image = np.random.uniform(size=(3, 3))
label = np.random.randint(0, 9, size=(3, 3))
# If we set `alpha = 0`, then rgb should match image exactly.
rgb = label2rgb(label, image=image, alpha=0, image_alpha=1,
bg_label=-1)
assert_array_almost_equal(rgb[..., 0], image)
assert_array_almost_equal(rgb[..., 1], image)
assert_array_almost_equal(rgb[..., 2], image)
def test_no_input_image():
label = np.arange(3).reshape(1, -1)
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
rgb = label2rgb(label, colors=colors, bg_label=-1)
assert_array_almost_equal(rgb, [colors])
def test_image_alpha():
image = np.random.uniform(size=(1, 3))
label = np.arange(3).reshape(1, -1)
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
# If we set `image_alpha = 0`, then rgb should match label colors exactly.
rgb = label2rgb(label, image=image, colors=colors, alpha=1,
image_alpha=0, bg_label=-1)
assert_array_almost_equal(rgb, [colors])
def test_color_names():
image = np.ones((1, 3))
label = np.arange(3).reshape(1, -1)
cnames = ['red', 'lime', 'blue']
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
# Set alphas just in case the defaults change
rgb = label2rgb(label, image=image, colors=cnames, alpha=1,
image_alpha=1, bg_label=-1)
assert_array_almost_equal(rgb, [colors])
def test_bg_and_color_cycle():
image = np.zeros((1, 10)) # dummy image
label = np.arange(10).reshape(1, -1)
colors = [(1, 0, 0), (0, 0, 1)]
bg_color = (0, 0, 0)
rgb = label2rgb(label, image=image, bg_label=0, bg_color=bg_color,
colors=colors, alpha=1)
assert_array_almost_equal(rgb[0, 0], bg_color)
for pixel, color in zip(rgb[0, 1:], itertools.cycle(colors)):
assert_array_almost_equal(pixel, color)
def test_negative_labels():
labels = np.array([0, -1, -2, 0])
rout = np.array([(0., 0., 0.), (0., 0., 1.), (1., 0., 0.), (0., 0., 0.)])
assert_array_almost_equal(
rout, label2rgb(labels, bg_label=0, alpha=1, image_alpha=1))
def test_nonconsecutive():
labels = np.array([0, 2, 4, 0])
colors = [(1, 0, 0), (0, 0, 1)]
rout = np.array([(1., 0., 0.), (0., 0., 1.), (1., 0., 0.), (1., 0., 0.)])
assert_array_almost_equal(
rout, label2rgb(labels, colors=colors, alpha=1,
image_alpha=1, bg_label=-1))
def test_label_consistency():
"""Assert that the same labels map to the same colors."""
label_1 = np.arange(5).reshape(1, -1)
label_2 = np.array([0, 1])
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1)]
# Set alphas just in case the defaults change
rgb_1 = label2rgb(label_1, colors=colors, bg_label=-1)
rgb_2 = label2rgb(label_2, colors=colors, bg_label=-1)
for label_id in label_2.flat:
assert_array_almost_equal(rgb_1[label_1 == label_id],
rgb_2[label_2 == label_id])
def test_leave_labels_alone():
labels = np.array([-1, 0, 1])
labels_saved = labels.copy()
label2rgb(labels, bg_label=-1)
label2rgb(labels, bg_label=1)
assert_array_equal(labels, labels_saved)
def test_avg():
# label image
label_field = np.array([[1, 1, 1, 2],
[1, 2, 2, 2],
[3, 3, 4, 4]], dtype=np.uint8)
# color image
r = np.array([[1., 1., 0., 0.],
[0., 0., 1., 1.],
[0., 0., 0., 0.]])
g = np.array([[0., 0., 0., 1.],
[1., 1., 1., 0.],
[0., 0., 0., 0.]])
b = np.array([[0., 0., 0., 1.],
[0., 1., 1., 1.],
[0., 0., 1., 1.]])
image = np.dstack((r, g, b))
# reference label-colored image
rout = np.array([[0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5],
[0. , 0. , 0. , 0. ]])
gout = np.array([[0.25, 0.25, 0.25, 0.75],
[0.25, 0.75, 0.75, 0.75],
[0. , 0. , 0. , 0. ]])
bout = np.array([[0. , 0. , 0. , 1. ],
[0. , 1. , 1. , 1. ],
[0.0, 0.0, 1.0, 1.0]])
expected_out = np.dstack((rout, gout, bout))
# test standard averaging
out = label2rgb(label_field, image, kind='avg', bg_label=-1)
assert_array_equal(out, expected_out)
# test averaging with custom background value
out_bg = label2rgb(label_field, image, bg_label=2, bg_color=(0, 0, 0),
kind='avg')
expected_out_bg = expected_out.copy()
expected_out_bg[label_field == 2] = 0
assert_array_equal(out_bg, expected_out_bg)
# test default background color
out_bg = label2rgb(label_field, image, bg_label=2, kind='avg')
assert_array_equal(out_bg, expected_out_bg)
def test_negative_intensity():
labels = np.arange(100).reshape(10, 10)
image = np.full((10, 10), -1, dtype='float64')
assert_warns(UserWarning, label2rgb, labels, image, bg_label=-1)

View file

@ -0,0 +1,179 @@
"""Test for correctness of color distance functions"""
from os.path import abspath, dirname, join as pjoin
import numpy as np
from skimage._shared.testing import (
assert_allclose, assert_equal, assert_almost_equal, fetch
)
from skimage.color.delta_e import (
deltaE_cie76, deltaE_ciede94, deltaE_ciede2000, deltaE_cmc
)
def test_ciede2000_dE():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_ciede2000(lab1, lab2)
assert_allclose(dE2, data['dE'], rtol=1.e-4)
def load_ciede2000_data():
dtype = [('pair', int),
('1', int),
('L1', float),
('a1', float),
('b1', float),
('a1_prime', float),
('C1_prime', float),
('h1_prime', float),
('hbar_prime', float),
('G', float),
('T', float),
('SL', float),
('SC', float),
('SH', float),
('RT', float),
('dE', float),
('2', int),
('L2', float),
('a2', float),
('b2', float),
('a2_prime', float),
('C2_prime', float),
('h2_prime', float),
]
# note: ciede_test_data.txt contains several intermediate quantities
path = fetch('color/tests/ciede2000_test_data.txt')
return np.loadtxt(path, dtype=dtype)
def test_cie76():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_cie76(lab1, lab2)
oracle = np.array([
4.00106328, 6.31415011, 9.1776999, 2.06270077, 2.36957073,
2.91529271, 2.23606798, 2.23606798, 4.98000036, 4.9800004,
4.98000044, 4.98000049, 4.98000036, 4.9800004, 4.98000044,
3.53553391, 36.86800781, 31.91002977, 30.25309901, 27.40894015,
0.89242934, 0.7972, 0.8583065, 0.82982507, 3.1819238,
2.21334297, 1.53890382, 4.60630929, 6.58467989, 3.88641412,
1.50514845, 2.3237848, 0.94413208, 1.31910843
])
assert_allclose(dE2, oracle, rtol=1.e-8)
def test_ciede94():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_ciede94(lab1, lab2)
oracle = np.array([
1.39503887, 1.93410055, 2.45433566, 0.68449187, 0.6695627,
0.69194527, 2.23606798, 2.03163832, 4.80069441, 4.80069445,
4.80069449, 4.80069453, 4.80069441, 4.80069445, 4.80069449,
3.40774352, 34.6891632, 29.44137328, 27.91408781, 24.93766082,
0.82213163, 0.71658427, 0.8048753, 0.75284394, 1.39099471,
1.24808929, 1.29795787, 1.82045088, 2.55613309, 1.42491303,
1.41945261, 2.3225685, 0.93853308, 1.30654464
])
assert_allclose(dE2, oracle, rtol=1.e-8)
def test_cmc():
data = load_ciede2000_data()
N = len(data)
lab1 = np.zeros((N, 3))
lab1[:, 0] = data['L1']
lab1[:, 1] = data['a1']
lab1[:, 2] = data['b1']
lab2 = np.zeros((N, 3))
lab2[:, 0] = data['L2']
lab2[:, 1] = data['a2']
lab2[:, 2] = data['b2']
dE2 = deltaE_cmc(lab1, lab2)
oracle = np.array([
1.73873611, 2.49660844, 3.30494501, 0.85735576, 0.88332927,
0.97822692, 3.50480874, 2.87930032, 6.5783807, 6.57838075,
6.5783808, 6.57838086, 6.67492321, 6.67492326, 6.67492331,
4.66852997, 42.10875485, 39.45889064, 38.36005919, 33.93663807,
1.14400168, 1.00600419, 1.11302547, 1.05335328, 1.42822951,
1.2548143, 1.76838061, 2.02583367, 3.08695508, 1.74893533,
1.90095165, 1.70258148, 1.80317207, 2.44934417
])
assert_allclose(dE2, oracle, rtol=1.e-8)
# Equal or close colors make `delta_e.get_dH2` function to return
# negative values resulting in NaNs when passed to sqrt (see #1908
# issue on Github):
lab1 = lab2
expected = np.zeros_like(oracle)
assert_almost_equal(deltaE_cmc(lab1, lab2), expected, decimal=6)
lab2[0, 0] += np.finfo(float).eps
assert_almost_equal(deltaE_cmc(lab1, lab2), expected, decimal=6)
# Single item case:
lab1 = lab2 = np.array([0., 1.59607713, 0.87755709])
assert_equal(deltaE_cmc(lab1, lab2), 0)
lab2[0] += np.finfo(float).eps
assert_equal(deltaE_cmc(lab1, lab2), 0)
def test_single_color_cie76():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_cie76(lab1, lab2)
def test_single_color_ciede94():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_ciede94(lab1, lab2)
def test_single_color_ciede2000():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_ciede2000(lab1, lab2)
def test_single_color_cmc():
lab1 = (0.5, 0.5, 0.5)
lab2 = (0.4, 0.4, 0.4)
deltaE_cmc(lab1, lab2)

View file

@ -0,0 +1,6 @@
# List of files that pytest should ignore
collect_ignore = ["io/_plugins",]
try:
import visvis
except ImportError:
collect_ignore.append("measure/mc_meta/visual_test.py")

View file

@ -0,0 +1,9 @@
This directory contains sample data from scikit-image.
By default, it only contains a small subset of the entire dataset.
The full detaset can be downloaded by using the following commands from
a python console.
>>> from skimage.data import download_all
>>> download_all()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,57 @@
import numpy as np
def binary_blobs(length=512, blob_size_fraction=0.1, n_dim=2,
volume_fraction=0.5, seed=None):
"""
Generate synthetic binary image with several rounded blob-like objects.
Parameters
----------
length : int, optional
Linear size of output image.
blob_size_fraction : float, optional
Typical linear size of blob, as a fraction of ``length``, should be
smaller than 1.
n_dim : int, optional
Number of dimensions of output image.
volume_fraction : float, default 0.5
Fraction of image pixels covered by the blobs (where the output is 1).
Should be in [0, 1].
seed : int, optional
Seed to initialize the random number generator.
If `None`, a random seed from the operating system is used.
Returns
-------
blobs : ndarray of bools
Output binary image
Examples
--------
>>> from skimage import data
>>> data.binary_blobs(length=5, blob_size_fraction=0.2, seed=1)
array([[ True, False, True, True, True],
[ True, True, True, False, True],
[False, True, False, True, True],
[ True, False, False, True, True],
[ True, False, False, False, True]])
>>> blobs = data.binary_blobs(length=256, blob_size_fraction=0.1)
>>> # Finer structures
>>> blobs = data.binary_blobs(length=256, blob_size_fraction=0.05)
>>> # Blobs cover a smaller volume fraction of the image
>>> blobs = data.binary_blobs(length=256, volume_fraction=0.3)
"""
# filters is quite an expensive import since it imports all of scipy.signal
# We lazy import here
from ..filters import gaussian
rs = np.random.RandomState(seed)
shape = tuple([length] * n_dim)
mask = np.zeros(shape)
n_pts = max(int(1. / blob_size_fraction) ** n_dim, 1)
points = (length * rs.rand(n_dim, n_pts)).astype(np.int)
mask[tuple(indices for indices in points)] = 1
mask = gaussian(mask, sigma=0.25 * length * blob_size_fraction)
threshold = np.percentile(mask, 100 * (1 - volume_fraction))
return np.logical_not(mask < threshold)

View file

@ -0,0 +1,139 @@
# flake8: noqa
# This minimal dataset was available as part of
# scikit-image 0.15 and will be retained until
# further notice.
# Testing data and additional datasets should only
# be made available by pooch
legacy_datasets = [
'astronaut.png',
'brick.png',
'camera.png',
'chessboard_GRAY.png',
'chessboard_RGB.png',
'chelsea.png',
'clock_motion.png',
'coffee.png',
'coins.png',
'color.png',
'cell.png',
'grass.png',
'gravel.png',
'horse.png',
'hubble_deep_field.jpg',
'ihc.png',
'lbpcascade_frontalface_opencv.xml',
'lfw_subset.npy',
'logo.png',
'microaneurysms.png',
'moon.png',
'page.png',
'text.png',
'retina.jpg',
'rocket.jpg',
'phantom.png',
'motorcycle_disp.npz',
'motorcycle_left.png',
'motorcycle_right.png',
]
# Registry of datafiles that can be downloaded along with their SHA256 hashes
# To generate the SHA256 hash, use the command
# openssl sha256 filename
registry = {
"color/tests/data/lab_array_a_2.npy": "793d5981cbffceb14b5fb589f998a2b1acdb5ff9c14d364c8e9e8bd45a80b275",
"color/tests/data/lab_array_d50_10.npy": "42e2ff26cb10e2a98fcf1bc06c2483302ff4fabf971fe8d49b530f490b5d24c7",
"color/tests/data/lab_array_d50_2.npy": "4aa03b7018ff7276643d3c082123cf07304f9d8d898ae92a5756a86955de4faf",
"color/tests/data/lab_array_d55_10.npy": "ab4f21368b6d8351578ab093381c44b49ae87a6b7f25c11aa094b07f215eed7d",
"color/tests/data/lab_array_d55_2.npy": "0319723de4632a252bae828b7c96d038fb075a7df05beadfbad653da05efe372",
"color/tests/data/lab_array_d65_10.npy": "5cb9e9c384d2577aaf8b7d2d21ff5b505708b80605a2f59d10e89d22c3d308d2",
"color/tests/data/lab_array_d65_2.npy": "16e847160f7ba4f19806d8194ed44a6654c9367e5a2cb240aa6e7eece44a6649",
"color/tests/data/lab_array_d75_10.npy": "c2d3de5422c785c925926b0c6223aeaf50b9393619d1c30830190d433606cbe1",
"color/tests/data/lab_array_d75_2.npy": "c94d53da398d36e076471ff7e0dafcaffc64ce4ba33b4d04849c32d19c87494a",
"color/tests/data/lab_array_e_2.npy": "ac05f17a83961b020ceccbdd46bddc86943d43e678dabcc898caf4a1e4be6165",
"color/tests/data/luv_array_a_2.npy": "eaf05dc61f4a70ece367d5e751a14d42b7c397c7b1c2df4cfecec9ddf26e1c1a",
"color/tests/data/luv_array_d50_10.npy": "fe223db556222ce3a59198bed3a3324c2c719b8083fb84dc5b00f214b4773b16",
"color/tests/data/luv_array_d50_2.npy": "48e8989048904bdf2c3c1ada265c1c29c5eff60f02f848a25cde622982c84901",
"color/tests/data/luv_array_d55_10.npy": "d88d53d2bad230c2331442187712ec52ffdee62bf0f60b200c33411bfed76c60",
"color/tests/data/luv_array_d55_2.npy": "c761b40475df591ae9c0475d54ef712d067190ca4652efc6308b69080a652061",
"color/tests/data/luv_array_d65_10.npy": "41a5452ffac4d31dd579d9528e725432c60d77b5f505d801898d9401429c89bf",
"color/tests/data/luv_array_d65_2.npy": "962ce180132c6c11798cbc423b2b204d1d10187670f6eb5dec1058eaad301e0e",
"color/tests/data/luv_array_d75_10.npy": "e1cc70d56eb6789633d4c2a4059b9533f616a7c8592c9bd342403e41d72f45e4",
"color/tests/data/luv_array_d75_2.npy": "07db3bd59bd89de8e5ff62dad786fe5f4b299133495ba9bea30495b375133a98",
"color/tests/data/luv_array_e_2.npy": "41b1037d81b267305ffe9e8e97e0affa9fa54b18e60413b01b8f11861cb32213",
"color/tests/ciede2000_test_data.txt": "2e005c6f76ddfb7bbcc8f68490f1f7b4b4a2a4b06b36a80c985677a2799c0e40",
"data/astronaut.png": "88431cd9653ccd539741b555fb0a46b61558b301d4110412b5bc28b5e3ea6cb5",
"data/brick.png": "7966caf324f6ba843118d98f7a07746d22f6a343430add0233eca5f6eaaa8fcf",
"data/cell.png": "8d23a7fb81f7cc877cd09f330357fc7f595651306e84e17252f6e0a1b3f61515",
"data/camera.png": "361a6d56d22ee52289cd308d5461d090e06a56cb36007d8dfc3226cbe8aaa5db",
"data/chessboard_GRAY.png": "3e51870774515af4d07d820bd8827364c70839bf9b573c746e485095e893df90",
"data/chessboard_RGB.png": "1ac01eff2d4e50f4eda55a2ddecdc28a6576623a58d7a7ef84513c5cc19a0331",
"data/chelsea.png": "596aa1e7cb875eb79f437e310381d26b338a81c2da23439704a73c4651e8c4bb",
"data/clock_motion.png": "f029226b28b642e80113d86622e9b215ee067a0966feaf5e60604a1e05733955",
"data/coffee.png": "cc02f8ca188b167c775a7101b5d767d1e71792cf762c33d6fa15a4599b5a8de7",
"data/coins.png": "f8d773fc9cfa6f4d8e5942dc34d0a0788fcaed2a4fefbbed0aef5398d7ef4cba",
"data/color.png": "7d2df993de2b4fa2a78e04e5df8050f49a9c511aa75e59ab3bd56ac9c98aef7e",
"data/horse.png": "c7fb60789fe394c485f842291ea3b21e50d140f39d6dcb5fb9917cc178225455",
"data/grass.png": "b6b6022426b38936c43a4ac09635cd78af074e90f42ffa8227ac8b7452d39f89",
"data/hubble_deep_field.jpg": "3a19c5dd8a927a9334bb1229a6d63711b1c0c767fb27e2286e7c84a3e2c2f5f4",
"data/ihc.png": "f8dd1aa387ddd1f49d8ad13b50921b237df8e9b262606d258770687b0ef93cef",
"data/logo.png": "f2c57fe8af089f08b5ba523d95573c26e62904ac5967f4c8851b27d033690168",
"data/lfw_subset.npy": "9560ec2f5edfac01973f63a8a99d00053fecd11e21877e18038fbe500f8e872c",
"data/microaneurysms.png": "a1e1be59aa447f8ce082f7fa809997ab369a2b137cb6c4202abc647c7ccf6456",
"data/moon.png": "78739619d11f7eb9c165bb5d2efd4772cee557812ec847532dbb1d92ef71f577",
"data/motorcycle_left.png": "db18e9c4157617403c3537a6ba355dfeafe9a7eabb6b9b94cb33f6525dd49179",
"data/motorcycle_right.png": "5fc913ae870e42a4b662314bc904d1786bcad8e2f0b9b67dba5a229406357797",
"data/motorcycle_disp.npz": "2e49c8cebff3fa20359a0cc6880c82e1c03bbb106da81a177218281bc2f113d7",
"data/mssim_matlab_output.npz": "cc11a14bfa040c75b02db32282439f2e2e3e96779196c171498afaa70528ed7a",
"data/page.png": "341a6f0a61557662b02734a9b6e56ec33a915b2c41886b97509dedf2a43b47a3",
"data/phantom.png": "552ff698167aa402cceb17981130607a228a0a0aa7c519299eaa4d5f301ba36c",
"data/retina.jpg": "38a07f36f27f095e818aea7b96d34202c05176d30253c66733f2e00379e9e0e6",
"data/rocket.jpg": "c2dd0de7c538df8d111e479619b129464d0269d0ae5fd18ca91d33a7fdfea95c",
"data/gravel.png": "c48615b451bf1e606fbd72c0aa9f8cc0f068ab7111ef7d93bb9b0f2586440c12",
"data/text.png": "bd84aa3a6e3c9887850d45d606c96b2e59433fbef50338570b63c319e668e6d1",
"data/chessboard_GRAY_U16.tif": "9fd3392c5b6cbc5f686d8ff83eb57ef91d038ee0852ac26817e5ac99df4c7f45",
"data/chessboard_GRAY_U16B.tif": "b0a9270751f0fc340c90b8b615b62b88187b9ab5995942717566735d523cddb2",
"data/chessboard_GRAY_U8.npy": "71f394694b721e8a33760a355b3666c9b7d7fc1188ff96b3cd23c2a1d73a38d8",
"data/lbpcascade_frontalface_opencv.xml": "03097789a3dcbb0e40d20b9ef82537dbc3b670b6a7f2268d735470f22e003a91",
"data/astronaut_GRAY_hog_L1.npy": "5d8ab22b166d1dd49c12caeff9d178ed28132efea3852b952e9d75f7f7f94954",
"data/astronaut_GRAY_hog_L2-Hys.npy": "c4dd6e50d1129aada358311cf8880ce8c775f31e0e550fc322c16e43a96d56fe",
"data/rank_filter_tests.npz": "efaf5699630f4a53255e91681dc72a965acd4a8aa1f84671c686fb93e7df046d",
"data/multi.fits": "5c71a83436762a52b1925f2f0d83881af7765ed50aede155af2800e54bbd5040",
"data/simple.fits": "cd36087fdbb909b6ba506bbff6bcd4c5f4da3a41862608fbac5e8555ef53d40f",
"data/palette_color.png": "c4e817035fb9f7730fe95cff1da3866dea01728efc72b6e703d78f7ab9717bdd",
"data/palette_gray.png": "bace7f73783bf3ab3b7fdaf701707e4fa09f0dbd0ea72cf5b12ddc73d50b02a9",
"data/green_palette.png": "42d49d94be8f9bc76e50639d3701ed0484258721f6b0bd7f50bb1b9274a010f0",
"data/truncated.jpg": "4c226038acc78012d335efba29c6119a24444a886842182b7e18db378f4a557d",
"data/multipage.tif": "4da0ad0d3df4807a9847247d1b5e565b50d46481f643afb5c37c14802c78130f",
"data/multipage_rgb.tif": "1d23b844fd38dce0e2d06f30432817cdb85e52070d8f5460a2ba58aebf34a0de",
"data/no_time_for_that_tiny.gif": "20abe94ba9e45f18de416c5fbef8d1f57a499600be40f9a200fae246010eefce",
"data/foo3x5x4indexed.png": "48a64c25c6da000ffdb5fcc34ebafe9ba3b1c9b61d7984ea7ca6dc54f9312dfa",
"data/mssim_matlab_output.npz": "cc11a14bfa040c75b02db32282439f2e2e3e96779196c171498afaa70528ed7a",
"data/gray_morph_output.npz": "3012eb994e864e1dca1f66fada6b4375f84eac63658d049886b710488c2394d1",
"data/disk-matlab-output.npz": "8a39d5c866f6216d6a9c9166312aa4bbf4d18fab3d0dcd963c024985bde5856b",
"data/diamond-matlab-output.npz": "02fca68907e2b252b501dfe977eef71ae39fadaaa3702ebdc855195422ae1cc2",
"data/bw_text.png": "308c2b09f8975a69b212e103b18520e8cbb7a4eccfce0f757836cd371f1b9094",
"data/bw_text_skeleton.npy": "9ff4fc23c6a01497d7987f14e3a97cbcc39cce54b2b3b7ee33b84c1b661d0ae1",
"data/_blobs_3d_fiji_skeleton.tif": "5182a2a94f240528985b8d15ec2aebbd5ca3c6b9214beff1eb6099c431e12b7b",
"data/checker_bilevel.png": "2e207e486545874a2a3e69ba653b28fdef923157be9017559540e65d1bcb8e28",
"restoration/tests/camera_rl.npy": "d219834415dc7094580abd975abb28bc7a6fb5ab83366e92c61ccffa66ca54fd",
"restoration/tests/camera_unsup.npy": "6d911fd0028ee78add8c416553097f15c6c4e59723ea32bd828f71269b6ea240",
"restoration/tests/camera_unsup2.npy": "30e81718f3cac0fc00d84518ca75a3c0fb9b246bb7748a9e20ec0b44da33734d",
"restoration/tests/camera_wiener.npy": "71e7cab739d6d145a288ec85dd235a62ff34442ccd1488b08139bc809850772b",
"registration/tests/data/OriginalX-130Y130.png": "bf24a06d99ae131c97e582ef5e1cd0c648a8dad0caab31281f3564045492811f",
"registration/tests/data/OriginalX130Y130.png": "7fdd4c06d504fec35ee0703bd7ed2c08830b075a74c8506bae4a70d682f5a2db",
"registration/tests/data/OriginalX75Y75.png": "c5cd58893c93140df02896df80b13ecf432f5c86eeaaf8fb311aec52a65c7016",
"registration/tests/data/TransformedX-130Y130.png": "1cda90ed69c921eb7605b73b76d141cf4ea03fb8ce3336445ca08080e40d7375",
"registration/tests/data/TransformedX130Y130.png": "bb10c6ae3f91a313b0ac543efdb7ca69c4b95e55674c65a88472a6c4f4692a25",
"registration/tests/data/TransformedX75Y75.png": "a1e9ead5f8e4a0f604271e1f9c50e89baf53f068f1d19fab2876af4938e695ea",
"data/cells.tif": "2120cfe08e0396324793a10a905c9bbcb64b117215eb63b2c24b643e1600c8c9",
}
registry_urls = {
"data/cells.tif": "https://github.com/scikit-image/skimage-tutorials/raw/master/images/cells.tif",
}
legacy_registry = {
('data/' + filename): registry['data/' + filename]
for filename in legacy_datasets
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

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