File: imopen.py

package info (click to toggle)
python-imageio 2.37.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,016 kB
  • sloc: python: 26,044; makefile: 138
file content (281 lines) | stat: -rw-r--r-- 9,752 bytes parent folder | download
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)