1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
|
from pathlib import Path
import warnings
from ..config import known_plugins
from ..config.extensions import known_extensions
from .request import (
SPECIAL_READ_URIS,
URI_FILENAME,
InitializationError,
IOMode,
Request,
)
def imopen(
uri,
io_mode,
*,
plugin=None,
extension=None,
format_hint=None,
legacy_mode=False,
**kwargs,
):
"""Open an ImageResource.
.. warning::
This warning is for pypy users. If you are not using a context manager,
remember to deconstruct the returned plugin to avoid leaking the file
handle to an unclosed file.
Parameters
----------
uri : str or pathlib.Path or bytes or file or Request
The :doc:`ImageResource <../../user_guide/requests>` to load the
image from.
io_mode : str
The mode in which the file is opened. Possible values are::
``r`` - open the file for reading
``w`` - open the file for writing
Depreciated since v2.9:
A second character can be added to give the reader a hint on what
the user expects. This will be ignored by new plugins and will
only have an effect on legacy plugins. Possible values are::
``i`` for a single image,
``I`` for multiple images,
``v`` for a single volume,
``V`` for multiple volumes,
``?`` for don't care
plugin : str, Plugin, or None
The plugin to use. If set to None imopen will perform a
search for a matching plugin. If not None, this takes priority over
the provided format hint.
extension : str
If not None, treat the provided ImageResource as if it had the given
extension. This affects the order in which backends are considered, and
when writing this may also influence the format used when encoding.
format_hint : str
Deprecated. Use `extension` instead.
legacy_mode : bool
If true use the v2 behavior when searching for a suitable
plugin. This will ignore v3 plugins and will check ``plugin``
against known extensions if no plugin with the given name can be found.
**kwargs : Any
Additional keyword arguments will be passed to the plugin upon
construction.
Notes
-----
Registered plugins are controlled via the ``known_plugins`` dict in
``imageio.config``.
Passing a ``Request`` as the uri is only supported if ``legacy_mode``
is ``True``. In this case ``io_mode`` is ignored.
Using the kwarg ``format_hint`` does not enforce the given format. It merely
provides a `hint` to the selection process and plugin. The selection
processes uses this hint for optimization; however, a plugin's decision how
to read a ImageResource will - typically - still be based on the content of
the resource.
Examples
--------
>>> import imageio.v3 as iio
>>> with iio.imopen("/path/to/image.png", "r") as file:
>>> im = file.read()
>>> with iio.imopen("/path/to/output.jpg", "w") as file:
>>> file.write(im)
"""
if isinstance(uri, Request) and legacy_mode:
warnings.warn(
"`iio.core.Request` is a low-level object and using it"
" directly as input to `imopen` is discouraged. This will raise"
" an exception in ImageIO v3.",
DeprecationWarning,
stacklevel=2,
)
request = uri
uri = request.raw_uri
io_mode = request.mode.io_mode
request.format_hint = format_hint
else:
request = Request(uri, io_mode, format_hint=format_hint, extension=extension)
source = "<bytes>" if isinstance(uri, bytes) else uri
# fast-path based on plugin
# (except in legacy mode)
if plugin is not None:
if isinstance(plugin, str):
try:
config = known_plugins[plugin]
except KeyError:
request.finish()
raise ValueError(
f"`{plugin}` is not a registered plugin name."
) from None
def loader(request, **kwargs):
return config.plugin_class(request, **kwargs)
else:
def loader(request, **kwargs):
return plugin(request, **kwargs)
try:
return loader(request, **kwargs)
except InitializationError as class_specific:
err_from = class_specific
err_type = RuntimeError if legacy_mode else IOError
err_msg = f"`{plugin}` can not handle the given uri."
except ImportError:
err_from = None
err_type = ImportError
err_msg = (
f"The `{config.name}` plugin is not installed. "
f"Use `pip install imageio[{config.install_name}]` to install it."
)
except Exception as generic_error:
err_from = generic_error
err_type = IOError
err_msg = f"An unknown error occurred while initializing plugin `{plugin}`."
request.finish()
raise err_type(err_msg) from err_from
# fast-path based on format_hint
if request.format_hint is not None:
for candidate_format in known_extensions[format_hint]:
for plugin_name in candidate_format.priority:
config = known_plugins[plugin_name]
try:
candidate_plugin = config.plugin_class
except ImportError:
# not installed
continue
try:
plugin_instance = candidate_plugin(request, **kwargs)
except InitializationError:
# file extension doesn't match file type
continue
return plugin_instance
else:
resource = (
"<bytes>" if isinstance(request.raw_uri, bytes) else request.raw_uri
)
warnings.warn(f"`{resource}` can not be opened as a `{format_hint}` file.")
# fast-path based on file extension
if request.extension in known_extensions:
for candidate_format in known_extensions[request.extension]:
for plugin_name in candidate_format.priority:
config = known_plugins[plugin_name]
try:
candidate_plugin = config.plugin_class
except ImportError:
# not installed
continue
try:
plugin_instance = candidate_plugin(request, **kwargs)
except InitializationError:
# file extension doesn't match file type
continue
return plugin_instance
# error out for read-only special targets
# this is hacky; can we come up with a better solution for this?
if request.mode.io_mode == IOMode.write:
if isinstance(uri, str) and uri.startswith(SPECIAL_READ_URIS):
request.finish()
err_type = ValueError if legacy_mode else IOError
err_msg = f"`{source}` is read-only."
raise err_type(err_msg)
# error out for directories
# this is a bit hacky and should be cleaned once we decide
# how to gracefully handle DICOM
if request._uri_type == URI_FILENAME and Path(request.raw_uri).is_dir():
request.finish()
err_type = ValueError if legacy_mode else IOError
err_msg = (
"ImageIO does not generally support reading folders. "
"Limited support may be available via specific plugins. "
"Specify the plugin explicitly using the `plugin` kwarg, e.g. `plugin='DICOM'`"
)
raise err_type(err_msg)
# close the current request here and use fresh/new ones while trying each
# plugin This is slow (means potentially reopening a resource several
# times), but should only happen rarely because this is the fallback if all
# else fails.
request.finish()
# fallback option: try all plugins
for config in known_plugins.values():
# each plugin gets its own request
request = Request(uri, io_mode, format_hint=format_hint)
try:
plugin_instance = config.plugin_class(request, **kwargs)
except InitializationError:
continue
except ImportError:
continue
else:
return plugin_instance
err_type = ValueError if legacy_mode else IOError
err_msg = f"Could not find a backend to open `{source}`` with iomode `{io_mode}`."
# check if a missing plugin could help
if request.extension in known_extensions:
missing_plugins = list()
formats = known_extensions[request.extension]
plugin_names = [
plugin for file_format in formats for plugin in file_format.priority
]
for name in plugin_names:
config = known_plugins[name]
try:
config.plugin_class
continue
except ImportError:
missing_plugins.append(config)
if len(missing_plugins) > 0:
install_candidates = "\n".join(
[
(
f" {config.name}: "
f"pip install imageio[{config.install_name}]"
)
for config in missing_plugins
]
)
err_msg += (
"\nBased on the extension, the following plugins might add capable backends:\n"
f"{install_candidates}"
)
request.finish()
raise err_type(err_msg)
|