This commit is contained in:
ton
2023-10-05 00:01:27 +07:00
parent 1541297f6d
commit 4a987d90c5
12169 changed files with 502 additions and 2656459 deletions

View File

@@ -1,62 +0,0 @@
"""Utilities to read and write images in various formats.
The following plug-ins are available:
"""
from .manage_plugins import *
from .sift import *
from .collection import *
from ._io import *
from ._image_stack import *
reset_plugins()
WRAP_LEN = 73
def _separator(char, lengths):
return [char * separator_length for separator_length in lengths]
def _format_plugin_info_table(info_table, column_lengths):
"""Add separators and column titles to plugin info table."""
info_table.insert(0, _separator('=', column_lengths))
info_table.insert(1, ('Plugin', 'Description'))
info_table.insert(2, _separator('-', column_lengths))
info_table.append(_separator('=', column_lengths))
def _update_doc(doc):
"""Add a list of plugins to the module docstring, formatted as
a ReStructuredText table.
"""
from textwrap import wrap
info_table = [(p, plugin_info(p).get('description', 'no description'))
for p in available_plugins if not p == 'test']
if len(info_table) > 0:
name_length = max([len(n) for (n, _) in info_table])
else:
name_length = 0
description_length = WRAP_LEN - 1 - name_length
column_lengths = [name_length, description_length]
_format_plugin_info_table(info_table, column_lengths)
for (name, plugin_description) in info_table:
description_lines = wrap(plugin_description, description_length)
name_column = [name]
name_column.extend(['' for _ in range(len(description_lines) - 1)])
for name, description in zip(name_column, description_lines):
doc += f"{name.ljust(name_length)} {description}\n"
doc = doc.strip()
return doc
if __doc__ is not None:
__doc__ = _update_doc(__doc__)

View File

@@ -1,35 +0,0 @@
import numpy as np
__all__ = ['image_stack', 'push', 'pop']
# Shared image queue
image_stack = []
def push(img):
"""Push an image onto the shared image stack.
Parameters
----------
img : ndarray
Image to push.
"""
if not isinstance(img, np.ndarray):
raise ValueError("Can only push ndarrays to the image stack.")
image_stack.append(img)
def pop():
"""Pop an image from the shared image stack.
Returns
-------
img : ndarray
Image popped from the stack.
"""
return image_stack.pop()

View File

@@ -1,209 +0,0 @@
import pathlib
import numpy as np
from .._shared.utils import warn
from ..exposure import is_low_contrast
from ..color.colorconv import rgb2gray, rgba2rgb
from ..io.manage_plugins import call_plugin
from .util import file_or_url_context
__all__ = ['imread', 'imsave', 'imshow', 'show',
'imread_collection', 'imshow_collection']
def imread(fname, as_gray=False, plugin=None, **plugin_args):
"""Load an image from file.
Parameters
----------
fname : str or pathlib.Path
Image file name, e.g. ``test.jpg`` or URL.
as_gray : bool, optional
If True, convert color images to gray-scale (64-bit floats).
Images that are already in gray-scale format are not converted.
plugin : str, optional
Name of plugin to use. By default, the different plugins are
tried (starting with imageio) until a suitable
candidate is found. If not given and fname is a tiff file, the
tifffile plugin will be used.
Other Parameters
----------------
plugin_args : keywords
Passed to the given plugin.
Returns
-------
img_array : ndarray
The different color bands/channels are stored in the
third dimension, such that a gray-image is MxN, an
RGB-image MxNx3 and an RGBA-image MxNx4.
"""
if isinstance(fname, pathlib.Path):
fname = str(fname.resolve())
if plugin is None and hasattr(fname, 'lower'):
if fname.lower().endswith(('.tiff', '.tif')):
plugin = 'tifffile'
with file_or_url_context(fname) as fname:
img = call_plugin('imread', fname, plugin=plugin, **plugin_args)
if not hasattr(img, 'ndim'):
return img
if img.ndim > 2:
if img.shape[-1] not in (3, 4) and img.shape[-3] in (3, 4):
img = np.swapaxes(img, -1, -3)
img = np.swapaxes(img, -2, -3)
if as_gray:
if img.shape[2] == 4:
img = rgba2rgb(img)
img = rgb2gray(img)
return img
def imread_collection(load_pattern, conserve_memory=True,
plugin=None, **plugin_args):
"""
Load a collection of images.
Parameters
----------
load_pattern : str or list
List of objects to load. These are usually filenames, but may
vary depending on the currently active plugin. See the docstring
for ``ImageCollection`` for the default behaviour of this parameter.
conserve_memory : bool, optional
If True, never keep more than one in memory at a specific
time. Otherwise, images will be cached once they are loaded.
Returns
-------
ic : ImageCollection
Collection of images.
Other Parameters
----------------
plugin_args : keywords
Passed to the given plugin.
"""
return call_plugin('imread_collection', load_pattern, conserve_memory,
plugin=plugin, **plugin_args)
def imsave(fname, arr, plugin=None, check_contrast=True, **plugin_args):
"""Save an image to file.
Parameters
----------
fname : str or pathlib.Path
Target filename.
arr : ndarray of shape (M,N) or (M,N,3) or (M,N,4)
Image data.
plugin : str, optional
Name of plugin to use. By default, the different plugins are
tried (starting with imageio) until a suitable
candidate is found. If not given and fname is a tiff file, the
tifffile plugin will be used.
check_contrast : bool, optional
Check for low contrast and print warning (default: True).
Other Parameters
----------------
plugin_args : keywords
Passed to the given plugin.
Notes
-----
When saving a JPEG, the compression ratio may be controlled using the
``quality`` keyword argument which is an integer with values in [1, 100]
where 1 is worst quality and smallest file size, and 100 is best quality
and largest file size (default 75). This is only available when using
the PIL and imageio plugins.
"""
if isinstance(fname, pathlib.Path):
fname = str(fname.resolve())
if plugin is None and hasattr(fname, 'lower'):
if fname.lower().endswith(('.tiff', '.tif')):
plugin = 'tifffile'
if arr.dtype == bool:
warn(f'{fname} is a boolean image: setting True to 255 and False to 0. '
'To silence this warning, please convert the image using '
'img_as_ubyte.', stacklevel=2)
arr = arr.astype('uint8') * 255
if check_contrast and is_low_contrast(arr):
warn(f'{fname} is a low contrast image')
return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
def imshow(arr, plugin=None, **plugin_args):
"""Display an image.
Parameters
----------
arr : ndarray or str
Image data or name of image file.
plugin : str
Name of plugin to use. By default, the different plugins are
tried (starting with imageio) until a suitable
candidate is found.
Other Parameters
----------------
plugin_args : keywords
Passed to the given plugin.
"""
if isinstance(arr, str):
arr = call_plugin('imread', arr, plugin=plugin)
return call_plugin('imshow', arr, plugin=plugin, **plugin_args)
def imshow_collection(ic, plugin=None, **plugin_args):
"""Display a collection of images.
Parameters
----------
ic : ImageCollection
Collection to display.
plugin : str
Name of plugin to use. By default, the different plugins are
tried until a suitable candidate is found.
Other Parameters
----------------
plugin_args : keywords
Passed to the given plugin.
"""
return call_plugin('imshow_collection', ic, plugin=plugin, **plugin_args)
def show():
'''Display pending images.
Launch the event loop of the current gui plugin, and display all
pending images, queued via `imshow`. This is required when using
`imshow` from non-interactive scripts.
A call to `show` will block execution of code until all windows
have been closed.
Examples
--------
>>> import skimage.io as io
>>> rng = np.random.default_rng()
>>> for i in range(4):
... ax_im = io.imshow(rng.random((50, 50)))
>>> io.show() # doctest: +SKIP
'''
return call_plugin('_app_show')

View File

@@ -1,3 +0,0 @@
[fits]
description = FITS image reading via PyFITS
provides = imread, imread_collection

View File

@@ -1,137 +0,0 @@
__all__ = ['imread', 'imread_collection']
import skimage.io as io
try:
from astropy.io import fits
except ImportError:
raise ImportError(
"Astropy could not be found. It is needed to read FITS files.\n"
"Please refer to https://www.astropy.org for installation\n"
"instructions.")
def imread(fname):
"""Load an image from a FITS file.
Parameters
----------
fname : string
Image file name, e.g. ``test.fits``.
Returns
-------
img_array : ndarray
Unlike plugins such as PIL, where different color bands/channels are
stored in the third dimension, FITS images are grayscale-only and can
be N-dimensional, so an array of the native FITS dimensionality is
returned, without color channels.
Currently if no image is found in the file, None will be returned
Notes
-----
Currently FITS ``imread()`` always returns the first image extension when
given a Multi-Extension FITS file; use ``imread_collection()`` (which does
lazy loading) to get all the extensions at once.
"""
with fits.open(fname) as hdulist:
# Iterate over FITS image extensions, ignoring any other extension types
# such as binary tables, and get the first image data array:
img_array = None
for hdu in hdulist:
if isinstance(hdu, fits.ImageHDU) or \
isinstance(hdu, fits.PrimaryHDU):
if hdu.data is not None:
img_array = hdu.data
break
return img_array
def imread_collection(load_pattern, conserve_memory=True):
"""Load a collection of images from one or more FITS files
Parameters
----------
load_pattern : str or list
List of extensions to load. Filename globbing is currently
unsupported.
conserve_memory : bool
If True, never keep more than one in memory at a specific
time. Otherwise, images will be cached once they are loaded.
Returns
-------
ic : ImageCollection
Collection of images.
"""
intype = type(load_pattern)
if intype is not list and intype is not str:
raise TypeError("Input must be a filename or list of filenames")
# Ensure we have a list, otherwise we'll end up iterating over the string:
if intype is not list:
load_pattern = [load_pattern]
# Generate a list of filename/extension pairs by opening the list of
# files and finding the image extensions in each one:
ext_list = []
for filename in load_pattern:
with fits.open(filename) as hdulist:
for n, hdu in zip(range(len(hdulist)), hdulist):
if isinstance(hdu, fits.ImageHDU) or \
isinstance(hdu, fits.PrimaryHDU):
# Ignore (primary) header units with no data (use '.size'
# rather than '.data' to avoid actually loading the image):
try:
data_size = hdu.size # size is int in Astropy 3.1.2
except TypeError:
data_size = hdu.size()
if data_size > 0:
ext_list.append((filename, n))
return io.ImageCollection(ext_list, load_func=FITSFactory,
conserve_memory=conserve_memory)
def FITSFactory(image_ext):
"""Load an image extension from a FITS file and return a NumPy array
Parameters
----------
image_ext : tuple
FITS extension to load, in the format ``(filename, ext_num)``.
The FITS ``(extname, extver)`` format is unsupported, since this
function is not called directly by the user and
``imread_collection()`` does the work of figuring out which
extensions need loading.
"""
# Expect a length-2 tuple with a filename as the first element:
if not isinstance(image_ext, tuple):
raise TypeError("Expected a tuple")
if len(image_ext) != 2:
raise ValueError("Expected a tuple of length 2")
filename = image_ext[0]
extnum = image_ext[1]
if type(filename) is not str or type(extnum) is not int:
raise ValueError("Expected a (filename, extension) tuple")
with fits.open(filename) as hdulist:
data = hdulist[extnum].data
if data is None:
raise RuntimeError(
f"Extension {extnum} of {filename} has no data"
)
return data

View File

@@ -1,3 +0,0 @@
[gdal]
description = Image reading via the GDAL Library (www.gdal.org)
provides = imread

View File

@@ -1,16 +0,0 @@
__all__ = ['imread']
try:
import osgeo.gdal as gdal
except ImportError:
raise ImportError("The GDAL Library could not be found. "
"Please refer to http://www.gdal.org/ "
"for further instructions.")
def imread(fname):
"""Load an image from file.
"""
ds = gdal.Open(fname)
return ds.ReadAsArray()

View File

@@ -1,3 +0,0 @@
[imageio]
description = Image reading via the ImageIO Library
provides = imread, imsave

View File

@@ -1,15 +0,0 @@
__all__ = ['imread', 'imsave']
from functools import wraps
import numpy as np
try:
# Try using the v2 API directly to avoid a warning from imageio >= 2.16.2
from imageio.v2 import imread as imageio_imread, imsave
except ImportError:
from imageio import imread as imageio_imread, imsave
@wraps(imageio_imread)
def imread(*args, **kwargs):
return np.asarray(imageio_imread(*args, **kwargs))

View File

@@ -1,3 +0,0 @@
[imread]
description = Image reading and writing via imread
provides = imread, imsave

View File

@@ -1,44 +0,0 @@
__all__ = ['imread', 'imsave']
from ...util.dtype import _convert
try:
import imread as _imread
except ImportError:
raise ImportError("Imread could not be found"
"Please refer to http://pypi.python.org/pypi/imread/ "
"for further instructions.")
def imread(fname, dtype=None):
"""Load an image from file.
Parameters
----------
fname : str
Name of input file
"""
im = _imread.imread(fname)
if dtype is not None:
im = _convert(im, dtype)
return im
def imsave(fname, arr, format_str=None):
"""Save an image to disk.
Parameters
----------
fname : str
Name of destination file.
arr : ndarray of uint8 or uint16
Array (image) to save.
format_str : str,optional
Format to save as.
Notes
-----
Currently, only 8-bit precision is supported.
"""
return _imread.imsave(fname, arr, formatstr=format_str)

View File

@@ -1,3 +0,0 @@
[matplotlib]
description = Display or save images using Matplotlib
provides = imshow, imread, imshow_collection, _app_show

View File

@@ -1,209 +0,0 @@
from collections import namedtuple
import numpy as np
from ...util import dtype as dtypes
from ...exposure import is_low_contrast
from ..._shared.utils import warn
from math import floor, ceil
_default_colormap = 'gray'
_nonstandard_colormap = 'viridis'
_diverging_colormap = 'RdBu'
ImageProperties = namedtuple('ImageProperties',
['signed', 'out_of_range_float',
'low_data_range', 'unsupported_dtype'])
def _get_image_properties(image):
"""Determine nonstandard properties of an input image.
Parameters
----------
image : array
The input image.
Returns
-------
ip : ImageProperties named tuple
The properties of the image:
- signed: whether the image has negative values.
- out_of_range_float: if the image has floating point data
outside of [-1, 1].
- low_data_range: if the image is in the standard image
range (e.g. [0, 1] for a floating point image) but its
data range would be too small to display with standard
image ranges.
- unsupported_dtype: if the image data type is not a
standard skimage type, e.g. ``numpy.uint64``.
"""
immin, immax = np.min(image), np.max(image)
imtype = image.dtype.type
try:
lo, hi = dtypes.dtype_range[imtype]
except KeyError:
lo, hi = immin, immax
signed = immin < 0
out_of_range_float = (np.issubdtype(image.dtype, np.floating) and
(immin < lo or immax > hi))
low_data_range = (immin != immax and
is_low_contrast(image))
unsupported_dtype = image.dtype not in dtypes._supported_types
return ImageProperties(signed, out_of_range_float,
low_data_range, unsupported_dtype)
def _raise_warnings(image_properties):
"""Raise the appropriate warning for each nonstandard image type.
Parameters
----------
image_properties : ImageProperties named tuple
The properties of the considered image.
"""
ip = image_properties
if ip.unsupported_dtype:
warn("Non-standard image type; displaying image with "
"stretched contrast.", stacklevel=3)
if ip.low_data_range:
warn("Low image data range; displaying image with "
"stretched contrast.", stacklevel=3)
if ip.out_of_range_float:
warn("Float image out of standard range; displaying "
"image with stretched contrast.", stacklevel=3)
def _get_display_range(image):
"""Return the display range for a given set of image properties.
Parameters
----------
image : array
The input image.
Returns
-------
lo, hi : same type as immin, immax
The display range to be used for the input image.
cmap : string
The name of the colormap to use.
"""
ip = _get_image_properties(image)
immin, immax = np.min(image), np.max(image)
if ip.signed:
magnitude = max(abs(immin), abs(immax))
lo, hi = -magnitude, magnitude
cmap = _diverging_colormap
elif any(ip):
_raise_warnings(ip)
lo, hi = immin, immax
cmap = _nonstandard_colormap
else:
lo = 0
imtype = image.dtype.type
hi = dtypes.dtype_range[imtype][1]
cmap = _default_colormap
return lo, hi, cmap
def imshow(image, ax=None, show_cbar=None, **kwargs):
"""Show the input image and return the current axes.
By default, the image is displayed in grayscale, rather than
the matplotlib default colormap.
Images are assumed to have standard range for their type. For
example, if a floating point image has values in [0, 0.5], the
most intense color will be gray50, not white.
If the image exceeds the standard range, or if the range is too
small to display, we fall back on displaying exactly the range of
the input image, along with a colorbar to clearly indicate that
this range transformation has occurred.
For signed images, we use a diverging colormap centered at 0.
Parameters
----------
image : array, shape (M, N[, 3])
The image to display.
ax : `matplotlib.axes.Axes`, optional
The axis to use for the image, defaults to plt.gca().
show_cbar : boolean, optional.
Whether to show the colorbar (used to override default behavior).
**kwargs : Keyword arguments
These are passed directly to `matplotlib.pyplot.imshow`.
Returns
-------
ax_im : `matplotlib.pyplot.AxesImage`
The `AxesImage` object returned by `plt.imshow`.
"""
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
lo, hi, cmap = _get_display_range(image)
kwargs.setdefault('interpolation', 'nearest')
kwargs.setdefault('cmap', cmap)
kwargs.setdefault('vmin', lo)
kwargs.setdefault('vmax', hi)
ax = ax or plt.gca()
ax_im = ax.imshow(image, **kwargs)
if (cmap != _default_colormap and show_cbar is not False) or show_cbar:
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(ax_im, cax=cax)
ax.get_figure().tight_layout()
return ax_im
def imshow_collection(ic, *args, **kwargs):
"""Display all images in the collection.
Returns
-------
fig : `matplotlib.figure.Figure`
The `Figure` object returned by `plt.subplots`.
"""
import matplotlib.pyplot as plt
if len(ic) < 1:
raise ValueError('Number of images to plot must be greater than 0')
# The target is to plot images on a grid with aspect ratio 4:3
num_images = len(ic)
# Two pairs of `nrows, ncols` are possible
k = (num_images * 12)**0.5
r1 = max(1, floor(k / 4))
r2 = ceil(k / 4)
c1 = ceil(num_images / r1)
c2 = ceil(num_images / r2)
# Select the one which is closer to 4:3
if abs(r1 / c1 - 0.75) < abs(r2 / c2 - 0.75):
nrows, ncols = r1, c1
else:
nrows, ncols = r2, c2
fig, axes = plt.subplots(nrows=nrows, ncols=ncols)
ax = np.asarray(axes).ravel()
for n, image in enumerate(ic):
ax[n].imshow(image, *args, **kwargs)
kwargs['ax'] = axes
return fig
def imread(*args, **kwargs):
import matplotlib.image
return matplotlib.image.imread(*args, **kwargs)
def _app_show():
from matplotlib.pyplot import show
show()

View File

@@ -1,3 +0,0 @@
[pil]
description = Image reading via the Python Imaging Library
provides = imread, imsave

View File

@@ -1,260 +0,0 @@
__all__ = ['imread', 'imsave']
import numpy as np
from PIL import Image
from ...util import img_as_ubyte, img_as_uint
def imread(fname, dtype=None, img_num=None, **kwargs):
"""Load an image from file.
Parameters
----------
fname : str or file
File name or file-like-object.
dtype : numpy dtype object or string specifier
Specifies data type of array elements.
img_num : int, optional
Specifies which image to read in a file with multiple images
(zero-indexed).
kwargs : keyword pairs, optional
Addition keyword arguments to pass through.
Notes
-----
Files are read using the Python Imaging Library.
See PIL docs [1]_ for a list of supported formats.
References
----------
.. [1] http://pillow.readthedocs.org/en/latest/handbook/image-file-formats.html
"""
if isinstance(fname, str):
with open(fname, 'rb') as f:
im = Image.open(f)
return pil_to_ndarray(im, dtype=dtype, img_num=img_num)
else:
im = Image.open(fname)
return pil_to_ndarray(im, dtype=dtype, img_num=img_num)
def pil_to_ndarray(image, dtype=None, img_num=None):
"""Import a PIL Image object to an ndarray, in memory.
Parameters
----------
Refer to ``imread``.
"""
try:
# this will raise an IOError if the file is not readable
image.getdata()[0]
except OSError as e:
site = "http://pillow.readthedocs.org/en/latest/installation.html#external-libraries"
pillow_error_message = str(e)
error_message = (f"Could not load '{image.filename}' \n"
f"Reason: '{pillow_error_message}'\n"
f"Please see documentation at: {site}")
raise ValueError(error_message)
frames = []
grayscale = None
i = 0
while 1:
try:
image.seek(i)
except EOFError:
break
frame = image
if img_num is not None and img_num != i:
image.getdata()[0]
i += 1
continue
if image.format == 'PNG' and image.mode == 'I' and dtype is None:
dtype = 'uint16'
if image.mode == 'P':
if grayscale is None:
grayscale = _palette_is_grayscale(image)
if grayscale:
frame = image.convert('L')
else:
if image.format == 'PNG' and 'transparency' in image.info:
frame = image.convert('RGBA')
else:
frame = image.convert('RGB')
elif image.mode == '1':
frame = image.convert('L')
elif 'A' in image.mode:
frame = image.convert('RGBA')
elif image.mode == 'CMYK':
frame = image.convert('RGB')
if image.mode.startswith('I;16'):
shape = image.size
dtype = '>u2' if image.mode.endswith('B') else '<u2'
if 'S' in image.mode:
dtype = dtype.replace('u', 'i')
frame = np.fromstring(frame.tobytes(), dtype)
frame.shape = shape[::-1]
else:
frame = np.array(frame, dtype=dtype)
frames.append(frame)
i += 1
if img_num is not None:
break
if hasattr(image, 'fp') and image.fp:
image.fp.close()
if img_num is None and len(frames) > 1:
return np.array(frames)
elif frames:
return frames[0]
elif img_num:
raise IndexError(f'Could not find image #{img_num}')
def _palette_is_grayscale(pil_image):
"""Return True if PIL image in palette mode is grayscale.
Parameters
----------
pil_image : PIL image
PIL Image that is in Palette mode.
Returns
-------
is_grayscale : bool
True if all colors in image palette are gray.
"""
if pil_image.mode != 'P':
raise ValueError('pil_image.mode must be equal to "P".')
# get palette as an array with R, G, B columns
# Starting in pillow 9.1 palettes may have less than 256 entries
palette = np.asarray(pil_image.getpalette()).reshape((-1, 3))
# Not all palette colors are used; unused colors have junk values.
start, stop = pil_image.getextrema()
valid_palette = palette[start:stop + 1]
# Image is grayscale if channel differences (R - G and G - B)
# are all zero.
return np.allclose(np.diff(valid_palette), 0)
def ndarray_to_pil(arr, format_str=None):
"""Export an ndarray to a PIL object.
Parameters
----------
Refer to ``imsave``.
"""
if arr.ndim == 3:
arr = img_as_ubyte(arr)
mode = {3: 'RGB', 4: 'RGBA'}[arr.shape[2]]
elif format_str in ['png', 'PNG']:
mode = 'I;16'
mode_base = 'I'
if arr.dtype.kind == 'f':
arr = img_as_uint(arr)
elif arr.max() < 256 and arr.min() >= 0:
arr = arr.astype(np.uint8)
mode = mode_base = 'L'
else:
arr = img_as_uint(arr)
else:
arr = img_as_ubyte(arr)
mode = 'L'
mode_base = 'L'
try:
array_buffer = arr.tobytes()
except AttributeError:
array_buffer = arr.tostring() # Numpy < 1.9
if arr.ndim == 2:
im = Image.new(mode_base, arr.T.shape)
try:
im.frombytes(array_buffer, 'raw', mode)
except AttributeError:
im.fromstring(array_buffer, 'raw', mode) # PIL 1.1.7
else:
image_shape = (arr.shape[1], arr.shape[0])
try:
im = Image.frombytes(mode, image_shape, array_buffer)
except AttributeError:
im = Image.fromstring(mode, image_shape, array_buffer) # PIL 1.1.7
return im
def imsave(fname, arr, format_str=None, **kwargs):
"""Save an image to disk.
Parameters
----------
fname : str or file-like object
Name of destination file.
arr : ndarray of uint8 or float
Array (image) to save. Arrays of data-type uint8 should have
values in [0, 255], whereas floating-point arrays must be
in [0, 1].
format_str: str
Format to save as, this is defaulted to PNG if using a file-like
object; this will be derived from the extension if fname is a string
kwargs: dict
Keyword arguments to the Pillow save function (or tifffile save
function, for Tiff files). These are format dependent. For example,
Pillow's JPEG save function supports an integer ``quality`` argument
with values in [1, 95], while TIFFFile supports a ``compress``
integer argument with values in [0, 9].
Notes
-----
Use the Python Imaging Library.
See PIL docs [1]_ for a list of other supported formats.
All images besides single channel PNGs are converted using `img_as_uint8`.
Single Channel PNGs have the following behavior:
- Integer values in [0, 255] and Boolean types -> img_as_uint8
- Floating point and other integers -> img_as_uint16
References
----------
.. [1] http://pillow.readthedocs.org/en/latest/handbook/image-file-formats.html
"""
# default to PNG if file-like object
if not isinstance(fname, str) and format_str is None:
format_str = "PNG"
# Check for png in filename
if (isinstance(fname, str)
and fname.lower().endswith(".png")):
format_str = "PNG"
arr = np.asanyarray(arr)
if arr.dtype.kind == 'b':
arr = arr.astype(np.uint8)
if arr.ndim not in (2, 3):
raise ValueError(f"Invalid shape for image array: {arr.shape}")
if arr.ndim == 3:
if arr.shape[2] not in (3, 4):
raise ValueError("Invalid number of channels in image array.")
img = ndarray_to_pil(arr, format_str=format_str)
img.save(fname, format=format_str, **kwargs)

View File

@@ -1,3 +0,0 @@
[simpleitk]
description = Image reading and writing via SimpleITK
provides = imread, imsave

View File

@@ -1,21 +0,0 @@
__all__ = ['imread', 'imsave']
try:
import SimpleITK as sitk
except ImportError:
raise ImportError("SimpleITK could not be found. "
"Please try "
" easy_install SimpleITK "
"or refer to "
" http://simpleitk.org/ "
"for further instructions.")
def imread(fname):
sitk_img = sitk.ReadImage(fname)
return sitk.GetArrayFromImage(sitk_img)
def imsave(fname, arr):
sitk_img = sitk.GetImageFromArray(arr, isVector=True)
sitk.WriteImage(sitk_img, fname)

View File

@@ -1,3 +0,0 @@
[tifffile]
description = Load and save TIFF and TIFF-based images using tifffile.py
provides = imread, imsave

View File

@@ -1,74 +0,0 @@
from tifffile import imread as tifffile_imread
from tifffile import imwrite as tifffile_imwrite
__all__ = ['imread', 'imsave']
def imsave(fname, arr, **kwargs):
"""Load a tiff image to file.
Parameters
----------
fname : str or file
File name or file-like object.
arr : ndarray
The array to write.
kwargs : keyword pairs, optional
Additional keyword arguments to pass through (see ``tifffile``'s
``imwrite`` function).
Notes
-----
Provided by the tifffile library [1]_, and supports many
advanced image types including multi-page and floating-point.
This implementation will set ``photometric='RGB'`` when writing if the first
or last axis of `arr` has length 3 or 4. To override this, explicitly
pass the ``photometric`` kwarg.
This implementation will set ``planarconfig='SEPARATE'`` when writing if the
first axis of arr has length 3 or 4. To override this, explicitly
specify the ``planarconfig`` kwarg.
References
----------
.. [1] https://pypi.org/project/tifffile/
"""
if arr.shape[0] in [3, 4]:
if 'planarconfig' not in kwargs:
kwargs['planarconfig'] = 'SEPARATE'
rgb = True
else:
rgb = arr.shape[-1] in [3, 4]
if rgb and 'photometric' not in kwargs:
kwargs['photometric'] = 'RGB'
return tifffile_imwrite(fname, arr, **kwargs)
def imread(fname, **kwargs):
"""Load a tiff image from file.
Parameters
----------
fname : str or file
File name or file-like-object.
kwargs : keyword pairs, optional
Additional keyword arguments to pass through (see ``tifffile``'s
``imread`` function).
Notes
-----
Provided by the tifffile library [1]_, and supports many
advanced image types including multi-page and floating point.
References
----------
.. [1] https://pypi.org/project/tifffile/
"""
if 'img_num' in kwargs:
kwargs['key'] = kwargs.pop('img_num')
return tifffile_imread(fname, **kwargs)

View File

@@ -1,481 +0,0 @@
"""Data structures to hold collections of images, with optional caching."""
import os
from glob import glob
import re
from collections.abc import Sequence
from copy import copy
import numpy as np
from PIL import Image
from tifffile import TiffFile
__all__ = ['MultiImage', 'ImageCollection', 'concatenate_images',
'imread_collection_wrapper']
def concatenate_images(ic):
"""Concatenate all images in the image collection into an array.
Parameters
----------
ic : an iterable of images
The images to be concatenated.
Returns
-------
array_cat : ndarray
An array having one more dimension than the images in `ic`.
See Also
--------
ImageCollection.concatenate, MultiImage.concatenate
Raises
------
ValueError
If images in `ic` don't have identical shapes.
Notes
-----
``concatenate_images`` receives any iterable object containing images,
including ImageCollection and MultiImage, and returns a NumPy array.
"""
all_images = [image[np.newaxis, ...] for image in ic]
try:
array_cat = np.concatenate(all_images)
except ValueError:
raise ValueError('Image dimensions must agree.')
return array_cat
def alphanumeric_key(s):
"""Convert string to list of strings and ints that gives intuitive sorting.
Parameters
----------
s : string
Returns
-------
k : a list of strings and ints
Examples
--------
>>> alphanumeric_key('z23a')
['z', 23, 'a']
>>> filenames = ['f9.10.png', 'e10.png', 'f9.9.png', 'f10.10.png',
... 'f10.9.png']
>>> sorted(filenames)
['e10.png', 'f10.10.png', 'f10.9.png', 'f9.10.png', 'f9.9.png']
>>> sorted(filenames, key=alphanumeric_key)
['e10.png', 'f9.9.png', 'f9.10.png', 'f10.9.png', 'f10.10.png']
"""
k = [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', s)]
return k
def _is_multipattern(input_pattern):
"""Helping function. Returns True if pattern contains a tuple, list, or a
string separated with os.pathsep."""
# Conditions to be accepted by ImageCollection:
has_str_ospathsep = (isinstance(input_pattern, str)
and os.pathsep in input_pattern)
not_a_string = not isinstance(input_pattern, str)
has_iterable = isinstance(input_pattern, Sequence)
has_strings = all(isinstance(pat, str) for pat in input_pattern)
is_multipattern = (has_str_ospathsep or
(not_a_string
and has_iterable
and has_strings))
return is_multipattern
class ImageCollection:
"""Load and manage a collection of image files.
Parameters
----------
load_pattern : str or list of str
Pattern string or list of strings to load. The filename path can be
absolute or relative.
conserve_memory : bool, optional
If True, `ImageCollection` does not keep more than one in memory at a
specific time. Otherwise, images will be cached once they are loaded.
Other parameters
----------------
load_func : callable
``imread`` by default. See notes below.
Attributes
----------
files : list of str
If a pattern string is given for `load_pattern`, this attribute
stores the expanded file list. Otherwise, this is equal to
`load_pattern`.
Notes
-----
Note that files are always returned in alphanumerical order. Also note
that slicing returns a new ImageCollection, *not* a view into the data.
ImageCollection can be modified to load images from an arbitrary
source by specifying a combination of `load_pattern` and
`load_func`. For an ImageCollection ``ic``, ``ic[5]`` uses
``load_func(load_pattern[5])`` to load the image.
Imagine, for example, an ImageCollection that loads every third
frame from a video file::
video_file = 'no_time_for_that_tiny.gif'
def vidread_step(f, step):
vid = imageio.get_reader(f)
seq = [v for v in vid.iter_data()]
return seq[::step]
ic = ImageCollection(video_file, load_func=vidread_step, step=3)
ic # is an ImageCollection object of length 1 because there is 1 file
x = ic[0] # calls vidread_step(video_file, step=3)
x[5] # is the sixth element of a list of length 8 (24 / 3)
Alternatively, if `load_func` is provided and `load_pattern` is a
sequence, an `ImageCollection` of corresponding length will be created,
and the individual images will be loaded by calling `load_func` with the
matching element of the `load_pattern` as its first argument. In this
case, the elements of the sequence do not need to be names of existing
files (or strings at all). For example, to create an `ImageCollection`
containing 500 images from a video::
class vidread_random:
def __init__ (self, f):
self.vid = imageio.get_reader(f)
def __call__ (self, frameno):
return self.vid.get_data(frameno)
ic = ImageCollection(range(500), load_func=vidread_random('movie.mp4'))
ic # is an ImageCollection object of length 500
Another use of `load_func` would be to convert all images to ``uint8``::
def imread_convert(f):
return imread(f).astype(np.uint8)
ic = ImageCollection('/tmp/*.png', load_func=imread_convert)
Examples
--------
>>> import imageio
>>> import skimage.io as io
>>> from skimage import data_dir
>>> coll = io.ImageCollection(data_dir + '/chess*.png')
>>> len(coll)
2
>>> coll[0].shape
(200, 200)
>>> image_col = io.ImageCollection(['/tmp/work/*.png', '/tmp/other/*.jpg'])
>>> class multiread:
... def __init__ (self, f):
... self.vid = imageio.get_reader(f)
... def __call__ (self, frameno):
... return self.vid.get_data(frameno)
...
>>> filename = data_dir + '/no_time_for_that_tiny.gif'
>>> image_col = io.ImageCollection(range(24), load_func=multiread(filename))
>>> len(image_col)
24
"""
def __init__(self, load_pattern, conserve_memory=True, load_func=None,
**load_func_kwargs):
"""Load and manage a collection of images."""
self._files = []
if _is_multipattern(load_pattern):
if isinstance(load_pattern, str):
load_pattern = load_pattern.split(os.pathsep)
for pattern in load_pattern:
self._files.extend(glob(pattern))
self._files = sorted(self._files, key=alphanumeric_key)
elif isinstance(load_pattern, str):
self._files.extend(glob(load_pattern))
self._files = sorted(self._files, key=alphanumeric_key)
elif isinstance(load_pattern, Sequence) and load_func is not None:
self._files = list(load_pattern)
else:
raise TypeError('Invalid pattern as input.')
if load_func is None:
from ._io import imread
self.load_func = imread
self._numframes = self._find_images()
else:
self.load_func = load_func
self._numframes = len(self._files)
self._frame_index = None
if conserve_memory:
memory_slots = 1
else:
memory_slots = self._numframes
self._conserve_memory = conserve_memory
self._cached = None
self.load_func_kwargs = load_func_kwargs
self.data = np.empty(memory_slots, dtype=object)
@property
def files(self):
return self._files
@property
def conserve_memory(self):
return self._conserve_memory
def _find_images(self):
index = []
for fname in self._files:
if fname.lower().endswith(('.tiff', '.tif')):
with open(fname, 'rb') as f:
img = TiffFile(f)
index += [(fname, i) for i in range(len(img.pages))]
else:
try:
im = Image.open(fname)
im.seek(0)
except OSError:
continue
i = 0
while True:
try:
im.seek(i)
except EOFError:
break
index.append((fname, i))
i += 1
if hasattr(im, 'fp') and im.fp:
im.fp.close()
self._frame_index = index
return len(index)
def __getitem__(self, n):
"""Return selected image(s) in the collection.
Loading is done on demand.
Parameters
----------
n : int or slice
The image number to be returned, or a slice selecting the images
and ordering to be returned in a new ImageCollection.
Returns
-------
img : ndarray or ImageCollection.
The `n`-th image in the collection, or a new ImageCollection with
the selected images.
"""
if hasattr(n, '__index__'):
n = n.__index__()
if type(n) not in [int, slice]:
raise TypeError('slicing must be with an int or slice object')
if type(n) is int:
n = self._check_imgnum(n)
idx = n % len(self.data)
if ((self.conserve_memory and n != self._cached) or
(self.data[idx] is None)):
kwargs = self.load_func_kwargs
if self._frame_index:
fname, img_num = self._frame_index[n]
if img_num is not None:
kwargs['img_num'] = img_num
try:
self.data[idx] = self.load_func(fname, **kwargs)
# Account for functions that do not accept an img_num kwarg
except TypeError as e:
if "unexpected keyword argument 'img_num'" in str(e):
del kwargs['img_num']
self.data[idx] = self.load_func(fname, **kwargs)
else:
raise
else:
self.data[idx] = self.load_func(self.files[n], **kwargs)
self._cached = n
return self.data[idx]
else:
# A slice object was provided, so create a new ImageCollection
# object. Any loaded image data in the original ImageCollection
# will be copied by reference to the new object. Image data
# loaded after this creation is not linked.
fidx = range(self._numframes)[n]
new_ic = copy(self)
if self._frame_index:
new_ic._files = [self._frame_index[i][0] for i in fidx]
new_ic._frame_index = [self._frame_index[i] for i in fidx]
else:
new_ic._files = [self._files[i] for i in fidx]
new_ic._numframes = len(fidx)
if self.conserve_memory:
if self._cached in fidx:
new_ic._cached = fidx.index(self._cached)
new_ic.data = np.copy(self.data)
else:
new_ic.data = np.empty(1, dtype=object)
else:
new_ic.data = self.data[fidx]
return new_ic
def _check_imgnum(self, n):
"""Check that the given image number is valid."""
num = self._numframes
if -num <= n < num:
n = n % num
else:
raise IndexError(f"There are only {num} images in the collection")
return n
def __iter__(self):
"""Iterate over the images."""
for i in range(len(self)):
yield self[i]
def __len__(self):
"""Number of images in collection."""
return self._numframes
def __str__(self):
return str(self.files)
def reload(self, n=None):
"""Clear the image cache.
Parameters
----------
n : None or int
Clear the cache for this image only. By default, the
entire cache is erased.
"""
self.data = np.empty_like(self.data)
def concatenate(self):
"""Concatenate all images in the collection into an array.
Returns
-------
ar : np.ndarray
An array having one more dimension than the images in `self`.
See Also
--------
concatenate_images
Raises
------
ValueError
If images in the `ImageCollection` don't have identical shapes.
"""
return concatenate_images(self)
def imread_collection_wrapper(imread):
def imread_collection(load_pattern, conserve_memory=True):
"""Return an `ImageCollection` from files matching the given pattern.
Note that files are always stored in alphabetical order. Also note that
slicing returns a new ImageCollection, *not* a view into the data.
See `skimage.io.ImageCollection` for details.
Parameters
----------
load_pattern : str or list
Pattern glob or filenames to load. The path can be absolute or
relative. Multiple patterns should be separated by a colon,
e.g. ``/tmp/work/*.png:/tmp/other/*.jpg``. Also see
implementation notes below.
conserve_memory : bool, optional
If True, never keep more than one in memory at a specific
time. Otherwise, images will be cached once they are loaded.
"""
return ImageCollection(load_pattern, conserve_memory=conserve_memory,
load_func=imread)
return imread_collection
class MultiImage(ImageCollection):
"""A class containing all frames from multi-frame TIFF images.
Parameters
----------
load_pattern : str or list of str
Pattern glob or filenames to load. The path can be absolute or
relative.
conserve_memory : bool, optional
Whether to conserve memory by only caching the frames of a single
image. Default is True.
Notes
-----
`MultiImage` returns a list of image-data arrays. In this
regard, it is very similar to `ImageCollection`, but the two differ in
their treatment of multi-frame images.
For a TIFF image containing N frames of size WxH, `MultiImage` stores
all frames of that image as a single element of shape `(N, W, H)` in the
list. `ImageCollection` instead creates N elements of shape `(W, H)`.
For an animated GIF image, `MultiImage` reads only the first frame, while
`ImageCollection` reads all frames by default.
Examples
--------
>>> from skimage import data_dir
>>> multipage_tiff = data_dir + '/multipage.tif'
>>> multi_img = MultiImage(multipage_tiff)
>>> len(multi_img) # multi_img contains one element
1
>>> multi_img[0].shape # this element is a two-frame image of shape:
(2, 15, 10)
>>> image_col = ImageCollection(multipage_tiff)
>>> len(image_col) # image_col contains two elements
2
>>> for frame in image_col:
... print(frame.shape) # each element is a frame of shape (15, 10)
...
(15, 10)
(15, 10)
"""
def __init__(self, filename, conserve_memory=True, dtype=None,
**imread_kwargs):
"""Load a multi-img."""
from ._io import imread
self._filename = filename
super().__init__(filename, conserve_memory,
load_func=imread, **imread_kwargs)
@property
def filename(self):
return self._filename

View File

@@ -1,340 +0,0 @@
"""Handle image reading, writing and plotting plugins.
To improve performance, plugins are only loaded as needed. As a result, there
can be multiple states for a given plugin:
available: Defined in an *ini file located in `skimage.io._plugins`.
See also `skimage.io.available_plugins`.
partial definition: Specified in an *ini file, but not defined in the
corresponding plugin module. This will raise an error when loaded.
available but not on this system: Defined in `skimage.io._plugins`, but
a dependent library (e.g. Qt, PIL) is not available on your system.
This will raise an error when loaded.
loaded: The real availability is determined when it's explicitly loaded,
either because it's one of the default plugins, or because it's
loaded explicitly by the user.
"""
import os.path
import warnings
from configparser import ConfigParser
from glob import glob
from .collection import imread_collection_wrapper
__all__ = ['use_plugin', 'call_plugin', 'plugin_info', 'plugin_order',
'reset_plugins', 'find_available_plugins', 'available_plugins']
# The plugin store will save a list of *loaded* io functions for each io type
# (e.g. 'imread', 'imsave', etc.). Plugins are loaded as requested.
plugin_store = None
# Dictionary mapping plugin names to a list of functions they provide.
plugin_provides = {}
# The module names for the plugins in `skimage.io._plugins`.
plugin_module_name = {}
# Meta-data about plugins provided by *.ini files.
plugin_meta_data = {}
# For each plugin type, default to the first available plugin as defined by
# the following preferences.
preferred_plugins = {
# Default plugins for all types (overridden by specific types below).
'all': ['imageio', 'pil', 'matplotlib'],
'imshow': ['matplotlib'],
'imshow_collection': ['matplotlib']
}
def _clear_plugins():
"""Clear the plugin state to the default, i.e., where no plugins are loaded
"""
global plugin_store
plugin_store = {'imread': [],
'imsave': [],
'imshow': [],
'imread_collection': [],
'imshow_collection': [],
'_app_show': []}
_clear_plugins()
def _load_preferred_plugins():
# Load preferred plugin for each io function.
io_types = ['imsave', 'imshow', 'imread_collection', 'imshow_collection',
'imread']
for p_type in io_types:
_set_plugin(p_type, preferred_plugins['all'])
plugin_types = (p for p in preferred_plugins.keys() if p != 'all')
for p_type in plugin_types:
_set_plugin(p_type, preferred_plugins[p_type])
def _set_plugin(plugin_type, plugin_list):
for plugin in plugin_list:
if plugin not in available_plugins:
continue
try:
use_plugin(plugin, kind=plugin_type)
break
except (ImportError, RuntimeError, OSError):
pass
def reset_plugins():
_clear_plugins()
_load_preferred_plugins()
def _parse_config_file(filename):
"""Return plugin name and meta-data dict from plugin config file."""
parser = ConfigParser()
parser.read(filename)
name = parser.sections()[0]
meta_data = {}
for opt in parser.options(name):
meta_data[opt] = parser.get(name, opt)
return name, meta_data
def _scan_plugins():
"""Scan the plugins directory for .ini files and parse them
to gather plugin meta-data.
"""
pd = os.path.dirname(__file__)
config_files = glob(os.path.join(pd, '_plugins', '*.ini'))
for filename in config_files:
name, meta_data = _parse_config_file(filename)
if 'provides' not in meta_data:
warnings.warn(f'file {filename} not recognized as a scikit-image io plugin, skipping.')
continue
plugin_meta_data[name] = meta_data
provides = [s.strip() for s in meta_data['provides'].split(',')]
valid_provides = [p for p in provides if p in plugin_store]
for p in provides:
if p not in plugin_store:
print(f"Plugin `{name}` wants to provide non-existent `{p}`. Ignoring.")
# Add plugins that provide 'imread' as provider of 'imread_collection'.
need_to_add_collection = ('imread_collection' not in valid_provides and
'imread' in valid_provides)
if need_to_add_collection:
valid_provides.append('imread_collection')
plugin_provides[name] = valid_provides
plugin_module_name[name] = os.path.basename(filename)[:-4]
_scan_plugins()
def find_available_plugins(loaded=False):
"""List available plugins.
Parameters
----------
loaded : bool
If True, show only those plugins currently loaded. By default,
all plugins are shown.
Returns
-------
p : dict
Dictionary with plugin names as keys and exposed functions as
values.
"""
active_plugins = set()
for plugin_func in plugin_store.values():
for plugin, func in plugin_func:
active_plugins.add(plugin)
d = {}
for plugin in plugin_provides:
if not loaded or plugin in active_plugins:
d[plugin] = [f for f in plugin_provides[plugin]
if not f.startswith('_')]
return d
available_plugins = find_available_plugins()
def call_plugin(kind, *args, **kwargs):
"""Find the appropriate plugin of 'kind' and execute it.
Parameters
----------
kind : {'imshow', 'imsave', 'imread', 'imread_collection'}
Function to look up.
plugin : str, optional
Plugin to load. Defaults to None, in which case the first
matching plugin is used.
*args, **kwargs : arguments and keyword arguments
Passed to the plugin function.
"""
if kind not in plugin_store:
raise ValueError(f'Invalid function ({kind}) requested.')
plugin_funcs = plugin_store[kind]
if len(plugin_funcs) == 0:
msg = (f"No suitable plugin registered for {kind}.\n\n"
"You may load I/O plugins with the `skimage.io.use_plugin` "
"command. A list of all available plugins are shown in the "
"`skimage.io` docstring.")
raise RuntimeError(msg)
plugin = kwargs.pop('plugin', None)
if plugin is None:
_, func = plugin_funcs[0]
else:
_load(plugin)
try:
func = [f for (p, f) in plugin_funcs if p == plugin][0]
except IndexError:
raise RuntimeError(f'Could not find the plugin "{plugin}" for {kind}.')
return func(*args, **kwargs)
def use_plugin(name, kind=None):
"""Set the default plugin for a specified operation. The plugin
will be loaded if it hasn't been already.
Parameters
----------
name : str
Name of plugin.
kind : {'imsave', 'imread', 'imshow', 'imread_collection', 'imshow_collection'}, optional
Set the plugin for this function. By default,
the plugin is set for all functions.
See Also
--------
available_plugins : List of available plugins
Examples
--------
To use Matplotlib as the default image reader, you would write:
>>> from skimage import io
>>> io.use_plugin('matplotlib', 'imread')
To see a list of available plugins run ``io.available_plugins``. Note that
this lists plugins that are defined, but the full list may not be usable
if your system does not have the required libraries installed.
"""
if kind is None:
kind = plugin_store.keys()
else:
if kind not in plugin_provides[name]:
raise RuntimeError(f"Plugin {name} does not support `{kind}`.")
if kind == 'imshow':
kind = [kind, '_app_show']
else:
kind = [kind]
_load(name)
for k in kind:
if k not in plugin_store:
raise RuntimeError(f"'{k}' is not a known plugin function.")
funcs = plugin_store[k]
# Shuffle the plugins so that the requested plugin stands first
# in line
funcs = [(n, f) for (n, f) in funcs if n == name] + \
[(n, f) for (n, f) in funcs if n != name]
plugin_store[k] = funcs
def _inject_imread_collection_if_needed(module):
"""Add `imread_collection` to module if not already present."""
if not hasattr(module, 'imread_collection') and hasattr(module, 'imread'):
imread = getattr(module, 'imread')
func = imread_collection_wrapper(imread)
setattr(module, 'imread_collection', func)
def _load(plugin):
"""Load the given plugin.
Parameters
----------
plugin : str
Name of plugin to load.
See Also
--------
plugins : List of available plugins
"""
if plugin in find_available_plugins(loaded=True):
return
if plugin not in plugin_module_name:
raise ValueError(f"Plugin {plugin} not found.")
else:
modname = plugin_module_name[plugin]
plugin_module = __import__('skimage.io._plugins.' + modname,
fromlist=[modname])
provides = plugin_provides[plugin]
for p in provides:
if p == 'imread_collection':
_inject_imread_collection_if_needed(plugin_module)
elif not hasattr(plugin_module, p):
print(f"Plugin {plugin} does not provide {p} as advertised. Ignoring.")
continue
store = plugin_store[p]
func = getattr(plugin_module, p)
if (plugin, func) not in store:
store.append((plugin, func))
def plugin_info(plugin):
"""Return plugin meta-data.
Parameters
----------
plugin : str
Name of plugin.
Returns
-------
m : dict
Meta data as specified in plugin ``.ini``.
"""
try:
return plugin_meta_data[plugin]
except KeyError:
raise ValueError(f'No information on plugin "{plugin}"')
def plugin_order():
"""Return the currently preferred plugin order.
Returns
-------
p : dict
Dictionary of preferred plugin order, with function name as key and
plugins (in order of preference) as value.
"""
p = {}
for func in plugin_store:
p[func] = [plugin_name for (plugin_name, f) in plugin_store[func]]
return p

View File

@@ -1,81 +0,0 @@
import numpy as np
__all__ = ['load_sift', 'load_surf']
def _sift_read(filelike, mode='SIFT'):
"""Read SIFT or SURF features from externally generated file.
This routine reads SIFT or SURF files generated by binary utilities from
http://people.cs.ubc.ca/~lowe/keypoints/ and
http://www.vision.ee.ethz.ch/~surf/.
This routine *does not* generate SIFT/SURF features from an image. These
algorithms are patent encumbered. Please use `skimage.feature.CENSURE`
instead.
Parameters
----------
filelike : string or open file
Input file generated by the feature detectors from
http://people.cs.ubc.ca/~lowe/keypoints/ or
http://www.vision.ee.ethz.ch/~surf/ .
mode : {'SIFT', 'SURF'}, optional
Kind of descriptor used to generate `filelike`.
Returns
-------
data : record array with fields
- row: int
row position of feature
- column: int
column position of feature
- scale: float
feature scale
- orientation: float
feature orientation
- data: array
feature values
"""
if isinstance(filelike, str):
f = open(filelike)
filelike_is_str = True
else:
f = filelike
filelike_is_str = False
if mode == 'SIFT':
nr_features, feature_len = map(int, f.readline().split())
datatype = np.dtype([('row', float), ('column', float),
('scale', float), ('orientation', float),
('data', (float, feature_len))])
else:
mode = 'SURF'
feature_len = int(f.readline()) - 1
nr_features = int(f.readline())
datatype = np.dtype([('column', float), ('row', float),
('second_moment', (float, 3)),
('sign', float), ('data', (float, feature_len))])
data = np.fromfile(f, sep=' ')
if data.size != nr_features * datatype.itemsize / np.dtype(float).itemsize:
raise OSError(f'Invalid {mode} feature file.')
# If `filelike` is passed to the function as filename - close the file
if filelike_is_str:
f.close()
return data.view(datatype)
def load_sift(f):
return _sift_read(f, mode='SIFT')
def load_surf(f):
return _sift_read(f, mode='SURF')
load_sift.__doc__ = _sift_read.__doc__
load_surf.__doc__ = _sift_read.__doc__

View File

@@ -1,154 +0,0 @@
import os
import numpy as np
import imageio
from skimage import data_dir
from skimage.io.collection import ImageCollection, MultiImage, alphanumeric_key
from skimage.io import reset_plugins
from skimage._shared import testing
from skimage._shared.testing import assert_equal, assert_allclose, fetch
import pytest
try:
has_pooch = True
except ModuleNotFoundError:
has_pooch = False
def test_string_split():
test_string = 'z23a'
test_str_result = ['z', 23, 'a']
assert_equal(alphanumeric_key(test_string), test_str_result)
def test_string_sort():
filenames = ['f9.10.png', 'f9.9.png', 'f10.10.png', 'f10.9.png',
'e9.png', 'e10.png', 'em.png']
expected_filenames = ['e9.png', 'e10.png', 'em.png', 'f9.9.png',
'f9.10.png', 'f10.9.png', 'f10.10.png']
sorted_filenames = sorted(filenames, key=alphanumeric_key)
assert_equal(expected_filenames, sorted_filenames)
def test_imagecollection_input():
"""Test function for ImageCollection. The new behavior (implemented
in 0.16) allows the `pattern` argument to accept a list of strings
as the input.
Notes
-----
If correct, `images` will receive three images.
"""
# Ensure that these images are part of the legacy datasets
# this means they will always be available in the user's install
# regardless of the availability of pooch
pattern = [os.path.join(data_dir, pic)
for pic in ['coffee.png',
'chessboard_GRAY.png',
'rocket.jpg']]
images = ImageCollection(pattern)
assert len(images) == 3
class TestImageCollection():
pattern = [os.path.join(data_dir, pic)
for pic in ['brick.png', 'color.png']]
pattern_matched = [os.path.join(data_dir, pic)
for pic in ['brick.png', 'moon.png']]
def setup_method(self):
reset_plugins()
# Generic image collection with images of different shapes.
self.images = ImageCollection(self.pattern)
# Image collection with images having shapes that match.
self.images_matched = ImageCollection(self.pattern_matched)
# Same images as a collection of frames
self.frames_matched = MultiImage(self.pattern_matched)
def test_len(self):
assert len(self.images) == 2
def test_getitem(self):
num = len(self.images)
for i in range(-num, num):
assert isinstance(self.images[i], np.ndarray)
assert_allclose(self.images[0],
self.images[-num])
def return_img(n):
return self.images[n]
with testing.raises(IndexError):
return_img(num)
with testing.raises(IndexError):
return_img(-num - 1)
def test_slicing(self):
assert type(self.images[:]) is ImageCollection
assert len(self.images[:]) == 2
assert len(self.images[:1]) == 1
assert len(self.images[1:]) == 1
assert_allclose(self.images[0], self.images[:1][0])
assert_allclose(self.images[1], self.images[1:][0])
assert_allclose(self.images[1], self.images[::-1][0])
assert_allclose(self.images[0], self.images[::-1][1])
def test_files_property(self):
assert isinstance(self.images.files, list)
def set_files(f):
self.images.files = f
with testing.raises(AttributeError):
set_files('newfiles')
@pytest.mark.skipif(not has_pooch, reason="needs pooch to download data")
def test_custom_load_func_sequence(self):
filename = fetch('data/no_time_for_that_tiny.gif')
def reader(frameno):
vid = imageio.get_reader(filename)
return vid.get_data(frameno)
ic = ImageCollection(range(24), load_func=reader)
# the length of ic should be that of the given load_pattern sequence
assert len(ic) == 24
# GIF file has frames of size 25x14 with 4 channels (RGBA)
assert ic[0].shape == (25, 14, 4)
@pytest.mark.skipif(not has_pooch, reason="needs pooch to download data")
def test_custom_load_func_w_kwarg(self):
load_pattern = fetch('data/no_time_for_that_tiny.gif')
def load_fn(f, step):
vid = imageio.get_reader(f)
seq = [v for v in vid.iter_data()]
return seq[::step]
ic = ImageCollection(load_pattern, load_func=load_fn, step=3)
# Each file should map to one image (array).
assert len(ic) == 1
# GIF file has 24 frames, so 24 / 3 equals 8.
assert len(ic[0]) == 8
def test_custom_load_func(self):
def load_fn(x):
return x
ic = ImageCollection(os.pathsep.join(self.pattern), load_func=load_fn)
assert_equal(ic[0], self.pattern[0])
def test_concatenate(self):
array = self.images_matched.concatenate()
expected_shape = (len(self.images_matched),) + self.images[0].shape
assert_equal(array.shape, expected_shape)
def test_concatenate_mismatched_image_shapes(self):
with testing.raises(ValueError):
self.images.concatenate()
def test_multiimage_imagecollection(self):
assert_equal(self.images_matched[0], self.frames_matched[0])
assert_equal(self.images_matched[1], self.frames_matched[1])

View File

@@ -1,32 +0,0 @@
import numpy as np
import skimage.io as io
from skimage._shared import testing
testing.pytest.importorskip('astropy')
def test_fits_plugin_import():
# Make sure we get an import exception if Astropy isn't there
# (not sure how useful this is, but it ensures there isn't some other
# error when trying to load the plugin)
try:
io.use_plugin('fits')
except ImportError:
raise()
def teardown():
io.reset_plugins()
def _same_ImageCollection(collection1, collection2):
"""
Ancillary function to compare two ImageCollection objects, checking that
their constituent arrays are equal.
"""
if len(collection1) != len(collection2):
return False
for ext1, ext2 in zip(collection1, collection2):
if not np.all(ext1 == ext2):
return False
return True

View File

@@ -1,25 +0,0 @@
import numpy as np
from skimage.io._plugins._histograms import histograms
from skimage._shared.testing import assert_array_equal, assert_equal, TestCase
class TestHistogram(TestCase):
def test_basic(self):
img = np.ones((50, 50, 3), dtype=np.uint8)
r, g, b, v = histograms(img, 255)
for band in (r, g, b, v):
yield assert_equal, band.sum(), 50 * 50
def test_counts(self):
channel = np.arange(255).reshape(51, 5)
img = np.empty((51, 5, 3), dtype='uint8')
img[:, :, 0] = channel
img[:, :, 1] = channel
img[:, :, 2] = channel
r, g, b, v = histograms(img, 255)
assert_array_equal(r, g)
assert_array_equal(r, b)
assert_array_equal(r, v)
assert_array_equal(r, np.ones(255))

View File

@@ -1,80 +0,0 @@
from tempfile import NamedTemporaryFile
import numpy as np
from skimage.io import imread, imsave, use_plugin, reset_plugins
from skimage._shared import testing
from skimage._shared.testing import assert_array_almost_equal, TestCase, fetch
from skimage._shared._warnings import expected_warnings
def setup():
use_plugin('imageio')
def teardown():
reset_plugins()
def test_imageio_as_gray():
img = imread(fetch('data/color.png'), as_gray=True)
assert img.ndim == 2
assert img.dtype == np.float64
img = imread(fetch('data/camera.png'), as_gray=True)
# check that conversion does not happen for a gray image
assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
def test_imageio_palette():
img = imread(fetch('data/palette_color.png'))
assert img.ndim == 3
def test_imageio_truncated_jpg():
# imageio>2.0 uses Pillow / PIL to try and load the file.
# Oddly, PIL explicitly raises a SyntaxError when the file read fails.
# The exception type changed from SyntaxError to OSError in PIL 8.2.0, so
# allow for either to be raised.
with testing.raises((OSError, SyntaxError)):
imread(fetch('data/truncated.jpg'))
class TestSave(TestCase):
def roundtrip(self, x, scaling=1):
with NamedTemporaryFile(suffix='.png') as f:
fname = f.name
imsave(fname, x)
y = imread(fname)
assert_array_almost_equal((x * scaling).astype(np.int32), y)
def test_imsave_roundtrip(self):
dtype = np.uint8
np.random.seed(0)
for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]:
x = np.ones(shape, dtype=dtype) * np.random.rand(*shape)
if np.issubdtype(dtype, np.floating):
yield self.roundtrip, x, 255
else:
x = (x * 255).astype(dtype)
yield self.roundtrip, x
def test_bool_array_save(self):
with NamedTemporaryFile(suffix='.png') as f:
fname = f.name
with expected_warnings(['.* is a boolean image']):
a = np.zeros((5, 5), bool)
a[2, 2] = True
imsave(fname, a)
def test_return_class():
testing.assert_equal(
type(imread(fetch('data/color.png'))),
np.ndarray
)

View File

@@ -1,70 +0,0 @@
from tempfile import NamedTemporaryFile
import numpy as np
from skimage import io
from skimage.io import imread, imsave, use_plugin, reset_plugins
from skimage._shared import testing
from skimage._shared.testing import (TestCase, assert_array_equal,
assert_array_almost_equal, fetch)
from pytest import importorskip
importorskip('imread')
def setup():
use_plugin('imread')
def teardown():
reset_plugins()
def test_imread_as_gray():
img = imread(fetch('data/color.png'), as_gray=True)
assert img.ndim == 2
assert img.dtype == np.float64
img = imread(fetch('data/camera.png'), as_gray=True)
# check that conversion does not happen for a gray image
assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
def test_imread_palette():
img = imread(fetch('data/palette_color.png'))
assert img.ndim == 3
def test_imread_truncated_jpg():
with testing.raises(RuntimeError):
io.imread(fetch('data/truncated.jpg'))
def test_bilevel():
expected = np.zeros((10, 10), bool)
expected[::2] = 1
img = imread(fetch('data/checker_bilevel.png'))
assert_array_equal(img.astype(bool), expected)
class TestSave(TestCase):
def roundtrip(self, x, scaling=1):
with NamedTemporaryFile(suffix='.png') as f:
fname = f.name
imsave(fname, x)
y = imread(fname)
assert_array_almost_equal((x * scaling).astype(np.int32), y)
def test_imsave_roundtrip(self):
dtype = np.uint8
np.random.seed(0)
for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]:
x = np.ones(shape, dtype=dtype) * np.random.rand(*shape)
if np.issubdtype(dtype, np.floating):
yield self.roundtrip, x, 255
else:
x = (x * 255).astype(dtype)
yield self.roundtrip, x

View File

@@ -1,120 +0,0 @@
import os
import pathlib
import tempfile
import numpy as np
import pytest
from skimage import io
from skimage._shared.testing import assert_array_equal, fetch
from skimage.data import data_dir
one_by_one_jpeg = (
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01'
b'\x00\x01\x00\x00\xff\xdb\x00C\x00\x03\x02\x02\x02\x02'
b'\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04'
b'\x04\x04\x04\x08\x06\x06\x05\x06\t\x08\n\n\t\x08\t\t'
b'\n\x0c\x0f\x0c\n\x0b\x0e\x0b\t\t\r\x11\r\x0e\x0f\x10'
b'\x10\x11\x10\n\x0c\x12\x13\x12\x10\x13\x0f\x10\x10'
b'\x10\xff\xc0\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11'
b'\x00\xff\xc4\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\xff\xc4\x00'
b'\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00'
b'\x00?\x00*\x9f\xff\xd9'
)
def test_stack_basic():
x = np.arange(12).reshape(3, 4)
io.push(x)
assert_array_equal(io.pop(), x)
def test_stack_non_array():
with pytest.raises(ValueError):
io.push([[1, 2, 3]])
def test_imread_file_url():
# tweak data path so that file URI works on both unix and windows.
data_path = str(fetch('data/camera.png'))
data_path = data_path.replace(os.path.sep, '/')
image_url = f'file:///{data_path}'
image = io.imread(image_url)
assert image.shape == (512, 512)
def test_imread_http_url(httpserver):
# httpserver is a fixture provided by pytest-localserver
# https://bitbucket.org/pytest-dev/pytest-localserver/
httpserver.serve_content(one_by_one_jpeg)
# it will serve anything you provide to it on its url.
# we add a /test.jpg so that we can identify the content
# by extension
image = io.imread(httpserver.url + '/test.jpg' + '?' + 's' * 266)
assert image.shape == (1, 1)
def test_imread_pathlib_tiff():
"""Tests reading from Path object (issue gh-5545)."""
# read via fetch
expected = io.imread(fetch('data/multipage.tif'))
# read by passing in a pathlib.Path object
fname = os.path.join(data_dir, 'multipage.tif')
path = pathlib.Path(fname)
img = io.imread(path)
assert img.shape == (2, 15, 10)
assert_array_equal(expected, img)
def _named_tempfile_func(error_class):
"""Create a mock function for NamedTemporaryFile that always raises.
Parameters
----------
error_class : exception class
The error that should be raised when asking for a NamedTemporaryFile.
Returns
-------
named_temp_file : callable
A function that always raises the desired error.
Notes
-----
Although this function has general utility for raising errors, it is
expected to be used to raise errors that ``tempfile.NamedTemporaryFile``
from the Python standard library could raise. As of this writing, these
are ``FileNotFoundError``, ``FileExistsError``, ``PermissionError``, and
``BaseException``. See
`this comment <https://github.com/scikit-image/scikit-image/issues/3785#issuecomment-486598307>`__ # noqa
for more information.
"""
def named_temp_file(*args, **kwargs):
raise error_class()
return named_temp_file
@pytest.mark.parametrize(
'error_class', [
FileNotFoundError, FileExistsError, PermissionError, BaseException
]
)
def test_failed_temporary_file(monkeypatch, error_class):
fetch('data/camera.png')
# tweak data path so that file URI works on both unix and windows.
data_path = data_dir.lstrip(os.path.sep)
data_path = data_path.replace(os.path.sep, '/')
image_url = f'file:///{data_path}/camera.png'
with monkeypatch.context():
monkeypatch.setattr(
tempfile, 'NamedTemporaryFile', _named_tempfile_func(error_class)
)
with pytest.raises(error_class):
io.imread(image_url)

View File

@@ -1,133 +0,0 @@
import numpy as np
import pytest
from skimage import io
from skimage._shared._warnings import expected_warnings
plt = pytest.importorskip("matplotlib.pyplot")
def setup():
io.reset_plugins()
# test images. Note that they don't have their full range for their dtype,
# but we still expect the display range to equal the full dtype range.
im8 = np.array([[0, 64], [128, 240]], np.uint8)
im16 = im8.astype(np.uint16) * 256
im64 = im8.astype(np.uint64)
imf = im8 / 255
im_lo = imf / 1000
im_hi = imf + 10
imshow_expected_warnings = [
r"tight_layout : falling back to Agg|\A\Z",
r"tight_layout: falling back to Agg|\A\Z", # formatting change in mpl
# Maptlotlib 2.2.3 seems to use np.asscalar which issues a warning
# with numpy 1.16
# Matplotlib 2.2.3 is the last supported version for python 2.7
r"np.asscalar|\A\Z"
]
def n_subplots(ax_im):
"""Return the number of subplots in the figure containing an ``AxesImage``.
Parameters
----------
ax_im : matplotlib.pyplot.AxesImage object
The input ``AxesImage``.
Returns
-------
n : int
The number of subplots in the corresponding figure.
Notes
-----
This function is intended to check whether a colorbar was drawn, in
which case two subplots are expected. For standard imshows, one
subplot is expected.
"""
return len(ax_im.get_figure().get_axes())
def test_uint8():
plt.figure()
with expected_warnings(imshow_expected_warnings +
[r"CObject type is marked|\A\Z"]):
ax_im = io.imshow(im8)
assert ax_im.cmap.name == 'gray'
assert ax_im.get_clim() == (0, 255)
assert n_subplots(ax_im) == 1
assert ax_im.colorbar is None
def test_uint16():
plt.figure()
with expected_warnings(imshow_expected_warnings +
[r"CObject type is marked|\A\Z"]):
ax_im = io.imshow(im16)
assert ax_im.cmap.name == 'gray'
assert ax_im.get_clim() == (0, 65535)
assert n_subplots(ax_im) == 1
assert ax_im.colorbar is None
def test_float():
plt.figure()
with expected_warnings(imshow_expected_warnings +
[r"CObject type is marked|\A\Z"]):
ax_im = io.imshow(imf)
assert ax_im.cmap.name == 'gray'
assert ax_im.get_clim() == (0, 1)
assert n_subplots(ax_im) == 1
assert ax_im.colorbar is None
def test_low_data_range():
with expected_warnings(imshow_expected_warnings +
["Low image data range|CObject type is marked"]):
ax_im = io.imshow(im_lo)
assert ax_im.get_clim() == (im_lo.min(), im_lo.max())
# check that a colorbar was created
assert ax_im.colorbar is not None
def test_outside_standard_range():
plt.figure()
# Warning raised by matplotlib on Windows:
# "The CObject type is marked Pending Deprecation in Python 2.7.
# Please use capsule objects instead."
# Ref: https://docs.python.org/2/c-api/cobject.html
with expected_warnings(imshow_expected_warnings +
["out of standard range|CObject type is marked"]):
ax_im = io.imshow(im_hi)
assert ax_im.get_clim() == (im_hi.min(), im_hi.max())
assert n_subplots(ax_im) == 2
assert ax_im.colorbar is not None
def test_nonstandard_type():
plt.figure()
# Warning raised by matplotlib on Windows:
# "The CObject type is marked Pending Deprecation in Python 2.7.
# Please use capsule objects instead."
# Ref: https://docs.python.org/2/c-api/cobject.html
with expected_warnings(imshow_expected_warnings +
["Low image data range|CObject type is marked"]):
ax_im = io.imshow(im64)
assert ax_im.get_clim() == (im64.min(), im64.max())
assert n_subplots(ax_im) == 2
assert ax_im.colorbar is not None
def test_signed_image():
plt.figure()
im_signed = np.array([[-0.5, -0.2], [0.1, 0.4]])
with expected_warnings(imshow_expected_warnings +
[r"CObject type is marked|\A\Z"]):
ax_im = io.imshow(im_signed)
assert ax_im.get_clim() == (-0.5, 0.5)
assert n_subplots(ax_im) == 2
assert ax_im.colorbar is not None

View File

@@ -1,88 +0,0 @@
import os
import numpy as np
from skimage.io import use_plugin, reset_plugins
from skimage.io.collection import MultiImage
from skimage._shared import testing
from skimage._shared.testing import assert_equal, assert_allclose
from pytest import fixture
@fixture
def imgs():
use_plugin('pil')
paths = [testing.fetch('data/multipage_rgb.tif'),
testing.fetch('data/no_time_for_that_tiny.gif')]
imgs = [MultiImage(paths[0]),
MultiImage(paths[0], conserve_memory=False),
MultiImage(paths[1]),
MultiImage(paths[1], conserve_memory=False),
MultiImage(os.pathsep.join(paths))]
yield imgs
reset_plugins()
def test_shapes(imgs):
imgs = imgs[-1]
assert imgs[0][0].shape == imgs[0][1].shape
assert imgs[0][0].shape == (10, 10, 3)
def test_len(imgs):
assert len(imgs[0][0]) == len(imgs[1][0]) == 2
assert len(imgs[2][0]) == len(imgs[3][0]) == 24
assert len(imgs[-1]) == 2, len(imgs[-1])
def test_slicing(imgs):
img = imgs[-1]
assert type(img[:]) is MultiImage
assert len(img[0][:]) + len(img[1][:]) == 26, len(img[:])
assert len(img[0][:1]) == 1
assert len(img[1][1:]) == 23
assert_allclose(img[0], img[:1][0])
assert_allclose(img[1], img[1:][0])
assert_allclose(img[-1], img[::-1][0])
assert_allclose(img[0], img[::-1][-1])
def test_getitem(imgs):
for img in imgs[0]:
num = len(img)
for i in range(-num, num):
assert type(img[i]) is np.ndarray
assert_allclose(img[0], img[-num])
with testing.raises(AssertionError):
assert_allclose(img[0], img[1])
with testing.raises(IndexError):
img[num]
with testing.raises(IndexError):
img[-num - 1]
def test_files_property(imgs):
for img in imgs:
if isinstance(img, MultiImage):
continue
assert isinstance(img.filename, str)
with testing.raises(AttributeError):
img.filename = "newfile"
def test_conserve_memory_property(imgs):
for img in imgs:
assert isinstance(img.conserve_memory, bool)
with testing.raises(AttributeError):
img.conserve_memory = True
def test_concatenate(imgs):
for img in imgs:
if img[0].shape != img[-1].shape:
with testing.raises(ValueError):
img.concatenate()
continue
array = img.concatenate()
assert_equal(array.shape, (len(img),) + img[0].shape)

View File

@@ -1,304 +0,0 @@
import os
from io import BytesIO
from tempfile import NamedTemporaryFile
import numpy as np
import pytest
from PIL import Image
from skimage._shared import testing
from skimage._shared._tempfile import temporary_file
from skimage._shared._warnings import expected_warnings
from skimage._shared.testing import (assert_allclose,
assert_array_almost_equal,
assert_array_equal, assert_equal,
color_check, fetch, mono_check)
from skimage.metrics import structural_similarity
from ... import img_as_float
from ...color import rgb2lab
from .. import imread, imsave, reset_plugins, use_plugin
from .._plugins.pil_plugin import (_palette_is_grayscale, ndarray_to_pil,
pil_to_ndarray)
def setup():
use_plugin('pil')
def teardown():
reset_plugins()
def setup_module(self):
"""The effect of the `plugin.use` call may be overridden by later imports.
Call `use_plugin` directly before the tests to ensure that PIL is used.
"""
try:
use_plugin('pil')
except ImportError:
pass
def test_png_round_trip():
with NamedTemporaryFile(suffix='.png') as f:
fname = f.name
I = np.eye(3)
imsave(fname, I)
Ip = img_as_float(imread(fname))
os.remove(fname)
assert np.sum(np.abs(Ip-I)) < 1e-3
def test_imread_as_gray():
img = imread(fetch('data/color.png'), as_gray=True)
assert img.ndim == 2
assert img.dtype == np.float64
img = imread(fetch('data/camera.png'), as_gray=True)
# check that conversion does not happen for a gray image
assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
@pytest.mark.parametrize('explicit_kwargs', [False, True])
def test_imread_separate_channels(explicit_kwargs):
# Test that imread returns RGB(A) values contiguously even when they are
# stored in separate planes.
x = np.random.rand(3, 16, 8)
with NamedTemporaryFile(suffix='.tif') as f:
fname = f.name
# Tifffile is used as backend whenever suffix is .tif or .tiff
# To avoid pending changes to tifffile defaults, we must specify this is an
# RGB image with separate planes (i.e., channel_axis=0).
if explicit_kwargs:
pass
else:
pass
imsave(fname, x)
img = imread(fname)
os.remove(fname)
assert img.shape == (16, 8, 3), img.shape
def test_imread_multipage_rgb_tif():
img = imread(fetch('data/multipage_rgb.tif'))
assert img.shape == (2, 10, 10, 3), img.shape
def test_imread_palette():
img = imread(fetch('data/palette_gray.png'))
assert img.ndim == 2
img = imread(fetch('data/palette_color.png'))
assert img.ndim == 3
def test_imread_index_png_with_alpha():
# The file `foo3x5x4indexed.png` was created with this array
# (3x5 is (height)x(width)):
dfoo = np.array([[[127, 0, 255, 255],
[127, 0, 255, 255],
[127, 0, 255, 255],
[127, 0, 255, 255],
[127, 0, 255, 255]],
[[192, 192, 255, 0],
[192, 192, 255, 0],
[0, 0, 255, 0],
[0, 0, 255, 0],
[0, 0, 255, 0]],
[[0, 31, 255, 255],
[0, 31, 255, 255],
[0, 31, 255, 255],
[0, 31, 255, 255],
[0, 31, 255, 255]]], dtype=np.uint8)
img = imread(fetch('data/foo3x5x4indexed.png'))
assert_array_equal(img, dfoo)
def test_palette_is_gray():
gray = Image.open(fetch('data/palette_gray.png'))
assert _palette_is_grayscale(gray)
color = Image.open(fetch('data/palette_color.png'))
assert not _palette_is_grayscale(color)
def test_bilevel():
expected = np.zeros((10, 10))
expected[::2] = 255
img = imread(fetch('data/checker_bilevel.png'))
assert_array_equal(img, expected)
def test_imread_uint16():
expected = np.load(fetch('data/chessboard_GRAY_U8.npy'))
img = imread(fetch('data/chessboard_GRAY_U16.tif'))
assert np.issubdtype(img.dtype, np.uint16)
assert_array_almost_equal(img, expected)
def test_imread_truncated_jpg():
with testing.raises(IOError):
imread(fetch('data/truncated.jpg'))
def test_jpg_quality_arg():
chessboard = np.load(fetch('data/chessboard_GRAY_U8.npy'))
with temporary_file(suffix='.jpg') as jpg:
imsave(jpg, chessboard, quality=95)
im = imread(jpg)
sim = structural_similarity(
chessboard, im,
data_range=chessboard.max() - chessboard.min())
assert sim > 0.99
def test_imread_uint16_big_endian():
expected = np.load(fetch('data/chessboard_GRAY_U8.npy'))
img = imread(fetch('data/chessboard_GRAY_U16B.tif'))
assert img.dtype == np.uint16
assert_array_almost_equal(img, expected)
class TestSave:
def roundtrip_file(self, x):
with temporary_file(suffix='.png') as fname:
imsave(fname, x)
y = imread(fname)
return y
def roundtrip_pil_image(self, x):
pil_image = ndarray_to_pil(x)
y = pil_to_ndarray(pil_image)
return y
def verify_roundtrip(self, dtype, x, y, scaling=1):
assert_array_almost_equal((x * scaling).astype(np.int32), y)
def verify_imsave_roundtrip(self, roundtrip_function):
for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]:
for dtype in (np.uint8, np.uint16, np.float32, np.float64):
x = np.ones(shape, dtype=dtype) * np.random.rand(*shape)
if np.issubdtype(dtype, np.floating):
yield (self.verify_roundtrip, dtype, x,
roundtrip_function(x), 255)
else:
x = (x * 255).astype(dtype)
yield (self.verify_roundtrip, dtype, x,
roundtrip_function(x))
def test_imsave_roundtrip_file(self):
self.verify_imsave_roundtrip(self.roundtrip_file)
def test_imsave_roundtrip_pil_image(self):
self.verify_imsave_roundtrip(self.roundtrip_pil_image)
def test_imsave_incorrect_dimension():
with temporary_file(suffix='.png') as fname:
with testing.raises(ValueError):
with expected_warnings([fname + ' is a low contrast image']):
imsave(fname, np.zeros((2, 3, 3, 1)))
with testing.raises(ValueError):
with expected_warnings([fname + ' is a low contrast image']):
imsave(fname, np.zeros((2, 3, 2)))
# test that low contrast check is ignored
with testing.raises(ValueError):
with expected_warnings([]):
imsave(fname, np.zeros((2, 3, 2)), check_contrast=False)
def test_imsave_filelike():
shape = (2, 2)
image = np.zeros(shape)
s = BytesIO()
# save to file-like object
with expected_warnings(['is a low contrast image']):
imsave(s, image)
# read from file-like object
s.seek(0)
out = imread(s)
assert_equal(out.shape, shape)
assert_allclose(out, image)
def test_imsave_boolean_input():
shape = (2, 2)
image = np.eye(*shape, dtype=bool)
s = BytesIO()
# save to file-like object
with expected_warnings(
['is a boolean image: setting True to 255 and False to 0']):
imsave(s, image)
# read from file-like object
s.seek(0)
out = imread(s)
assert_equal(out.shape, shape)
assert_allclose(out.astype(bool), image)
def test_imexport_imimport():
shape = (2, 2)
image = np.zeros(shape)
pil_image = ndarray_to_pil(image)
out = pil_to_ndarray(pil_image)
assert_equal(out.shape, shape)
def test_all_color():
with expected_warnings(['.* is a boolean image']):
color_check('pil')
with expected_warnings(['.* is a boolean image']):
color_check('pil', 'bmp')
def test_all_mono():
with expected_warnings(['.* is a boolean image']):
mono_check('pil')
def test_multi_page_gif():
img = imread(fetch('data/no_time_for_that_tiny.gif'))
assert img.shape == (24, 25, 14, 3), img.shape
img2 = imread(fetch('data/no_time_for_that_tiny.gif'),
img_num=5)
assert img2.shape == (25, 14, 3)
assert_allclose(img[5], img2)
def test_cmyk():
ref = imread(fetch('data/color.png'))
img = Image.open(fetch('data/color.png'))
img = img.convert('CMYK')
with NamedTemporaryFile(suffix='.jpg') as f:
fname = f.name
img.save(fname)
try:
img.close()
except AttributeError: # `close` not available on PIL
pass
new = imread(fname)
ref_lab = rgb2lab(ref)
new_lab = rgb2lab(new)
for i in range(3):
newi = np.ascontiguousarray(new_lab[:, :, i])
refi = np.ascontiguousarray(ref_lab[:, :, i])
sim = structural_similarity(refi, newi,
data_range=refi.max() - refi.min())
assert sim > 0.99
def test_extreme_palette():
img = imread(fetch('data/green_palette.png'))
assert_equal(img.ndim, 3)

View File

@@ -1,74 +0,0 @@
from contextlib import contextmanager
import numpy as np
import pytest
from skimage._shared._dependency_checks import has_mpl
from skimage import io
from skimage.io import manage_plugins
priority_plugin = 'pil'
def setup():
io.use_plugin('pil')
def teardown_module():
io.reset_plugins()
@contextmanager
def protect_preferred_plugins():
"""Contexts where `preferred_plugins` can be modified w/o side-effects."""
preferred_plugins = manage_plugins.preferred_plugins.copy()
try:
yield
finally:
manage_plugins.preferred_plugins = preferred_plugins
def test_failed_use():
with pytest.raises(ValueError):
manage_plugins.use_plugin('asd')
@pytest.mark.skipif(not has_mpl, reason="matplotlib not installed")
def test_use_priority():
manage_plugins.use_plugin(priority_plugin)
plug, func = manage_plugins.plugin_store['imread'][0]
np.testing.assert_equal(plug, priority_plugin)
manage_plugins.use_plugin('matplotlib')
plug, func = manage_plugins.plugin_store['imread'][0]
np.testing.assert_equal(plug, 'matplotlib')
@pytest.mark.skipif(not has_mpl, reason="matplotlib not installed")
def test_load_preferred_plugins_all():
from skimage.io._plugins import pil_plugin, matplotlib_plugin
with protect_preferred_plugins():
manage_plugins.preferred_plugins = {'all': ['pil'],
'imshow': ['matplotlib']}
manage_plugins.reset_plugins()
for plugin_type in ('imread', 'imsave'):
plug, func = manage_plugins.plugin_store[plugin_type][0]
assert func == getattr(pil_plugin, plugin_type)
plug, func = manage_plugins.plugin_store['imshow'][0]
assert func == getattr(matplotlib_plugin, 'imshow')
@pytest.mark.skipif(not has_mpl, reason="matplotlib not installed")
def test_load_preferred_plugins_imread():
from skimage.io._plugins import pil_plugin, matplotlib_plugin
with protect_preferred_plugins():
manage_plugins.preferred_plugins['imread'] = ['pil']
manage_plugins.reset_plugins()
plug, func = manage_plugins.plugin_store['imread'][0]
assert func == pil_plugin.imread
plug, func = manage_plugins.plugin_store['imshow'][0]
assert func == matplotlib_plugin.imshow, func.__module__

View File

@@ -1,66 +0,0 @@
import os
from tempfile import NamedTemporaryFile
from skimage.io import load_sift, load_surf
from skimage._shared.testing import assert_equal
def test_load_sift():
with NamedTemporaryFile(delete=False) as f:
fname = f.name
with open(fname, 'wb') as f:
f.write(b'''2 128
133.92 135.88 14.38 -2.732
3 12 23 38 10 15 78 20 39 67 42 8 12 8 39 35 118 43 17 0
0 1 12 109 9 2 6 0 0 21 46 22 14 18 51 19 5 9 41 52
65 30 3 21 55 49 26 30 118 118 25 12 8 3 2 60 53 56 72 20
7 10 16 7 88 23 13 15 12 11 11 71 45 7 4 49 82 38 38 91
118 15 2 16 33 3 5 118 98 38 6 19 36 1 0 15 64 22 1 2
6 11 18 61 31 3 0 6 15 23 118 118 13 0 0 35 38 18 40 96
24 1 0 13 17 3 24 98
132.36 99.75 11.45 -2.910
94 32 7 2 13 7 5 23 121 94 13 5 0 0 4 59 13 30 71 32
0 6 32 11 25 32 13 0 0 16 51 5 44 50 0 3 33 55 11 9
121 121 12 9 6 3 0 18 55 60 48 44 44 9 0 2 106 117 13 2
1 0 1 1 37 1 1 25 80 35 15 41 121 3 0 2 14 3 2 121
51 11 0 20 93 6 0 20 109 57 3 4 5 0 0 28 21 2 0 5
13 12 75 119 35 0 0 13 28 14 37 121 12 0 0 21 46 5 11 93
29 0 0 3 14 4 11 99''')
# Check whether loading by filename works
load_sift(fname)
with open(fname) as f:
features = load_sift(f)
os.remove(fname)
assert_equal(len(features), 2)
assert_equal(len(features['data'][0]), 128)
assert_equal(features['row'][0], 133.92)
assert_equal(features['column'][1], 99.75)
def test_load_surf():
with NamedTemporaryFile(delete=False) as f:
fname = f.name
with open(fname, 'wb') as f:
f.write(b'''65
2
38.3727 62.0491 0.0371343 0 0.0371343 -1 -0.0705589 0.0130983 -0.00460534 0.132168 -0.0718833 0.0320583 -0.0134032 0.0988654 -0.0542241 0.0171002 -0.00135754 0.105755 -0.0362088 0.0151748 -0.00694272 0.0610017 -0.247091 0.109605 -0.0337623 0.0813307 -0.24185 0.278548 -0.0494523 0.107804 -0.166312 0.0691584 -0.0288199 0.138476 -0.110956 0.0280772 -0.0752509 0.0736344 -0.22667 0.164226 -0.0544717 0.0388139 -0.30194 0.33065 -0.0537507 0.0596398 -0.245395 0.110925 -0.0603322 0.0239389 -0.18726 0.0374145 -0.0355872 0.0140762 -0.129022 0.135104 -0.0703396 0.0374049 -0.24256 0.222544 -0.0536354 0.0501252 -0.209004 0.0971316 -0.0550094 0.0229767 -0.125547 0.0317879 -0.0291574 0.0124569
68.5773 61.474 0.0313267 0 0.0313267 1 -0.10198 0.130987 -0.0321845 0.0487543 -0.0900435 0.121113 -0.100917 0.0444702 -0.0151742 0.107604 -0.0542035 0.014069 -0.00594097 0.0339933 -0.00994295 0.0127262 -0.125613 0.192551 -0.0174399 0.0433488 -0.272698 0.164641 -0.0676735 0.0467444 -0.0527907 0.258005 -0.0818114 0.0440569 -0.0104433 0.0548934 -0.0323454 0.0145296 -0.112357 0.199223 -0.0532903 0.0332622 -0.342481 0.207469 -0.0526129 0.0741355 -0.256234 0.402708 -0.108296 0.117362 -0.0560274 0.128856 -0.123509 0.0510046 -0.0198793 0.0775934 -0.103863 0.00406679 -0.10264 0.1312 -0.108244 0.0812913 -0.127868 0.182924 -0.0680942 0.071913 -0.0858004 0.144806 -0.0176522 0.0686146''')
# Check whether loading by filename works
load_surf(fname)
with open(fname) as f:
features = load_surf(f)
os.remove(fname)
assert_equal(len(features), 2)
assert_equal(len(features['data'][0]), 64)
assert_equal(features['column'][1], 68.5773)
assert_equal(features['row'][0], 62.0491)

View File

@@ -1,83 +0,0 @@
import numpy as np
import unittest
from tempfile import NamedTemporaryFile
from skimage.io import imread, imsave, use_plugin, reset_plugins
from skimage._shared import testing
from pytest import importorskip, raises, fixture
importorskip('SimpleITK')
np.random.seed(0)
def teardown():
reset_plugins()
@fixture(autouse=True)
def setup_plugin():
"""This ensures that `use_plugin` is directly called before all tests to
ensure that SimpleITK is used.
"""
use_plugin('simpleitk')
yield
def test_imread_as_gray():
img = imread(testing.fetch('data/color.png'), as_gray=True)
assert img.ndim == 2
assert img.dtype == np.float64
img = imread(testing.fetch('data/camera.png'), as_gray=True)
# check that conversion does not happen for a gray image
assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
def test_bilevel():
expected = np.zeros((10, 10))
expected[::2] = 255
img = imread(testing.fetch('data/checker_bilevel.png'))
np.testing.assert_array_equal(img, expected)
def test_imread_truncated_jpg():
with raises(RuntimeError):
imread(testing.fetch('data/truncated.jpg'))
def test_imread_uint16():
expected = np.load(testing.fetch('data/chessboard_GRAY_U8.npy'))
img = imread(testing.fetch('data/chessboard_GRAY_U16.tif'))
assert np.issubdtype(img.dtype, np.uint16)
np.testing.assert_array_almost_equal(img, expected)
def test_imread_uint16_big_endian():
expected = np.load(testing.fetch('data/chessboard_GRAY_U8.npy'))
img = imread(testing.fetch('data/chessboard_GRAY_U16B.tif'))
np.testing.assert_array_almost_equal(img, expected)
class TestSave(unittest.TestCase):
def roundtrip(self, dtype, x):
with NamedTemporaryFile(suffix='.mha') as f:
fname = f.name
imsave(fname, x)
y = imread(fname)
np.testing.assert_array_almost_equal(x, y)
def test_imsave_roundtrip(self):
for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]:
for dtype in (np.uint8, np.uint16, np.float32, np.float64):
x = np.ones(shape, dtype=dtype) * np.random.rand(*shape)
if np.issubdtype(dtype, np.floating):
yield self.roundtrip, dtype, x
else:
x = (x * 255).astype(dtype)
yield self.roundtrip, dtype, x

View File

@@ -1,83 +0,0 @@
import pathlib
from tempfile import NamedTemporaryFile
import numpy as np
import pytest
from numpy.testing import assert_array_almost_equal, assert_array_equal
from skimage._shared.testing import fetch
from skimage.io import imread, imsave, reset_plugins, use_plugin
def setup():
use_plugin('tifffile')
np.random.seed(0)
def teardown():
reset_plugins()
def test_imread_uint16():
expected = np.load(fetch('data/chessboard_GRAY_U8.npy'))
img = imread(fetch('data/chessboard_GRAY_U16.tif'))
assert img.dtype == np.uint16
assert_array_almost_equal(img, expected)
def test_imread_uint16_big_endian():
expected = np.load(fetch('data/chessboard_GRAY_U8.npy'))
img = imread(fetch('data/chessboard_GRAY_U16B.tif'))
assert img.dtype == np.uint16
assert_array_almost_equal(img, expected)
def test_imread_multipage_rgb_tif():
img = imread(fetch('data/multipage_rgb.tif'))
assert img.shape == (2, 10, 10, 3), img.shape
def test_tifffile_kwarg_passthrough ():
img = imread(fetch('data/multipage.tif'), key=[1], is_ome=True)
assert img.shape == (15, 10), img.shape
def test_imread_handle():
expected = np.load(fetch('data/chessboard_GRAY_U8.npy'))
with open(fetch('data/chessboard_GRAY_U16.tif'), 'rb') as fh:
img = imread(fh)
assert img.dtype == np.uint16
assert_array_almost_equal(img, expected)
class TestSave:
def roundtrip(self, dtype, x, use_pathlib=False, **kwargs):
with NamedTemporaryFile(suffix='.tif') as f:
fname = f.name
if use_pathlib:
fname = pathlib.Path(fname)
imsave(fname, x, check_contrast=False, **kwargs)
y = imread(fname)
assert_array_equal(x, y)
shapes = ((10, 10), (10, 10, 3), (10, 10, 4))
dtypes = (np.uint8, np.uint16, np.float32, np.int16, np.float64)
@pytest.mark.parametrize("shape", shapes)
@pytest.mark.parametrize("dtype", dtypes)
@pytest.mark.parametrize("use_pathlib", [False, True])
@pytest.mark.parametrize('explicit_photometric_kwarg', [False, True])
def test_imsave_roundtrip(self, shape, dtype, use_pathlib,
explicit_photometric_kwarg):
x = np.random.rand(*shape)
if not np.issubdtype(dtype, np.floating):
x = (x * np.iinfo(dtype).max).astype(dtype)
else:
x = x.astype(dtype)
if explicit_photometric_kwarg and x.shape[-1] in [3, 4]:
kwargs = {'photometric': 'rgb'}
else:
kwargs = {}
self.roundtrip(dtype, x, use_pathlib, **kwargs)

View File

@@ -1,43 +0,0 @@
import urllib.parse
import urllib.request
from urllib.error import URLError, HTTPError
import os
import re
import tempfile
from contextlib import contextmanager
URL_REGEX = re.compile(r'http://|https://|ftp://|file://|file:\\')
def is_url(filename):
"""Return True if string is an http or ftp path."""
return (isinstance(filename, str) and
URL_REGEX.match(filename) is not None)
@contextmanager
def file_or_url_context(resource_name):
"""Yield name of file from the given resource (i.e. file or url)."""
if is_url(resource_name):
url_components = urllib.parse.urlparse(resource_name)
_, ext = os.path.splitext(url_components.path)
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f:
u = urllib.request.urlopen(resource_name)
f.write(u.read())
# f must be closed before yielding
yield f.name
except (URLError, HTTPError):
# could not open URL
os.remove(f.name)
raise
except (FileNotFoundError, FileExistsError,
PermissionError, BaseException):
# could not create temporary file
raise
else:
os.remove(f.name)
else:
yield resource_name