| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- 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)
|