192 lines
5.8 KiB
Python
192 lines
5.8 KiB
Python
""" 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)
|