1""" Read/Write images using rawpy.
2
3rawpy is an easy-to-use Python wrapper for the LibRaw library.
4It also contains some extra functionality for finding and repairing hot/dead pixels.
5"""
6
7from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
8import rawpy
9import numpy as np
10
11from ..core.request import URI_BYTES, InitializationError, IOMode, Request
12from ..core.v3_plugin_api import ImageProperties, PluginV3
13from ..typing import ArrayLike
14
15
16class RawPyPlugin(PluginV3):
17 """A class representing the rawpy plugin.
18
19 Methods
20 -------
21
22 .. autosummary::
23 :toctree: _plugins/rawpy
24
25 RawPyPlugin.read
26 """
27
28 def __init__(self, request: Request) -> None:
29 """Instantiates a new rawpy plugin object
30
31 Parameters
32 ----------
33 request: Request
34 A request object representing the resource to be operated on.
35 """
36
37 super().__init__(request)
38
39 self._image_file = None
40
41 if request.mode.io_mode == IOMode.read:
42 try:
43 self._image_file = rawpy.imread(request.get_file())
44 except (
45 rawpy.NotSupportedError,
46 rawpy.LibRawFileUnsupportedError,
47 rawpy.LibRawIOError,
48 ):
49 if request._uri_type == URI_BYTES:
50 raise InitializationError(
51 "RawPy can not read the provided bytes."
52 ) from None
53 else:
54 raise InitializationError(
55 f"RawPy can not read {request.raw_uri}."
56 ) from None
57 elif request.mode.io_mode == IOMode.write:
58 raise InitializationError("RawPy does not support writing.") from None
59
60 def close(self) -> None:
61 if self._image_file:
62 self._image_file.close()
63
64 self._request.finish()
65
66 def read(self, *, index: int = 0, **kwargs) -> np.ndarray:
67 """Read Raw Image.
68
69 Returns
70 -------
71 nd_image: ndarray
72 The image data
73 """
74
75 nd_image: np.ndarray
76
77 try:
78 nd_image = self._image_file.postprocess(**kwargs)
79 except Exception:
80 pass
81
82 if index is Ellipsis:
83 nd_image = nd_image[None, ...]
84
85 return nd_image
86
87 def write(self, ndimage: Union[ArrayLike, List[ArrayLike]]) -> Optional[bytes]:
88 """RawPy does not support writing."""
89 raise NotImplementedError()
90
91 def iter(self) -> Iterator[np.ndarray]:
92 """Load the image.
93
94 Returns
95 -------
96 nd_image: ndarray
97 The image data
98 """
99
100 try:
101 yield self.read()
102 except Exception:
103 pass
104
105 def metadata(
106 self, index: int = None, exclude_applied: bool = True
107 ) -> Dict[str, Any]:
108 """Read ndimage metadata.
109
110 Parameters
111 ----------
112 exclude_applied : bool
113 If True, exclude metadata fields that are applied to the image while
114 reading. For example, if the binary data contains a rotation flag,
115 the image is rotated by default and the rotation flag is excluded
116 from the metadata to avoid confusion.
117
118 Returns
119 -------
120 metadata : dict
121 A dictionary of format-specific metadata.
122
123 """
124
125 metadata = {}
126
127 image_size = self._image_file.sizes
128
129 metadata["black_level_per_channel"] = self._image_file.black_level_per_channel
130 metadata["camera_white_level_per_channel"] = (
131 self._image_file.camera_white_level_per_channel
132 )
133 metadata["color_desc"] = self._image_file.color_desc
134 metadata["color_matrix"] = self._image_file.color_matrix
135 metadata["daylight_whitebalance"] = self._image_file.daylight_whitebalance
136 metadata["dtype"] = self._image_file.raw_image.dtype
137 metadata["flip"] = image_size.flip
138 metadata["num_colors"] = self._image_file.num_colors
139 metadata["tone_curve"] = self._image_file.tone_curve
140 metadata["width"] = image_size.width
141 metadata["height"] = image_size.height
142 metadata["raw_width"] = image_size.raw_width
143 metadata["raw_height"] = image_size.raw_height
144 metadata["raw_shape"] = self._image_file.raw_image.shape
145 metadata["iwidth"] = image_size.iwidth
146 metadata["iheight"] = image_size.iheight
147 metadata["pixel_aspect"] = image_size.pixel_aspect
148 metadata["white_level"] = self._image_file.white_level
149
150 if exclude_applied:
151 metadata.pop("black_level_per_channel", None)
152 metadata.pop("camera_white_level_per_channel", None)
153 metadata.pop("color_desc", None)
154 metadata.pop("color_matrix", None)
155 metadata.pop("daylight_whitebalance", None)
156 metadata.pop("dtype", None)
157 metadata.pop("flip", None)
158 metadata.pop("num_colors", None)
159 metadata.pop("tone_curve", None)
160 metadata.pop("raw_width", None)
161 metadata.pop("raw_height", None)
162 metadata.pop("raw_shape", None)
163 metadata.pop("iwidth", None)
164 metadata.pop("iheight", None)
165 metadata.pop("white_level", None)
166
167 return metadata
168
169 def properties(self, index: int = None) -> ImageProperties:
170 """Standardized ndimage metadata
171
172 Returns
173 -------
174 properties : ImageProperties
175 A dataclass filled with standardized image metadata.
176
177 Notes
178 -----
179 This does not decode pixel data and is fast for large images.
180
181 """
182
183 ImageSize = self._image_file.sizes
184
185 width: int = ImageSize.width
186 height: int = ImageSize.height
187 shape: Tuple[int, ...] = (height, width)
188
189 dtype = self._image_file.raw_image.dtype
190
191 return ImageProperties(shape=shape, dtype=dtype)