Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
16
venv/Lib/site-packages/imageio/core/__init__.py
Normal file
16
venv/Lib/site-packages/imageio/core/__init__.py
Normal 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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
247
venv/Lib/site-packages/imageio/core/fetching.py
Normal file
247
venv/Lib/site-packages/imageio/core/fetching.py
Normal 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"
|
161
venv/Lib/site-packages/imageio/core/findlib.py
Normal file
161
venv/Lib/site-packages/imageio/core/findlib.py
Normal 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
|
735
venv/Lib/site-packages/imageio/core/format.py
Normal file
735
venv/Lib/site-packages/imageio/core/format.py
Normal 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)
|
625
venv/Lib/site-packages/imageio/core/functions.py
Normal file
625
venv/Lib/site-packages/imageio/core/functions.py
Normal 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
|
577
venv/Lib/site-packages/imageio/core/request.py
Normal file
577
venv/Lib/site-packages/imageio/core/request.py
Normal 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
|
563
venv/Lib/site-packages/imageio/core/util.py
Normal file
563
venv/Lib/site-packages/imageio/core/util.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue