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
|
""" Read/Write images using rawpy.
rawpy is an easy-to-use Python wrapper for the LibRaw library.
It also contains some extra functionality for finding and repairing hot/dead pixels.
"""
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import rawpy
import numpy as np
from ..core.request import URI_BYTES, InitializationError, IOMode, Request
from ..core.v3_plugin_api import ImageProperties, PluginV3
from ..typing import ArrayLike
class RawPyPlugin(PluginV3):
"""A class representing the rawpy plugin.
Methods
-------
.. autosummary::
:toctree: _plugins/rawpy
RawPyPlugin.read
"""
def __init__(self, request: Request) -> None:
"""Instantiates a new rawpy plugin object
Parameters
----------
request: Request
A request object representing the resource to be operated on.
"""
super().__init__(request)
self._image_file = None
if request.mode.io_mode == IOMode.read:
try:
self._image_file = rawpy.imread(request.get_file())
except (
rawpy.NotSupportedError,
rawpy.LibRawFileUnsupportedError,
rawpy.LibRawIOError,
):
if request._uri_type == URI_BYTES:
raise InitializationError(
"RawPy can not read the provided bytes."
) from None
else:
raise InitializationError(
f"RawPy can not read {request.raw_uri}."
) from None
elif request.mode.io_mode == IOMode.write:
raise InitializationError("RawPy does not support writing.") from None
def close(self) -> None:
if self._image_file:
self._image_file.close()
self._request.finish()
def read(self, *, index: int = 0, **kwargs) -> np.ndarray:
"""Read Raw Image.
Returns
-------
nd_image: ndarray
The image data
"""
nd_image: np.ndarray
try:
nd_image = self._image_file.postprocess(**kwargs)
except Exception:
pass
if index is Ellipsis:
nd_image = nd_image[None, ...]
return nd_image
def write(self, ndimage: Union[ArrayLike, List[ArrayLike]]) -> Optional[bytes]:
"""RawPy does not support writing."""
raise NotImplementedError()
def iter(self) -> Iterator[np.ndarray]:
"""Load the image.
Returns
-------
nd_image: ndarray
The image data
"""
try:
yield self.read()
except Exception:
pass
def metadata(
self, index: int = None, exclude_applied: bool = True
) -> Dict[str, Any]:
"""Read ndimage metadata.
Parameters
----------
exclude_applied : bool
If True, exclude metadata fields that are applied to the image while
reading. For example, if the binary data contains a rotation flag,
the image is rotated by default and the rotation flag is excluded
from the metadata to avoid confusion.
Returns
-------
metadata : dict
A dictionary of format-specific metadata.
"""
metadata = {}
image_size = self._image_file.sizes
metadata["black_level_per_channel"] = self._image_file.black_level_per_channel
metadata["camera_white_level_per_channel"] = (
self._image_file.camera_white_level_per_channel
)
metadata["color_desc"] = self._image_file.color_desc
metadata["color_matrix"] = self._image_file.color_matrix
metadata["daylight_whitebalance"] = self._image_file.daylight_whitebalance
metadata["dtype"] = self._image_file.raw_image.dtype
metadata["flip"] = image_size.flip
metadata["num_colors"] = self._image_file.num_colors
metadata["tone_curve"] = self._image_file.tone_curve
metadata["width"] = image_size.width
metadata["height"] = image_size.height
metadata["raw_width"] = image_size.raw_width
metadata["raw_height"] = image_size.raw_height
metadata["raw_shape"] = self._image_file.raw_image.shape
metadata["iwidth"] = image_size.iwidth
metadata["iheight"] = image_size.iheight
metadata["pixel_aspect"] = image_size.pixel_aspect
metadata["white_level"] = self._image_file.white_level
if exclude_applied:
metadata.pop("black_level_per_channel", None)
metadata.pop("camera_white_level_per_channel", None)
metadata.pop("color_desc", None)
metadata.pop("color_matrix", None)
metadata.pop("daylight_whitebalance", None)
metadata.pop("dtype", None)
metadata.pop("flip", None)
metadata.pop("num_colors", None)
metadata.pop("tone_curve", None)
metadata.pop("raw_width", None)
metadata.pop("raw_height", None)
metadata.pop("raw_shape", None)
metadata.pop("iwidth", None)
metadata.pop("iheight", None)
metadata.pop("white_level", None)
return metadata
def properties(self, index: int = None) -> ImageProperties:
"""Standardized ndimage metadata
Returns
-------
properties : ImageProperties
A dataclass filled with standardized image metadata.
Notes
-----
This does not decode pixel data and is fast for large images.
"""
ImageSize = self._image_file.sizes
width: int = ImageSize.width
height: int = ImageSize.height
shape: Tuple[int, ...] = (height, width)
dtype = self._image_file.raw_image.dtype
return ImageProperties(shape=shape, dtype=dtype)
|