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,16 @@
# -*- coding: utf-8 -*-
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
""" This subpackage provides the core functionality of imageio
(everything but the plugins).
"""
# flake8: noqa
from .util import Image, Array, Dict, asarray, image_as_uint, urlopen
from .util import BaseProgressIndicator, StdoutProgressIndicator, IS_PYPY
from .util import get_platform, appdata_dir, resource_dirs, has_module
from .findlib import load_lib
from .fetching import get_remote_file, InternetNotAllowedError, NeedDownloadError
from .request import Request, read_n_bytes, RETURN_BYTES
from .format import Format, FormatManager

View file

@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
# Based on code from the vispy project
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
"""Data downloading and reading functions
"""
from math import log
import os
from os import path as op
import sys
import shutil
import time
from . import appdata_dir, resource_dirs
from . import StdoutProgressIndicator, urlopen
class InternetNotAllowedError(IOError):
""" Plugins that need resources can just use get_remote_file(), but
should catch this error and silently ignore it.
"""
pass
class NeedDownloadError(IOError):
""" Is raised when a remote file is requested that is not locally
available, but which needs to be explicitly downloaded by the user.
"""
def get_remote_file(fname, directory=None, force_download=False, auto=True):
""" Get a the filename for the local version of a file from the web
Parameters
----------
fname : str
The relative filename on the remote data repository to download.
These correspond to paths on
``https://github.com/imageio/imageio-binaries/``.
directory : str | None
The directory where the file will be cached if a download was
required to obtain the file. By default, the appdata directory
is used. This is also the first directory that is checked for
a local version of the file. If the directory does not exist,
it will be created.
force_download : bool | str
If True, the file will be downloaded even if a local copy exists
(and this copy will be overwritten). Can also be a YYYY-MM-DD date
to ensure a file is up-to-date (modified date of a file on disk,
if present, is checked).
auto : bool
Whether to auto-download the file if its not present locally. Default
True. If False and a download is needed, raises NeedDownloadError.
Returns
-------
fname : str
The path to the file on the local system.
"""
_url_root = "https://github.com/imageio/imageio-binaries/raw/master/"
url = _url_root + fname
nfname = op.normcase(fname) # convert to native
# Get dirs to look for the resource
given_directory = directory
directory = given_directory or appdata_dir("imageio")
dirs = resource_dirs()
dirs.insert(0, directory) # Given dir has preference
# Try to find the resource locally
for dir in dirs:
filename = op.join(dir, nfname)
if op.isfile(filename):
if not force_download: # we're done
if given_directory and given_directory != dir:
filename2 = os.path.join(given_directory, nfname)
# Make sure the output directory exists
if not op.isdir(op.dirname(filename2)):
os.makedirs(op.abspath(op.dirname(filename2)))
shutil.copy(filename, filename2)
return filename2
return filename
if isinstance(force_download, str):
ntime = time.strptime(force_download, "%Y-%m-%d")
ftime = time.gmtime(op.getctime(filename))
if ftime >= ntime:
if given_directory and given_directory != dir:
filename2 = os.path.join(given_directory, nfname)
# Make sure the output directory exists
if not op.isdir(op.dirname(filename2)):
os.makedirs(op.abspath(op.dirname(filename2)))
shutil.copy(filename, filename2)
return filename2
return filename
else:
print("File older than %s, updating..." % force_download)
break
# If we get here, we're going to try to download the file
if os.getenv("IMAGEIO_NO_INTERNET", "").lower() in ("1", "true", "yes"):
raise InternetNotAllowedError(
"Will not download resource from the "
"internet because environment variable "
"IMAGEIO_NO_INTERNET is set."
)
# Can we proceed with auto-download?
if not auto:
raise NeedDownloadError()
# Get filename to store to and make sure the dir exists
filename = op.join(directory, nfname)
if not op.isdir(op.dirname(filename)):
os.makedirs(op.abspath(op.dirname(filename)))
# let's go get the file
if os.getenv("CONTINUOUS_INTEGRATION", False): # pragma: no cover
# On Travis, we retry a few times ...
for i in range(2):
try:
_fetch_file(url, filename)
return filename
except IOError:
time.sleep(0.5)
else:
_fetch_file(url, filename)
return filename
else: # pragma: no cover
_fetch_file(url, filename)
return filename
def _fetch_file(url, file_name, print_destination=True):
"""Load requested file, downloading it if needed or requested
Parameters
----------
url: string
The url of file to be downloaded.
file_name: string
Name, along with the path, of where downloaded file will be saved.
print_destination: bool, optional
If true, destination of where file was saved will be printed after
download finishes.
resume: bool, optional
If true, try to resume partially downloaded files.
"""
# Adapted from NISL:
# https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
print(
"Imageio: %r was not found on your computer; "
"downloading it now." % os.path.basename(file_name)
)
temp_file_name = file_name + ".part"
local_file = None
initial_size = 0
errors = []
for tries in range(4):
try:
# Checking file size and displaying it alongside the download url
remote_file = urlopen(url, timeout=5.0)
file_size = int(remote_file.headers["Content-Length"].strip())
size_str = _sizeof_fmt(file_size)
print("Try %i. Download from %s (%s)" % (tries + 1, url, size_str))
# Downloading data (can be extended to resume if need be)
local_file = open(temp_file_name, "wb")
_chunk_read(remote_file, local_file, initial_size=initial_size)
# temp file must be closed prior to the move
if not local_file.closed:
local_file.close()
shutil.move(temp_file_name, file_name)
if print_destination is True:
sys.stdout.write("File saved as %s.\n" % file_name)
break
except Exception as e:
errors.append(e)
print("Error while fetching file: %s." % str(e))
finally:
if local_file is not None:
if not local_file.closed:
local_file.close()
else:
raise IOError(
"Unable to download %r. Perhaps there is a no internet "
"connection? If there is, please report this problem."
% os.path.basename(file_name)
)
def _chunk_read(response, local_file, chunk_size=8192, initial_size=0):
"""Download a file chunk by chunk and show advancement
Can also be used when resuming downloads over http.
Parameters
----------
response: urllib.response.addinfourl
Response to the download request in order to get file size.
local_file: file
Hard disk file where data should be written.
chunk_size: integer, optional
Size of downloaded chunks. Default: 8192
initial_size: int, optional
If resuming, indicate the initial size of the file.
"""
# Adapted from NISL:
# https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
bytes_so_far = initial_size
# Returns only amount left to download when resuming, not the size of the
# entire file
total_size = int(response.headers["Content-Length"].strip())
total_size += initial_size
progress = StdoutProgressIndicator("Downloading")
progress.start("", "bytes", total_size)
while True:
chunk = response.read(chunk_size)
bytes_so_far += len(chunk)
if not chunk:
break
_chunk_write(chunk, local_file, progress)
progress.finish("Done")
def _chunk_write(chunk, local_file, progress):
"""Write a chunk to file and update the progress bar"""
local_file.write(chunk)
progress.increase_progress(len(chunk))
time.sleep(0) # Give other threads a chance, e.g. those that handle stdout pipes
def _sizeof_fmt(num):
"""Turn number of bytes into human-readable str"""
units = ["bytes", "kB", "MB", "GB", "TB", "PB"]
decimals = [0, 0, 1, 2, 2, 2]
"""Human friendly file size"""
if num > 1:
exponent = min(int(log(num, 1024)), len(units) - 1)
quotient = float(num) / 1024 ** exponent
unit = units[exponent]
num_decimals = decimals[exponent]
format_string = "{0:.%sf} {1}" % num_decimals
return format_string.format(quotient, unit)
return "0 bytes" if num == 0 else "1 byte"

View file

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015-1018, imageio contributors
# Copyright (C) 2013, Zach Pincus, Almar Klein and others
""" This module contains generic code to find and load a dynamic library.
"""
import os
import sys
import ctypes
LOCALDIR = os.path.abspath(os.path.dirname(__file__))
# Flag that can be patched / set to True to disable loading non-system libs
SYSTEM_LIBS_ONLY = False
def looks_lib(fname):
""" Returns True if the given filename looks like a dynamic library.
Based on extension, but cross-platform and more flexible.
"""
fname = fname.lower()
if sys.platform.startswith("win"):
return fname.endswith(".dll")
elif sys.platform.startswith("darwin"):
return fname.endswith(".dylib")
else:
return fname.endswith(".so") or ".so." in fname
def generate_candidate_libs(lib_names, lib_dirs=None):
""" Generate a list of candidate filenames of what might be the dynamic
library corresponding with the given list of names.
Returns (lib_dirs, lib_paths)
"""
lib_dirs = lib_dirs or []
# Get system dirs to search
sys_lib_dirs = [
"/lib",
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/aarch64-linux-gnu",
"/usr/local/lib",
"/opt/local/lib",
]
# Get Python dirs to search (shared if for Pyzo)
py_sub_dirs = ["lib", "DLLs", "Library/bin", "shared"]
py_lib_dirs = [os.path.join(sys.prefix, d) for d in py_sub_dirs]
if hasattr(sys, "base_prefix"):
py_lib_dirs += [os.path.join(sys.base_prefix, d) for d in py_sub_dirs]
# Get user dirs to search (i.e. HOME)
home_dir = os.path.expanduser("~")
user_lib_dirs = [os.path.join(home_dir, d) for d in ["lib"]]
# Select only the dirs for which a directory exists, and remove duplicates
potential_lib_dirs = lib_dirs + sys_lib_dirs + py_lib_dirs + user_lib_dirs
lib_dirs = []
for ld in potential_lib_dirs:
if os.path.isdir(ld) and ld not in lib_dirs:
lib_dirs.append(ld)
# Now attempt to find libraries of that name in the given directory
# (case-insensitive)
lib_paths = []
for lib_dir in lib_dirs:
# Get files, prefer short names, last version
files = os.listdir(lib_dir)
files = reversed(sorted(files))
files = sorted(files, key=len)
for lib_name in lib_names:
# Test all filenames for name and ext
for fname in files:
if fname.lower().startswith(lib_name) and looks_lib(fname):
lib_paths.append(os.path.join(lib_dir, fname))
# Return (only the items which are files)
lib_paths = [lp for lp in lib_paths if os.path.isfile(lp)]
return lib_dirs, lib_paths
def load_lib(exact_lib_names, lib_names, lib_dirs=None):
""" load_lib(exact_lib_names, lib_names, lib_dirs=None)
Load a dynamic library.
This function first tries to load the library from the given exact
names. When that fails, it tries to find the library in common
locations. It searches for files that start with one of the names
given in lib_names (case insensitive). The search is performed in
the given lib_dirs and a set of common library dirs.
Returns ``(ctypes_library, library_path)``
"""
# Checks
assert isinstance(exact_lib_names, list)
assert isinstance(lib_names, list)
if lib_dirs is not None:
assert isinstance(lib_dirs, list)
exact_lib_names = [n for n in exact_lib_names if n]
lib_names = [n for n in lib_names if n]
# Get reference name (for better messages)
if lib_names:
the_lib_name = lib_names[0]
elif exact_lib_names:
the_lib_name = exact_lib_names[0]
else:
raise ValueError("No library name given.")
# Collect filenames of potential libraries
# First try a few bare library names that ctypes might be able to find
# in the default locations for each platform.
if SYSTEM_LIBS_ONLY:
lib_dirs, lib_paths = [], []
else:
lib_dirs, lib_paths = generate_candidate_libs(lib_names, lib_dirs)
lib_paths = exact_lib_names + lib_paths
# Select loader
if sys.platform.startswith("win"):
loader = ctypes.windll
else:
loader = ctypes.cdll
# Try to load until success
the_lib = None
errors = []
for fname in lib_paths:
try:
the_lib = loader.LoadLibrary(fname)
break
except Exception as err:
# Don't record errors when it couldn't load the library from an
# exact name -- this fails often, and doesn't provide any useful
# debugging information anyway, beyond "couldn't find library..."
if fname not in exact_lib_names:
errors.append((fname, err))
# No success ...
if the_lib is None:
if errors:
# No library loaded, and load-errors reported for some
# candidate libs
err_txt = ["%s:\n%s" % (l, str(e)) for l, e in errors]
msg = (
"One or more %s libraries were found, but "
+ "could not be loaded due to the following errors:\n%s"
)
raise OSError(msg % (the_lib_name, "\n\n".join(err_txt)))
else:
# No errors, because no potential libraries found at all!
msg = "Could not find a %s library in any of:\n%s"
raise OSError(msg % (the_lib_name, "\n".join(lib_dirs)))
# Done
return the_lib, fname

View file

@ -0,0 +1,735 @@
# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
"""
.. note::
imageio is under construction, some details with regard to the
Reader and Writer classes may change.
These are the main classes of imageio. They expose an interface for
advanced users and plugin developers. A brief overview:
* imageio.FormatManager - for keeping track of registered formats.
* imageio.Format - representation of a file format reader/writer
* imageio.Format.Reader - object used during the reading of a file.
* imageio.Format.Writer - object used during saving a file.
* imageio.Request - used to store the filename and other info.
Plugins need to implement a Format class and register
a format object using ``imageio.formats.add_format()``.
"""
# todo: do we even use the known extensions?
# Some notes:
#
# The classes in this module use the Request object to pass filename and
# related info around. This request object is instantiated in
# imageio.get_reader and imageio.get_writer.
import os
import sys
import numpy as np
from . import Array, asarray
MODENAMES = {
"i": "single-image",
"I": "multi-image",
"v": "single-volume",
"V": "multi-volume",
"?": "any-mode",
}
class Format(object):
""" Represents an implementation to read/write a particular file format
A format instance is responsible for 1) providing information about
a format; 2) determining whether a certain file can be read/written
with this format; 3) providing a reader/writer class.
Generally, imageio will select the right format and use that to
read/write an image. A format can also be explicitly chosen in all
read/write functions. Use ``print(format)``, or ``help(format_name)``
to see its documentation.
To implement a specific format, one should create a subclass of
Format and the Format.Reader and Format.Writer classes. see
:doc:`plugins` for details.
Parameters
----------
name : str
A short name of this format. Users can select a format using its name.
description : str
A one-line description of the format.
extensions : str | list | None
List of filename extensions that this format supports. If a
string is passed it should be space or comma separated. The
extensions are used in the documentation and to allow users to
select a format by file extension. It is not used to determine
what format to use for reading/saving a file.
modes : str
A string containing the modes that this format can handle ('iIvV'),
i for an image, I for multiple images, v for a volume,
V for multiple volumes.
This attribute is used in the documentation and to select the
formats when reading/saving a file.
"""
def __init__(self, name, description, extensions=None, modes=None):
# Store name and description
self._name = name.upper()
self._description = description
# Store extensions, do some effort to normalize them.
# They are stored as a list of lowercase strings without leading dots.
if extensions is None:
extensions = []
elif isinstance(extensions, str):
extensions = extensions.replace(",", " ").split(" ")
#
if isinstance(extensions, (tuple, list)):
self._extensions = tuple(
["." + e.strip(".").lower() for e in extensions if e]
)
else:
raise ValueError("Invalid value for extensions given.")
# Store mode
self._modes = modes or ""
if not isinstance(self._modes, str):
raise ValueError("Invalid value for modes given.")
for m in self._modes:
if m not in "iIvV?":
raise ValueError("Invalid value for mode given.")
def __repr__(self):
# Short description
return "<Format %s - %s>" % (self.name, self.description)
def __str__(self):
return self.doc
@property
def doc(self):
""" The documentation for this format (name + description + docstring).
"""
# Our docsring is assumed to be indented by four spaces. The
# first line needs special attention.
return "%s - %s\n\n %s\n" % (
self.name,
self.description,
self.__doc__.strip(),
)
@property
def name(self):
""" The name of this format.
"""
return self._name
@property
def description(self):
""" A short description of this format.
"""
return self._description
@property
def extensions(self):
""" A list of file extensions supported by this plugin.
These are all lowercase with a leading dot.
"""
return self._extensions
@property
def modes(self):
""" A string specifying the modes that this format can handle.
"""
return self._modes
def get_reader(self, request):
""" get_reader(request)
Return a reader object that can be used to read data and info
from the given file. Users are encouraged to use
imageio.get_reader() instead.
"""
select_mode = request.mode[1] if request.mode[1] in "iIvV" else ""
if select_mode not in self.modes:
modename = MODENAMES.get(select_mode, select_mode)
raise RuntimeError(
"Format %s cannot read in %s mode" % (self.name, modename)
)
return self.Reader(self, request)
def get_writer(self, request):
""" get_writer(request)
Return a writer object that can be used to write data and info
to the given file. Users are encouraged to use
imageio.get_writer() instead.
"""
select_mode = request.mode[1] if request.mode[1] in "iIvV" else ""
if select_mode not in self.modes:
modename = MODENAMES.get(select_mode, select_mode)
raise RuntimeError(
"Format %s cannot write in %s mode" % (self.name, modename)
)
return self.Writer(self, request)
def can_read(self, request):
""" can_read(request)
Get whether this format can read data from the specified uri.
"""
return self._can_read(request)
def can_write(self, request):
""" can_write(request)
Get whether this format can write data to the speciefed uri.
"""
return self._can_write(request)
def _can_read(self, request): # pragma: no cover
return None # Plugins must implement this
def _can_write(self, request): # pragma: no cover
return None # Plugins must implement this
# -----
class _BaseReaderWriter(object):
""" Base class for the Reader and Writer class to implement common
functionality. It implements a similar approach for opening/closing
and context management as Python's file objects.
"""
def __init__(self, format, request):
self.__closed = False
self._BaseReaderWriter_last_index = -1
self._format = format
self._request = request
# Open the reader/writer
self._open(**self.request.kwargs.copy())
@property
def format(self):
""" The :class:`.Format` object corresponding to the current
read/write operation.
"""
return self._format
@property
def request(self):
""" The :class:`.Request` object corresponding to the
current read/write operation.
"""
return self._request
def __enter__(self):
self._checkClosed()
return self
def __exit__(self, type, value, traceback):
if value is None:
# Otherwise error in close hide the real error.
self.close()
def __del__(self):
try:
self.close()
except Exception: # pragma: no cover
pass # Supress noise when called during interpreter shutdown
def close(self):
""" Flush and close the reader/writer.
This method has no effect if it is already closed.
"""
if self.__closed:
return
self.__closed = True
self._close()
# Process results and clean request object
self.request.finish()
@property
def closed(self):
""" Whether the reader/writer is closed.
"""
return self.__closed
def _checkClosed(self, msg=None):
"""Internal: raise an ValueError if reader/writer is closed
"""
if self.closed:
what = self.__class__.__name__
msg = msg or ("I/O operation on closed %s." % what)
raise RuntimeError(msg)
# To implement
def _open(self, **kwargs):
""" _open(**kwargs)
Plugins should probably implement this.
It is called when reader/writer is created. Here the
plugin can do its initialization. The given keyword arguments
are those that were given by the user at imageio.read() or
imageio.write().
"""
raise NotImplementedError()
def _close(self):
""" _close()
Plugins should probably implement this.
It is called when the reader/writer is closed. Here the plugin
can do a cleanup, flush, etc.
"""
raise NotImplementedError()
# -----
class Reader(_BaseReaderWriter):
"""
The purpose of a reader object is to read data from an image
resource, and should be obtained by calling :func:`.get_reader`.
A reader can be used as an iterator to read multiple images,
and (if the format permits) only reads data from the file when
new data is requested (i.e. streaming). A reader can also be
used as a context manager so that it is automatically closed.
Plugins implement Reader's for different formats. Though rare,
plugins may provide additional functionality (beyond what is
provided by the base reader class).
"""
def get_length(self):
""" get_length()
Get the number of images in the file. (Note: you can also
use ``len(reader_object)``.)
The result can be:
* 0 for files that only have meta data
* 1 for singleton images (e.g. in PNG, JPEG, etc.)
* N for image series
* inf for streams (series of unknown length)
"""
return self._get_length()
def get_data(self, index, **kwargs):
""" get_data(index, **kwargs)
Read image data from the file, using the image index. The
returned image has a 'meta' attribute with the meta data.
Raises IndexError if the index is out of range.
Some formats may support additional keyword arguments. These are
listed in the documentation of those formats.
"""
self._checkClosed()
self._BaseReaderWriter_last_index = index
try:
im, meta = self._get_data(index, **kwargs)
except StopIteration:
raise IndexError(index)
return Array(im, meta) # Array tests im and meta
def get_next_data(self, **kwargs):
""" get_next_data(**kwargs)
Read the next image from the series.
Some formats may support additional keyword arguments. These are
listed in the documentation of those formats.
"""
return self.get_data(self._BaseReaderWriter_last_index + 1, **kwargs)
def set_image_index(self, index, **kwargs):
""" set_image_index(index)
Set the internal pointer such that the next call to
get_next_data() returns the image specified by the index
"""
self._checkClosed()
n = self.get_length()
if index <= n:
self._BaseReaderWriter_last_index = index - 1
def get_meta_data(self, index=None):
""" get_meta_data(index=None)
Read meta data from the file. using the image index. If the
index is omitted or None, return the file's (global) meta data.
Note that ``get_data`` also provides the meta data for the returned
image as an atrribute of that image.
The meta data is a dict, which shape depends on the format.
E.g. for JPEG, the dict maps group names to subdicts and each
group is a dict with name-value pairs. The groups represent
the different metadata formats (EXIF, XMP, etc.).
"""
self._checkClosed()
meta = self._get_meta_data(index)
if not isinstance(meta, dict):
raise ValueError(
"Meta data must be a dict, not %r" % meta.__class__.__name__
)
return meta
def iter_data(self):
""" iter_data()
Iterate over all images in the series. (Note: you can also
iterate over the reader object.)
"""
self._checkClosed()
n = self.get_length()
i = 0
while i < n:
try:
im, meta = self._get_data(i)
except StopIteration:
return
except IndexError:
if n == float("inf"):
return
raise
yield Array(im, meta)
i += 1
# Compatibility
def __iter__(self):
return self.iter_data()
def __len__(self):
n = self.get_length()
if n == float("inf"):
n = sys.maxsize
return n
# To implement
def _get_length(self):
""" _get_length()
Plugins must implement this.
The retured scalar specifies the number of images in the series.
See Reader.get_length for more information.
"""
raise NotImplementedError()
def _get_data(self, index):
""" _get_data()
Plugins must implement this, but may raise an IndexError in
case the plugin does not support random access.
It should return the image and meta data: (ndarray, dict).
"""
raise NotImplementedError()
def _get_meta_data(self, index):
""" _get_meta_data(index)
Plugins must implement this.
It should return the meta data as a dict, corresponding to the
given index, or to the file's (global) meta data if index is
None.
"""
raise NotImplementedError()
# -----
class Writer(_BaseReaderWriter):
"""
The purpose of a writer object is to write data to an image
resource, and should be obtained by calling :func:`.get_writer`.
A writer will (if the format permits) write data to the file
as soon as new data is provided (i.e. streaming). A writer can
also be used as a context manager so that it is automatically
closed.
Plugins implement Writer's for different formats. Though rare,
plugins may provide additional functionality (beyond what is
provided by the base writer class).
"""
def append_data(self, im, meta=None):
""" append_data(im, meta={})
Append an image (and meta data) to the file. The final meta
data that is used consists of the meta data on the given
image (if applicable), updated with the given meta data.
"""
self._checkClosed()
# Check image data
if not isinstance(im, np.ndarray):
raise ValueError("append_data requires ndarray as first arg")
# Get total meta dict
total_meta = {}
if hasattr(im, "meta") and isinstance(im.meta, dict):
total_meta.update(im.meta)
if meta is None:
pass
elif not isinstance(meta, dict):
raise ValueError("Meta must be a dict.")
else:
total_meta.update(meta)
# Decouple meta info
im = asarray(im)
# Call
return self._append_data(im, total_meta)
def set_meta_data(self, meta):
""" set_meta_data(meta)
Sets the file's (global) meta data. The meta data is a dict which
shape depends on the format. E.g. for JPEG the dict maps
group names to subdicts, and each group is a dict with
name-value pairs. The groups represents the different
metadata formats (EXIF, XMP, etc.).
Note that some meta formats may not be supported for
writing, and individual fields may be ignored without
warning if they are invalid.
"""
self._checkClosed()
if not isinstance(meta, dict):
raise ValueError("Meta must be a dict.")
else:
return self._set_meta_data(meta)
# To implement
def _append_data(self, im, meta):
# Plugins must implement this
raise NotImplementedError()
def _set_meta_data(self, meta):
# Plugins must implement this
raise NotImplementedError()
class FormatManager(object):
"""
There is exactly one FormatManager object in imageio: ``imageio.formats``.
Its purpose it to keep track of the registered formats.
The format manager supports getting a format object using indexing (by
format name or extension). When used as an iterator, this object
yields all registered format objects.
See also :func:`.help`.
"""
def __init__(self):
self._formats = []
self._formats_sorted = []
def __repr__(self):
return "<imageio.FormatManager with %i registered formats>" % len(self)
def __iter__(self):
return iter(self._formats_sorted)
def __len__(self):
return len(self._formats)
def __str__(self):
ss = []
for format in self:
ext = ", ".join(format.extensions)
s = "%s - %s [%s]" % (format.name, format.description, ext)
ss.append(s)
return "\n".join(ss)
def __getitem__(self, name):
# Check
if not isinstance(name, str):
raise ValueError(
"Looking up a format should be done by name " "or by extension."
)
if not name:
raise ValueError("No format matches the empty string.")
# Test if name is existing file
if os.path.isfile(name):
from . import Request
format = self.search_read_format(Request(name, "r?"))
if format is not None:
return format
if "." in name:
# Look for extension
e1, e2 = os.path.splitext(name.lower())
name = e2 or e1
# Search for format that supports this extension
for format in self:
if name in format.extensions:
return format
else:
# Look for name
name = name.upper()
for format in self:
if name == format.name:
return format
for format in self:
if name == format.name.rsplit("-", 1)[0]:
return format
else:
# Maybe the user meant to specify an extension
try:
return self["." + name.lower()]
except IndexError:
pass # Fail using original name below
# Nothing found ...
raise IndexError("No format known by name %s." % name)
def sort(self, *names):
""" sort(name1, name2, name3, ...)
Sort the formats based on zero or more given names; a format with
a name that matches one of the given names will take precedence
over other formats. A match means an equal name, or ending with
that name (though the former counts higher). Case insensitive.
Format preference will match the order of the given names: using
``sort('TIFF', '-FI', '-PIL')`` would prefer the FreeImage formats
over the Pillow formats, but prefer TIFF even more. Each time
this is called, the starting point is the default format order,
and calling ``sort()`` with no arguments will reset the order.
Be aware that using the function can affect the behavior of
other code that makes use of imageio.
Also see the ``IMAGEIO_FORMAT_ORDER`` environment variable.
"""
# Check and sanitize imput
for name in names:
if not isinstance(name, str):
raise TypeError("formats.sort() accepts only string names.")
if any(c in name for c in ".,"):
raise ValueError(
"Names given to formats.sort() should not "
"contain dots or commas."
)
names = [name.strip().upper() for name in names]
# Reset
self._formats_sorted = list(self._formats)
# Sort
for name in reversed(names):
sorter = lambda f: -((f.name == name) + (f.name.endswith(name)))
self._formats_sorted.sort(key=sorter)
def add_format(self, format, overwrite=False):
""" add_format(format, overwrite=False)
Register a format, so that imageio can use it. If a format with the
same name already exists, an error is raised, unless overwrite is True,
in which case the current format is replaced.
"""
if not isinstance(format, Format):
raise ValueError("add_format needs argument to be a Format object")
elif format in self._formats:
raise ValueError("Given Format instance is already registered")
elif format.name in self.get_format_names():
if overwrite:
old_format = self[format.name]
self._formats.remove(old_format)
if old_format in self._formats_sorted:
self._formats_sorted.remove(old_format)
else:
raise ValueError(
"A Format named %r is already registered, use"
" overwrite=True to replace." % format.name
)
self._formats.append(format)
self._formats_sorted.append(format)
def search_read_format(self, request):
""" search_read_format(request)
Search a format that can read a file according to the given request.
Returns None if no appropriate format was found. (used internally)
"""
select_mode = request.mode[1] if request.mode[1] in "iIvV" else ""
# Select formats that seem to be able to read it
selected_formats = []
for format in self:
if select_mode in format.modes:
if request.extension in format.extensions:
selected_formats.append(format)
# Select the first that can
for format in selected_formats:
if format.can_read(request):
return format
# If no format could read it, it could be that file has no or
# the wrong extension. We ask all formats again.
for format in self:
if format not in selected_formats:
if format.can_read(request):
return format
def search_write_format(self, request):
""" search_write_format(request)
Search a format that can write a file according to the given request.
Returns None if no appropriate format was found. (used internally)
"""
select_mode = request.mode[1] if request.mode[1] in "iIvV" else ""
# Select formats that seem to be able to write it
selected_formats = []
for format in self:
if select_mode in format.modes:
if request.extension in format.extensions:
selected_formats.append(format)
# Select the first that can
for format in selected_formats:
if format.can_write(request):
return format
# If none of the selected formats could write it, maybe another
# format can still write it. It might prefer a different mode,
# or be able to handle more formats than it says by its extensions.
for format in self:
if format not in selected_formats:
if format.can_write(request):
return format
def get_format_names(self):
""" Get the names of all registered formats.
"""
return [f.name for f in self]
def show(self):
""" Show a nicely formatted list of available formats
"""
print(self)

View file

@ -0,0 +1,625 @@
# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
"""
These functions represent imageio's main interface for the user. They
provide a common API to read and write image data for a large
variety of formats. All read and write functions accept keyword
arguments, which are passed on to the format that does the actual work.
To see what keyword arguments are supported by a specific format, use
the :func:`.help` function.
Functions for reading:
* :func:`.imread` - read an image from the specified uri
* :func:`.mimread` - read a series of images from the specified uri
* :func:`.volread` - read a volume from the specified uri
* :func:`.mvolread` - read a series of volumes from the specified uri
Functions for saving:
* :func:`.imwrite` - write an image to the specified uri
* :func:`.mimwrite` - write a series of images to the specified uri
* :func:`.volwrite` - write a volume to the specified uri
* :func:`.mvolwrite` - write a series of volumes to the specified uri
More control:
For a larger degree of control, imageio provides functions
:func:`.get_reader` and :func:`.get_writer`. They respectively return an
:class:`.Reader` and an :class:`.Writer` object, which can
be used to read/write data and meta data in a more controlled manner.
This also allows specific scientific formats to be exposed in a way
that best suits that file-format.
----
All read-functions return images as numpy arrays, and have a ``meta``
attribute; the meta-data dictionary can be accessed with ``im.meta``.
To make this work, imageio actually makes use of a subclass of
``np.ndarray``. If needed, the image can be converted to a plain numpy
array using ``np.asarray(im)``.
----
Supported resource URI's:
All functions described here accept a URI to describe the resource to
read from or write to. These can be a wide range of things. (Imageio
takes care of handling the URI so that plugins can access the data in
an easy way.)
For reading and writing:
* a normal filename, e.g. ``'c:\\foo\\bar.png'``
* a file in a zipfile, e.g. ``'c:\\foo\\bar.zip\\eggs.png'``
* a file object with a ``read()`` / ``write()`` method.
For reading:
* an http/ftp address, e.g. ``'http://example.com/foo.png'``
* the raw bytes of an image file
* ``get_reader("<video0>")`` to grab images from a (web) camera.
* ``imread("<screen>")`` to grab a screenshot (on Windows or OS X).
* ``imread("<clipboard>")`` to grab an image from the clipboard (on Windows).
For writing one can also use ``'<bytes>'`` or ``imageio.RETURN_BYTES`` to
make a write function return the bytes instead of writing to a file.
Note that reading from HTTP and zipfiles works for many formats including
png and jpeg, but may not work for all formats (some plugins "seek" the
file object, which HTTP/zip streams do not support). In such a case one
can download/extract the file first. For HTTP one can use something
like ``imageio.imread(imageio.core.urlopen(url).read(), '.gif')``.
"""
from numbers import Number
import re
import numpy as np
from . import Request, RETURN_BYTES
from .. import formats
from .format import MODENAMES
MEMTEST_DEFAULT_MIM = "256MB"
MEMTEST_DEFAULT_MVOL = "1GB"
mem_re = re.compile(r"^(\d+\.?\d*)\s*([kKMGTPEZY]?i?)B?$")
sizes = {"": 1, None: 1}
for i, si in enumerate([""] + list("kMGTPEZY")):
sizes[si] = 1000 ** i
if si:
sizes[si.upper() + "i"] = 1024 ** i
def to_nbytes(arg, default=None):
if not arg:
return None
if arg is True:
return default
if isinstance(arg, Number):
return arg
match = mem_re.match(arg)
if match is None:
raise ValueError(
"Memory size could not be parsed "
"(is your capitalisation correct?): {}".format(arg)
)
num, unit = match.groups()
try:
return float(num) * sizes[unit]
except KeyError:
raise ValueError(
"Memory size unit not recognised "
"(is your capitalisation correct?): {}".format(unit)
)
def help(name=None):
""" help(name=None)
Print the documentation of the format specified by name, or a list
of supported formats if name is omitted.
Parameters
----------
name : str
Can be the name of a format, a filename extension, or a full
filename. See also the :doc:`formats page <formats>`.
"""
if not name:
print(formats)
else:
print(formats[name])
## Base functions that return a reader/writer
def get_reader(uri, format=None, mode="?", **kwargs):
""" get_reader(uri, format=None, mode='?', **kwargs)
Returns a :class:`.Reader` object which can be used to read data
and meta data from the specified file.
Parameters
----------
uri : {str, pathlib.Path, bytes, file}
The resource to load the image from, e.g. a filename, pathlib.Path,
http address or file object, see the docs for more info.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
mode : {'i', 'I', 'v', 'V', '?'}
Used to give the reader a hint on what the user expects (default "?"):
"i" for an image, "I" for multiple images, "v" for a volume,
"V" for multiple volumes, "?" for don't care.
kwargs : ...
Further keyword arguments are passed to the reader. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Create request object
request = Request(uri, "r" + mode, **kwargs)
# Get format
if format is not None:
format = formats[format]
else:
format = formats.search_read_format(request)
if format is None:
modename = MODENAMES.get(mode, mode)
raise ValueError(
"Could not find a format to read the specified file in %s mode" % modename
)
# Return its reader object
return format.get_reader(request)
def get_writer(uri, format=None, mode="?", **kwargs):
""" get_writer(uri, format=None, mode='?', **kwargs)
Returns a :class:`.Writer` object which can be used to write data
and meta data to the specified file.
Parameters
----------
uri : {str, pathlib.Path, file}
The resource to write the image to, e.g. a filename, pathlib.Path
or file object, see the docs for more info.
format : str
The format to use to write the file. By default imageio selects
the appropriate for you based on the filename.
mode : {'i', 'I', 'v', 'V', '?'}
Used to give the writer a hint on what the user expects (default '?'):
"i" for an image, "I" for multiple images, "v" for a volume,
"V" for multiple volumes, "?" for don't care.
kwargs : ...
Further keyword arguments are passed to the writer. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Signal extension when returning as bytes, needed by e.g. ffmpeg
if uri == RETURN_BYTES and isinstance(format, str):
uri = RETURN_BYTES + "." + format.strip(". ")
# Create request object
request = Request(uri, "w" + mode, **kwargs)
# Get format
if format is not None:
format = formats[format]
else:
format = formats.search_write_format(request)
if format is None:
modename = MODENAMES.get(mode, mode)
raise ValueError(
"Could not find a format to write the specified file in %s mode" % modename
)
# Return its writer object
return format.get_writer(request)
## Images
def imread(uri, format=None, **kwargs):
""" imread(uri, format=None, **kwargs)
Reads an image from the specified file. Returns a numpy array, which
comes with a dict of meta data at its 'meta' attribute.
Note that the image data is returned as-is, and may not always have
a dtype of uint8 (and thus may differ from what e.g. PIL returns).
Parameters
----------
uri : {str, pathlib.Path, bytes, file}
The resource to load the image from, e.g. a filename, pathlib.Path,
http address or file object, see the docs for more info.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the reader. See :func:`.help`
to see what arguments are available for a particular format.
"""
if "mode" in kwargs:
raise TypeError(
'Invalid keyword argument "mode", ' 'perhaps you mean "pilmode"?'
)
# Get reader and read first
reader = read(uri, format, "i", **kwargs)
with reader:
return reader.get_data(0)
def imwrite(uri, im, format=None, **kwargs):
""" imwrite(uri, im, format=None, **kwargs)
Write an image to the specified file.
Parameters
----------
uri : {str, pathlib.Path, file}
The resource to write the image to, e.g. a filename, pathlib.Path
or file object, see the docs for more info.
im : numpy.ndarray
The image data. Must be NxM, NxMx3 or NxMx4.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the writer. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Test image
imt = type(im)
im = np.asanyarray(im)
if not np.issubdtype(im.dtype, np.number):
raise ValueError("Image is not numeric, but {}.".format(imt.__name__))
elif im.ndim == 2:
pass
elif im.ndim == 3 and im.shape[2] in [1, 3, 4]:
pass
else:
raise ValueError("Image must be 2D (grayscale, RGB, or RGBA).")
# Get writer and write first
writer = get_writer(uri, format, "i", **kwargs)
with writer:
writer.append_data(im)
# Return a result if there is any
return writer.request.get_result()
## Multiple images
def mimread(uri, format=None, memtest=MEMTEST_DEFAULT_MIM, **kwargs):
""" mimread(uri, format=None, memtest="256MB", **kwargs)
Reads multiple images from the specified file. Returns a list of
numpy arrays, each with a dict of meta data at its 'meta' attribute.
Parameters
----------
uri : {str, pathlib.Path, bytes, file}
The resource to load the images from, e.g. a filename,pathlib.Path,
http address or file object, see the docs for more info.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
memtest : {bool, int, float, str}
If truthy, this function will raise an error if the resulting
list of images consumes greater than the amount of memory specified.
This is to protect the system from using so much memory that it needs
to resort to swapping, and thereby stall the computer. E.g.
``mimread('hunger_games.avi')``.
If the argument is a number, that will be used as the threshold number
of bytes.
If the argument is a string, it will be interpreted as a number of bytes with
SI/IEC prefixed units (e.g. '1kB', '250MiB', '80.3YB').
- Units are case sensitive
- k, M etc. represent a 1000-fold change, where Ki, Mi etc. represent 1024-fold
- The "B" is optional, but if present, must be capitalised
If the argument is True, the default will be used, for compatibility reasons.
Default: '256MB'
kwargs : ...
Further keyword arguments are passed to the reader. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Get reader
reader = read(uri, format, "I", **kwargs)
nbyte_limit = to_nbytes(memtest, MEMTEST_DEFAULT_MIM)
# Read
ims = []
nbytes = 0
for im in reader:
ims.append(im)
# Memory check
nbytes += im.nbytes
if nbyte_limit and nbytes > nbyte_limit:
ims[:] = [] # clear to free the memory
raise RuntimeError(
"imageio.mimread() has read over {}B of "
"image data.\nStopped to avoid memory problems."
" Use imageio.get_reader(), increase threshold, or memtest=False".format(
int(nbyte_limit)
)
)
return ims
def mimwrite(uri, ims, format=None, **kwargs):
""" mimwrite(uri, ims, format=None, **kwargs)
Write multiple images to the specified file.
Parameters
----------
uri : {str, pathlib.Path, file}
The resource to write the images to, e.g. a filename, pathlib.Path
or file object, see the docs for more info.
ims : sequence of numpy arrays
The image data. Each array must be NxM, NxMx3 or NxMx4.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the writer. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Get writer
writer = get_writer(uri, format, "I", **kwargs)
written = 0
with writer:
# Iterate over images (ims may be a generator)
for im in ims:
# Test image
imt = type(im)
im = np.asanyarray(im)
if not np.issubdtype(im.dtype, np.number):
raise ValueError("Image is not numeric, but {}.".format(imt.__name__))
elif im.ndim == 2:
pass
elif im.ndim == 3 and im.shape[2] in [1, 3, 4]:
pass
else:
raise ValueError("Image must be 2D " "(grayscale, RGB, or RGBA).")
# Add image
writer.append_data(im)
written += 1
# Check that something was written. Check after writing, because ims might
# be a generator. The damage is done, but we want to error when it happens.
if not written:
raise RuntimeError("Zero images were written.")
# Return a result if there is any
return writer.request.get_result()
## Volumes
def volread(uri, format=None, **kwargs):
""" volread(uri, format=None, **kwargs)
Reads a volume from the specified file. Returns a numpy array, which
comes with a dict of meta data at its 'meta' attribute.
Parameters
----------
uri : {str, pathlib.Path, bytes, file}
The resource to load the volume from, e.g. a filename, pathlib.Path,
http address or file object, see the docs for more info.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the reader. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Get reader and read first
reader = read(uri, format, "v", **kwargs)
with reader:
return reader.get_data(0)
def volwrite(uri, im, format=None, **kwargs):
""" volwrite(uri, vol, format=None, **kwargs)
Write a volume to the specified file.
Parameters
----------
uri : {str, pathlib.Path, file}
The resource to write the image to, e.g. a filename, pathlib.Path
or file object, see the docs for more info.
vol : numpy.ndarray
The image data. Must be NxMxL (or NxMxLxK if each voxel is a tuple).
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the writer. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Test image
imt = type(im)
im = np.asanyarray(im)
if not np.issubdtype(im.dtype, np.number):
raise ValueError("Image is not numeric, but {}.".format(imt.__name__))
elif im.ndim == 3:
pass
elif im.ndim == 4 and im.shape[3] < 32: # How large can a tuple be?
pass
else:
raise ValueError("Image must be 3D, or 4D if each voxel is a tuple.")
# Get writer and write first
writer = get_writer(uri, format, "v", **kwargs)
with writer:
writer.append_data(im)
# Return a result if there is any
return writer.request.get_result()
## Multiple volumes
def mvolread(uri, format=None, memtest=MEMTEST_DEFAULT_MVOL, **kwargs):
""" mvolread(uri, format=None, memtest='1GB', **kwargs)
Reads multiple volumes from the specified file. Returns a list of
numpy arrays, each with a dict of meta data at its 'meta' attribute.
Parameters
----------
uri : {str, pathlib.Path, bytes, file}
The resource to load the volumes from, e.g. a filename, pathlib.Path,
http address or file object, see the docs for more info.
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
memtest : {bool, int, float, str}
If truthy, this function will raise an error if the resulting
list of images consumes greater than the amount of memory specified.
This is to protect the system from using so much memory that it needs
to resort to swapping, and thereby stall the computer. E.g.
``mimread('hunger_games.avi')``.
If the argument is a number, that will be used as the threshold number
of bytes.
If the argument is a string, it will be interpreted as a number of bytes with
SI/IEC prefixed units (e.g. '1kB', '250MiB', '80.3YB').
- Units are case sensitive
- k, M etc. represent a 1000-fold change, where Ki, Mi etc. represent 1024-fold
- The "B" is optional, but if present, must be capitalised
If the argument is True, the default will be used, for compatibility reasons.
Default: '1GB'
kwargs : ...
Further keyword arguments are passed to the reader. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Get reader and read all
reader = read(uri, format, "V", **kwargs)
nbyte_limit = to_nbytes(memtest, MEMTEST_DEFAULT_MVOL)
ims = []
nbytes = 0
for im in reader:
ims.append(im)
# Memory check
nbytes += im.nbytes
if nbyte_limit and nbytes > nbyte_limit: # pragma: no cover
ims[:] = [] # clear to free the memory
raise RuntimeError(
"imageio.mvolread() has read over {}B of "
"image data.\nStopped to avoid memory problems."
" Use imageio.get_reader(), increase threshold, or memtest=False".format(
int(nbyte_limit)
)
)
return ims
def mvolwrite(uri, ims, format=None, **kwargs):
""" mvolwrite(uri, vols, format=None, **kwargs)
Write multiple volumes to the specified file.
Parameters
----------
uri : {str, pathlib.Path, file}
The resource to write the volumes to, e.g. a filename, pathlib.Path
or file object, see the docs for more info.
ims : sequence of numpy arrays
The image data. Each array must be NxMxL (or NxMxLxK if each
voxel is a tuple).
format : str
The format to use to read the file. By default imageio selects
the appropriate for you based on the filename and its contents.
kwargs : ...
Further keyword arguments are passed to the writer. See :func:`.help`
to see what arguments are available for a particular format.
"""
# Get writer
writer = get_writer(uri, format, "V", **kwargs)
written = 0
with writer:
# Iterate over images (ims may be a generator)
for im in ims:
# Test image
imt = type(im)
im = np.asanyarray(im)
if not np.issubdtype(im.dtype, np.number):
raise ValueError("Image is not numeric, but {}.".format(imt.__name__))
elif im.ndim == 3:
pass
elif im.ndim == 4 and im.shape[3] < 32:
pass # How large can a tuple be?
else:
raise ValueError("Image must be 3D, or 4D if each voxel is a tuple.")
# Add image
writer.append_data(im)
written += 1
# Check that something was written. Check after writing, because ims might
# be a generator. The damage is done, but we want to error when it happens.
if not written:
raise RuntimeError("Zero volumes were written.")
# Return a result if there is any
return writer.request.get_result()
## Aliases
read = get_reader
save = get_writer
imsave = imwrite
mimsave = mimwrite
volsave = volwrite
mvolsave = mvolwrite

View file

@ -0,0 +1,577 @@
# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
"""
Definition of the Request object, which acts as a kind of bridge between
what the user wants and what the plugins can.
"""
import os
from io import BytesIO
import zipfile
import tempfile
import shutil
from ..core import urlopen, get_remote_file
try:
from pathlib import Path
except ImportError:
Path = None
# URI types
URI_BYTES = 1
URI_FILE = 2
URI_FILENAME = 3
URI_ZIPPED = 4
URI_HTTP = 5
URI_FTP = 6
SPECIAL_READ_URIS = "<video", "<screen>", "<clipboard>"
# The user can use this string in a write call to get the data back as bytes.
RETURN_BYTES = "<bytes>"
# Example images that will be auto-downloaded
EXAMPLE_IMAGES = {
"astronaut.png": "Image of the astronaut Eileen Collins",
"camera.png": "Classic grayscale image of a photographer",
"checkerboard.png": "Black and white image of a chekerboard",
"wood.jpg": "A (repeatable) texture of wooden planks",
"bricks.jpg": "A (repeatable) texture of stone bricks",
"clock.png": "Photo of a clock with motion blur (Stefan van der Walt)",
"coffee.png": "Image of a cup of coffee (Rachel Michetti)",
"chelsea.png": "Image of Stefan's cat",
"wikkie.png": "Image of Almar's cat",
"coins.png": "Image showing greek coins from Pompeii",
"horse.png": "Image showing the silhouette of a horse (Andreas Preuss)",
"hubble_deep_field.png": "Photograph taken by Hubble telescope (NASA)",
"immunohistochemistry.png": "Immunohistochemical (IHC) staining",
"moon.png": "Image showing a portion of the surface of the moon",
"page.png": "A scanned page of text",
"text.png": "A photograph of handdrawn text",
"chelsea.zip": "The chelsea.png in a zipfile (for testing)",
"chelsea.bsdf": "The chelsea.png in a BSDF file(for testing)",
"newtonscradle.gif": "Animated GIF of a newton's cradle",
"cockatoo.mp4": "Video file of a cockatoo",
"stent.npz": "Volumetric image showing a stented abdominal aorta",
"meadow_cube.jpg": "A cubemap image of a meadow, e.g. to render a skybox.",
}
class Request(object):
""" Request(uri, mode, **kwargs)
Represents a request for reading or saving an image resource. This
object wraps information to that request and acts as an interface
for the plugins to several resources; it allows the user to read
from filenames, files, http, zipfiles, raw bytes, etc., but offer
a simple interface to the plugins via ``get_file()`` and
``get_local_filename()``.
For each read/write operation a single Request instance is used and passed
to the can_read/can_write method of a format, and subsequently to
the Reader/Writer class. This allows rudimentary passing of
information between different formats and between a format and
associated reader/writer.
parameters
----------
uri : {str, bytes, file}
The resource to load the image from.
mode : str
The first character is "r" or "w", indicating a read or write
request. The second character is used to indicate the kind of data:
"i" for an image, "I" for multiple images, "v" for a volume,
"V" for multiple volumes, "?" for don't care.
"""
def __init__(self, uri, mode, **kwargs):
# General
self._uri_type = None
self._filename = None
self._extension = None
self._kwargs = kwargs
self._result = None # Some write actions may have a result
# To handle the user-side
self._filename_zip = None # not None if a zipfile is used
self._bytes = None # Incoming bytes
self._zipfile = None # To store a zipfile instance (if used)
# To handle the plugin side
self._file = None # To store the file instance
self._file_is_local = False # whether the data needs to be copied at end
self._filename_local = None # not None if using tempfile on this FS
self._firstbytes = None # For easy header parsing
# To store formats that may be able to fulfil this request
# self._potential_formats = []
# Check mode
self._mode = mode
if not isinstance(mode, str):
raise ValueError("Request requires mode must be a string")
if not len(mode) == 2:
raise ValueError("Request requires mode to have two chars")
if mode[0] not in "rw":
raise ValueError('Request requires mode[0] to be "r" or "w"')
if mode[1] not in "iIvV?":
raise ValueError('Request requires mode[1] to be in "iIvV?"')
# Parse what was given
self._parse_uri(uri)
# Set extension
if self._filename is not None:
ext = self._filename
if self._filename.startswith(("http://", "https://", "ftp://", "ftps://")):
ext = ext.split("?")[0]
self._extension = "." + ext.split(".")[-1].lower()
def _parse_uri(self, uri):
""" Try to figure our what we were given
"""
is_read_request = self.mode[0] == "r"
is_write_request = self.mode[0] == "w"
if isinstance(uri, str):
# Explicit
if uri.startswith("imageio:"):
if is_write_request:
raise RuntimeError("Cannot write to the standard images.")
fn = uri.split(":", 1)[-1].lower()
fn, _, zip_part = fn.partition(".zip/")
if zip_part:
fn += ".zip"
if fn not in EXAMPLE_IMAGES:
raise ValueError("Unknown standard image %r." % fn)
self._uri_type = URI_FILENAME
self._filename = get_remote_file("images/" + fn, auto=True)
if zip_part:
self._filename += "/" + zip_part
elif uri.startswith("http://") or uri.startswith("https://"):
self._uri_type = URI_HTTP
self._filename = uri
elif uri.startswith("ftp://") or uri.startswith("ftps://"):
self._uri_type = URI_FTP
self._filename = uri
elif uri.startswith("file://"):
self._uri_type = URI_FILENAME
self._filename = uri[7:]
elif uri.startswith(SPECIAL_READ_URIS) and is_read_request:
self._uri_type = URI_BYTES
self._filename = uri
elif uri.startswith(RETURN_BYTES) and is_write_request:
self._uri_type = URI_BYTES
self._filename = uri
else:
self._uri_type = URI_FILENAME
self._filename = uri
elif isinstance(uri, memoryview) and is_read_request:
self._uri_type = URI_BYTES
self._filename = "<bytes>"
self._bytes = uri.tobytes()
elif isinstance(uri, bytes) and is_read_request:
self._uri_type = URI_BYTES
self._filename = "<bytes>"
self._bytes = uri
elif Path is not None and isinstance(uri, Path):
self._uri_type = URI_FILENAME
self._filename = str(uri)
# Files
elif is_read_request:
if hasattr(uri, "read") and hasattr(uri, "close"):
self._uri_type = URI_FILE
self._filename = "<file>"
self._file = uri # Data must be read from here
elif is_write_request:
if hasattr(uri, "write") and hasattr(uri, "close"):
self._uri_type = URI_FILE
self._filename = "<file>"
self._file = uri # Data must be written here
# Expand user dir
if self._uri_type == URI_FILENAME and self._filename.startswith("~"):
self._filename = os.path.expanduser(self._filename)
# Check if a zipfile
if self._uri_type == URI_FILENAME:
# Search for zip extension followed by a path separater
for needle in [".zip/", ".zip\\"]:
zip_i = self._filename.lower().find(needle)
if zip_i > 0:
zip_i += 4
zip_path = self._filename[:zip_i]
if is_write_request or os.path.isfile(zip_path):
self._uri_type = URI_ZIPPED
self._filename_zip = (
zip_path,
self._filename[zip_i:].lstrip("/\\"),
)
break
# Check if we could read it
if self._uri_type is None:
uri_r = repr(uri)
if len(uri_r) > 60:
uri_r = uri_r[:57] + "..."
raise IOError("Cannot understand given URI: %s." % uri_r)
# Check if this is supported
noWriting = [URI_HTTP, URI_FTP]
if is_write_request and self._uri_type in noWriting:
raise IOError("imageio does not support writing to http/ftp.")
# Deprecated way to load standard images, give a sensible error message
if is_read_request and self._uri_type in [URI_FILENAME, URI_ZIPPED]:
fn = self._filename
if self._filename_zip:
fn = self._filename_zip[0]
if (not os.path.exists(fn)) and (fn in EXAMPLE_IMAGES):
raise IOError(
"No such file: %r. This file looks like one of "
"the standard images, but from imageio 2.1, "
"standard images have to be specified using "
'"imageio:%s".' % (fn, fn)
)
# Make filename absolute
if self._uri_type in [URI_FILENAME, URI_ZIPPED]:
if self._filename_zip:
self._filename_zip = (
os.path.abspath(self._filename_zip[0]),
self._filename_zip[1],
)
else:
self._filename = os.path.abspath(self._filename)
# Check whether file name is valid
if self._uri_type in [URI_FILENAME, URI_ZIPPED]:
fn = self._filename
if self._filename_zip:
fn = self._filename_zip[0]
if is_read_request:
# Reading: check that the file exists (but is allowed a dir)
if not os.path.exists(fn):
raise FileNotFoundError("No such file: '%s'" % fn)
else:
# Writing: check that the directory to write to does exist
dn = os.path.dirname(fn)
if not os.path.exists(dn):
raise FileNotFoundError("The directory %r does not exist" % dn)
@property
def filename(self):
""" The uri for which reading/saving was requested. This
can be a filename, an http address, or other resource
identifier. Do not rely on the filename to obtain the data,
but use ``get_file()`` or ``get_local_filename()`` instead.
"""
return self._filename
@property
def extension(self):
""" The (lowercase) extension of the requested filename.
Suffixes in url's are stripped. Can be None if the request is
not based on a filename.
"""
return self._extension
@property
def mode(self):
""" The mode of the request. The first character is "r" or "w",
indicating a read or write request. The second character is
used to indicate the kind of data:
"i" for an image, "I" for multiple images, "v" for a volume,
"V" for multiple volumes, "?" for don't care.
"""
return self._mode
@property
def kwargs(self):
""" The dict of keyword arguments supplied by the user.
"""
return self._kwargs
## For obtaining data
def get_file(self):
""" get_file()
Get a file object for the resource associated with this request.
If this is a reading request, the file is in read mode,
otherwise in write mode. This method is not thread safe. Plugins
should not close the file when done.
This is the preferred way to read/write the data. But if a
format cannot handle file-like objects, they should use
``get_local_filename()``.
"""
want_to_write = self.mode[0] == "w"
# Is there already a file?
# Either _uri_type == URI_FILE, or we already opened the file,
# e.g. by using firstbytes
if self._file is not None:
return self._file
if self._uri_type == URI_BYTES:
if want_to_write:
# Create new file object, we catch the bytes in finish()
self._file = BytesIO()
self._file_is_local = True
else:
self._file = BytesIO(self._bytes)
elif self._uri_type == URI_FILENAME:
if want_to_write:
self._file = open(self.filename, "wb")
else:
self._file = open(self.filename, "rb")
elif self._uri_type == URI_ZIPPED:
# Get the correct filename
filename, name = self._filename_zip
if want_to_write:
# Create new file object, we catch the bytes in finish()
self._file = BytesIO()
self._file_is_local = True
else:
# Open zipfile and open new file object for specific file
self._zipfile = zipfile.ZipFile(filename, "r")
self._file = self._zipfile.open(name, "r")
self._file = SeekableFileObject(self._file)
elif self._uri_type in [URI_HTTP or URI_FTP]:
assert not want_to_write # This should have been tested in init
timeout = os.getenv('IMAGEIO_REQUEST_TIMEOUT')
if timeout is None or not timeout.isdigit():
timeout = 5
self._file = urlopen(self.filename, timeout=float(timeout))
self._file = SeekableFileObject(self._file)
return self._file
def get_local_filename(self):
""" get_local_filename()
If the filename is an existing file on this filesystem, return
that. Otherwise a temporary file is created on the local file
system which can be used by the format to read from or write to.
"""
if self._uri_type == URI_FILENAME:
return self._filename
else:
# Get filename
if self._uri_type in (URI_HTTP, URI_FTP):
ext = os.path.splitext(self._filename.split("?")[0])[1]
else:
ext = os.path.splitext(self._filename)[1]
self._filename_local = tempfile.mktemp(ext, "imageio_")
# Write stuff to it?
if self.mode[0] == "r":
with open(self._filename_local, "wb") as file:
shutil.copyfileobj(self.get_file(), file)
return self._filename_local
def finish(self):
""" finish()
For internal use (called when the context of the reader/writer
exits). Finishes this request. Close open files and process
results.
"""
if self.mode[0] == "w":
# See if we "own" the data and must put it somewhere
bytes = None
if self._filename_local:
with open(self._filename_local, "rb") as file:
bytes = file.read()
elif self._file_is_local:
bytes = self._file.getvalue()
# Put the data in the right place
if bytes is not None:
if self._uri_type == URI_BYTES:
self._result = bytes # Picked up by imread function
elif self._uri_type == URI_FILE:
self._file.write(bytes)
elif self._uri_type == URI_ZIPPED:
zf = zipfile.ZipFile(self._filename_zip[0], "a")
zf.writestr(self._filename_zip[1], bytes)
zf.close()
# elif self._uri_type == URI_FILENAME: -> is always direct
# elif self._uri_type == URI_FTP/HTTP: -> write not supported
# Close open files that we know of (and are responsible for)
if self._file and self._uri_type != URI_FILE:
self._file.close()
self._file = None
if self._zipfile:
self._zipfile.close()
self._zipfile = None
# Remove temp file
if self._filename_local:
try:
os.remove(self._filename_local)
except Exception: # pragma: no cover
pass
self._filename_local = None
# Detach so gc can clean even if a reference of self lingers
self._bytes = None
def get_result(self):
""" For internal use. In some situations a write action can have
a result (bytes data). That is obtained with this function.
"""
self._result, res = None, self._result
return res
@property
def firstbytes(self):
""" The first 256 bytes of the file. These can be used to
parse the header to determine the file-format.
"""
if self._firstbytes is None:
self._read_first_bytes()
return self._firstbytes
def _read_first_bytes(self, N=256):
if self._bytes is not None:
self._firstbytes = self._bytes[:N]
else:
# Prepare
try:
f = self.get_file()
except IOError:
if os.path.isdir(self.filename): # A directory, e.g. for DICOM
self._firstbytes = bytes()
return
raise
try:
i = f.tell()
except Exception:
i = None
# Read
self._firstbytes = read_n_bytes(f, N)
# Set back
try:
if i is None:
raise Exception("cannot seek with None")
f.seek(i)
except Exception:
# Prevent get_file() from reusing the file
self._file = None
# If the given URI was a file object, we have a problem,
if self._uri_type == URI_FILE:
raise IOError("Cannot seek back after getting firstbytes!")
def read_n_bytes(f, N):
""" read_n_bytes(file, n)
Read n bytes from the given file, or less if the file has less
bytes. Returns zero bytes if the file is closed.
"""
bb = bytes()
while len(bb) < N:
extra_bytes = f.read(N - len(bb))
if not extra_bytes:
break
bb += extra_bytes
return bb
class SeekableFileObject:
""" A readonly wrapper file object that add support for seeking, even if
the wrapped file object does not. The allows us to stream from http and
still use Pillow.
"""
def __init__(self, f):
self.f = f
self._i = 0 # >=0 but can exceed buffer
self._buffer = b""
self._have_all = False
self.closed = False
def read(self, n=None):
# Fix up n
if n is None:
pass
else:
n = int(n)
if n < 0:
n = None
# Can and must we read more?
if not self._have_all:
more = b""
if n is None:
more = self.f.read()
self._have_all = True
else:
want_i = self._i + n
want_more = want_i - len(self._buffer)
if want_more > 0:
more = self.f.read(want_more)
if len(more) < want_more:
self._have_all = True
self._buffer += more
# Read data from buffer and update pointer
if n is None:
res = self._buffer[self._i :]
else:
res = self._buffer[self._i : self._i + n]
self._i += len(res)
return res
def tell(self):
return self._i
def seek(self, i, mode=0):
# Mimic BytesIO behavior
# Get the absolute new position
i = int(i)
if mode == 0:
if i < 0:
raise ValueError("negative seek value " + str(i))
real_i = i
elif mode == 1:
real_i = max(0, self._i + i) # negative ok here
elif mode == 2:
if not self._have_all:
self.read()
real_i = max(0, len(self._buffer) + i)
else:
raise ValueError("invalid whence (%s, should be 0, 1 or 2)" % i)
# Read some?
if real_i <= len(self._buffer):
pass # no need to read
elif not self._have_all:
assert real_i > self._i # if we don't have all, _i cannot be > _buffer
self.read(real_i - self._i) # sets self._i
self._i = real_i
return self._i
def close(self):
self.closed = True
self.f.close()
def isatty(self):
return False
def seekable(self):
return True

View file

@ -0,0 +1,563 @@
# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
"""
Various utilities for imageio
"""
import os
import re
import struct
import sys
import time
import logging
logger = logging.getLogger("imageio")
import numpy as np
IS_PYPY = "__pypy__" in sys.builtin_module_names
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
def urlopen(*args, **kwargs):
""" Compatibility function for the urlopen function. Raises an
RuntimeError if urlopen could not be imported (which can occur in
frozen applications.
"""
try:
from urllib.request import urlopen
except ImportError:
raise RuntimeError("Could not import urlopen.")
return urlopen(*args, **kwargs)
def _precision_warn(p1, p2, extra=""):
t = (
"Lossy conversion from {} to {}. {} Convert image to {} prior to "
"saving to suppress this warning."
)
logger.warning(t.format(p1, p2, extra, p2))
def image_as_uint(im, bitdepth=None):
""" Convert the given image to uint (default: uint8)
If the dtype already matches the desired format, it is returned
as-is. If the image is float, and all values are between 0 and 1,
the values are multiplied by np.power(2.0, bitdepth). In all other
situations, the values are scaled such that the minimum value
becomes 0 and the maximum value becomes np.power(2.0, bitdepth)-1
(255 for 8-bit and 65535 for 16-bit).
"""
if not bitdepth:
bitdepth = 8
if not isinstance(im, np.ndarray):
raise ValueError("Image must be a numpy array")
if bitdepth == 8:
out_type = np.uint8
elif bitdepth == 16:
out_type = np.uint16
else:
raise ValueError("Bitdepth must be either 8 or 16")
dtype_str1 = str(im.dtype)
dtype_str2 = out_type.__name__
if (im.dtype == np.uint8 and bitdepth == 8) or (
im.dtype == np.uint16 and bitdepth == 16
):
# Already the correct format? Return as-is
return im
if dtype_str1.startswith("float") and np.nanmin(im) >= 0 and np.nanmax(im) <= 1:
_precision_warn(dtype_str1, dtype_str2, "Range [0, 1].")
im = im.astype(np.float64) * (np.power(2.0, bitdepth) - 1) + 0.499999999
elif im.dtype == np.uint16 and bitdepth == 8:
_precision_warn(dtype_str1, dtype_str2, "Losing 8 bits of resolution.")
im = np.right_shift(im, 8)
elif im.dtype == np.uint32:
_precision_warn(
dtype_str1,
dtype_str2,
"Losing {} bits of resolution.".format(32 - bitdepth),
)
im = np.right_shift(im, 32 - bitdepth)
elif im.dtype == np.uint64:
_precision_warn(
dtype_str1,
dtype_str2,
"Losing {} bits of resolution.".format(64 - bitdepth),
)
im = np.right_shift(im, 64 - bitdepth)
else:
mi = np.nanmin(im)
ma = np.nanmax(im)
if not np.isfinite(mi):
raise ValueError("Minimum image value is not finite")
if not np.isfinite(ma):
raise ValueError("Maximum image value is not finite")
if ma == mi:
return im.astype(out_type)
_precision_warn(dtype_str1, dtype_str2, "Range [{}, {}].".format(mi, ma))
# Now make float copy before we scale
im = im.astype("float64")
# Scale the values between 0 and 1 then multiply by the max value
im = (im - mi) / (ma - mi) * (np.power(2.0, bitdepth) - 1) + 0.499999999
assert np.nanmin(im) >= 0
assert np.nanmax(im) < np.power(2.0, bitdepth)
return im.astype(out_type)
class Array(np.ndarray):
""" Array(array, meta=None)
A subclass of np.ndarray that has a meta attribute. Get the dictionary
that contains the meta data using ``im.meta``. Convert to a plain numpy
array using ``np.asarray(im)``.
"""
def __new__(cls, array, meta=None):
# Check
if not isinstance(array, np.ndarray):
raise ValueError("Array expects a numpy array.")
if not (meta is None or isinstance(meta, dict)):
raise ValueError("Array expects meta data to be a dict.")
# Convert and return
meta = meta if meta is not None else {}
try:
ob = array.view(cls)
except AttributeError: # pragma: no cover
# Just return the original; no metadata on the array in Pypy!
return array
ob._copy_meta(meta)
return ob
def _copy_meta(self, meta):
""" Make a 2-level deep copy of the meta dictionary.
"""
self._meta = Dict()
for key, val in meta.items():
if isinstance(val, dict):
val = Dict(val) # Copy this level
self._meta[key] = val
@property
def meta(self):
""" The dict with the meta data of this image.
"""
return self._meta
def __array_finalize__(self, ob):
""" So the meta info is maintained when doing calculations with
the array.
"""
if isinstance(ob, Array):
self._copy_meta(ob.meta)
else:
self._copy_meta({})
def __array_wrap__(self, out, context=None):
""" So that we return a native numpy array (or scalar) when a
reducting ufunc is applied (such as sum(), std(), etc.)
"""
if not out.shape:
return out.dtype.type(out) # Scalar
elif out.shape != self.shape:
return out.view(type=np.ndarray)
else:
return out # Type Array
Image = Array # Alias for backwards compatibility
def asarray(a):
""" Pypy-safe version of np.asarray. Pypy's np.asarray consumes a
*lot* of memory if the given array is an ndarray subclass. This
function does not.
"""
if isinstance(a, np.ndarray):
if IS_PYPY: # pragma: no cover
a = a.copy() # pypy has issues with base views
plain = a.view(type=np.ndarray)
return plain
return np.asarray(a)
from collections import OrderedDict
class Dict(OrderedDict):
""" A dict in which the keys can be get and set as if they were
attributes. Very convenient in combination with autocompletion.
This Dict still behaves as much as possible as a normal dict, and
keys can be anything that are otherwise valid keys. However,
keys that are not valid identifiers or that are names of the dict
class (such as 'items' and 'copy') cannot be get/set as attributes.
"""
__reserved_names__ = dir(OrderedDict()) # Also from OrderedDict
__pure_names__ = dir(dict())
def __getattribute__(self, key):
try:
return object.__getattribute__(self, key)
except AttributeError:
if key in self:
return self[key]
else:
raise
def __setattr__(self, key, val):
if key in Dict.__reserved_names__:
# Either let OrderedDict do its work, or disallow
if key not in Dict.__pure_names__:
return OrderedDict.__setattr__(self, key, val)
else:
raise AttributeError(
"Reserved name, this key can only "
+ "be set via ``d[%r] = X``" % key
)
else:
# if isinstance(val, dict): val = Dict(val) -> no, makes a copy!
self[key] = val
def __dir__(self):
isidentifier = lambda x: bool(re.match(r"[a-z_]\w*$", x, re.I))
names = [k for k in self.keys() if (isinstance(k, str) and isidentifier(k))]
return Dict.__reserved_names__ + names
class BaseProgressIndicator(object):
""" BaseProgressIndicator(name)
A progress indicator helps display the progres of a task to the
user. Progress can be pending, running, finished or failed.
Each task has:
* a name - a short description of what needs to be done.
* an action - the current action in performing the task (e.g. a subtask)
* progress - how far the task is completed
* max - max number of progress units. If 0, the progress is indefinite
* unit - the units in which the progress is counted
* status - 0: pending, 1: in progress, 2: finished, 3: failed
This class defines an abstract interface. Subclasses should implement
_start, _stop, _update_progress(progressText), _write(message).
"""
def __init__(self, name):
self._name = name
self._action = ""
self._unit = ""
self._max = 0
self._status = 0
self._last_progress_update = 0
def start(self, action="", unit="", max=0):
""" start(action='', unit='', max=0)
Start the progress. Optionally specify an action, a unit,
and a maxium progress value.
"""
if self._status == 1:
self.finish()
self._action = action
self._unit = unit
self._max = max
#
self._progress = 0
self._status = 1
self._start()
def status(self):
""" status()
Get the status of the progress - 0: pending, 1: in progress,
2: finished, 3: failed
"""
return self._status
def set_progress(self, progress=0, force=False):
""" set_progress(progress=0, force=False)
Set the current progress. To avoid unnecessary progress updates
this will only have a visual effect if the time since the last
update is > 0.1 seconds, or if force is True.
"""
self._progress = progress
# Update or not?
if not (force or (time.time() - self._last_progress_update > 0.1)):
return
self._last_progress_update = time.time()
# Compose new string
unit = self._unit or ""
progressText = ""
if unit == "%":
progressText = "%2.1f%%" % progress
elif self._max > 0:
percent = 100 * float(progress) / self._max
progressText = "%i/%i %s (%2.1f%%)" % (progress, self._max, unit, percent)
elif progress > 0:
if isinstance(progress, float):
progressText = "%0.4g %s" % (progress, unit)
else:
progressText = "%i %s" % (progress, unit)
# Update
self._update_progress(progressText)
def increase_progress(self, extra_progress):
""" increase_progress(extra_progress)
Increase the progress by a certain amount.
"""
self.set_progress(self._progress + extra_progress)
def finish(self, message=None):
""" finish(message=None)
Finish the progress, optionally specifying a message. This will
not set the progress to the maximum.
"""
self.set_progress(self._progress, True) # fore update
self._status = 2
self._stop()
if message is not None:
self._write(message)
def fail(self, message=None):
""" fail(message=None)
Stop the progress with a failure, optionally specifying a message.
"""
self.set_progress(self._progress, True) # fore update
self._status = 3
self._stop()
message = "FAIL " + (message or "")
self._write(message)
def write(self, message):
""" write(message)
Write a message during progress (such as a warning).
"""
if self.__class__ == BaseProgressIndicator:
# When this class is used as a dummy, print explicit message
print(message)
else:
return self._write(message)
# Implementing classes should implement these
def _start(self):
pass
def _stop(self):
pass
def _update_progress(self, progressText):
pass
def _write(self, message):
pass
class StdoutProgressIndicator(BaseProgressIndicator):
""" StdoutProgressIndicator(name)
A progress indicator that shows the progress in stdout. It
assumes that the tty can appropriately deal with backspace
characters.
"""
def _start(self):
self._chars_prefix, self._chars = "", ""
# Write message
if self._action:
self._chars_prefix = "%s (%s): " % (self._name, self._action)
else:
self._chars_prefix = "%s: " % self._name
sys.stdout.write(self._chars_prefix)
sys.stdout.flush()
def _update_progress(self, progressText):
# If progress is unknown, at least make something move
if not progressText:
i1, i2, i3, i4 = "-\\|/"
M = {i1: i2, i2: i3, i3: i4, i4: i1}
progressText = M.get(self._chars, i1)
# Store new string and write
delChars = "\b" * len(self._chars)
self._chars = progressText
sys.stdout.write(delChars + self._chars)
sys.stdout.flush()
def _stop(self):
self._chars = self._chars_prefix = ""
sys.stdout.write("\n")
sys.stdout.flush()
def _write(self, message):
# Write message
delChars = "\b" * len(self._chars_prefix + self._chars)
sys.stdout.write(delChars + " " + message + "\n")
# Reprint progress text
sys.stdout.write(self._chars_prefix + self._chars)
sys.stdout.flush()
# From pyzolib/paths.py (https://bitbucket.org/pyzo/pyzolib/src/tip/paths.py)
def appdata_dir(appname=None, roaming=False):
""" appdata_dir(appname=None, roaming=False)
Get the path to the application directory, where applications are allowed
to write user specific files (e.g. configurations). For non-user specific
data, consider using common_appdata_dir().
If appname is given, a subdir is appended (and created if necessary).
If roaming is True, will prefer a roaming directory (Windows Vista/7).
"""
# Define default user directory
userDir = os.getenv("IMAGEIO_USERDIR", None)
if userDir is None:
userDir = os.path.expanduser("~")
if not os.path.isdir(userDir): # pragma: no cover
userDir = "/var/tmp" # issue #54
# Get system app data dir
path = None
if sys.platform.startswith("win"):
path1, path2 = os.getenv("LOCALAPPDATA"), os.getenv("APPDATA")
path = (path2 or path1) if roaming else (path1 or path2)
elif sys.platform.startswith("darwin"):
path = os.path.join(userDir, "Library", "Application Support")
# On Linux and as fallback
if not (path and os.path.isdir(path)):
path = userDir
# Maybe we should store things local to the executable (in case of a
# portable distro or a frozen application that wants to be portable)
prefix = sys.prefix
if getattr(sys, "frozen", None):
prefix = os.path.abspath(os.path.dirname(sys.executable))
for reldir in ("settings", "../settings"):
localpath = os.path.abspath(os.path.join(prefix, reldir))
if os.path.isdir(localpath): # pragma: no cover
try:
open(os.path.join(localpath, "test.write"), "wb").close()
os.remove(os.path.join(localpath, "test.write"))
except IOError:
pass # We cannot write in this directory
else:
path = localpath
break
# Get path specific for this app
if appname:
if path == userDir:
appname = "." + appname.lstrip(".") # Make it a hidden directory
path = os.path.join(path, appname)
if not os.path.isdir(path): # pragma: no cover
os.makedirs(path, exist_ok=True)
# Done
return path
def resource_dirs():
""" resource_dirs()
Get a list of directories where imageio resources may be located.
The first directory in this list is the "resources" directory in
the package itself. The second directory is the appdata directory
(~/.imageio on Linux). The list further contains the application
directory (for frozen apps), and may include additional directories
in the future.
"""
dirs = [resource_package_dir()]
# Resource dir baked in the package.
# Appdata directory
try:
dirs.append(appdata_dir("imageio"))
except Exception: # pragma: no cover
pass # The home dir may not be writable
# Directory where the app is located (mainly for frozen apps)
if getattr(sys, "frozen", None):
dirs.append(os.path.abspath(os.path.dirname(sys.executable)))
elif sys.path and sys.path[0]:
dirs.append(os.path.abspath(sys.path[0]))
return dirs
def resource_package_dir():
""" package_dir
Get the resources directory in the imageio package installation
directory.
Notes
-----
This is a convenience method that is used by `resource_dirs` and
imageio entry point scripts.
"""
# Make pkg_resources optional if setuptools is not available
try:
# Avoid importing pkg_resources in the top level due to how slow it is
# https://github.com/pypa/setuptools/issues/510
import pkg_resources
except ImportError:
pkg_resources = None
if pkg_resources:
# The directory returned by `pkg_resources.resource_filename`
# also works with eggs.
pdir = pkg_resources.resource_filename("imageio", "resources")
else:
# If setuptools is not available, use fallback
pdir = os.path.abspath(os.path.join(THIS_DIR, "..", "resources"))
return pdir
def get_platform():
""" get_platform()
Get a string that specifies the platform more specific than
sys.platform does. The result can be: linux32, linux64, win32,
win64, osx32, osx64. Other platforms may be added in the future.
"""
# Get platform
if sys.platform.startswith("linux"):
plat = "linux%i"
elif sys.platform.startswith("win"):
plat = "win%i"
elif sys.platform.startswith("darwin"):
plat = "osx%i"
elif sys.platform.startswith("freebsd"):
plat = "freebsd%i"
else: # pragma: no cover
return None
return plat % (struct.calcsize("P") * 8) # 32 or 64 bits
def has_module(module_name):
"""Check to see if a python module is available.
"""
if sys.version_info > (3, 4):
import importlib
name_parts = module_name.split(".")
for i in range(len(name_parts)):
if importlib.util.find_spec(".".join(name_parts[: i + 1])) is None:
return False
return True
else: # pragma: no cover
import imp
try:
imp.find_module(module_name)
except ImportError:
return False
return True