rawpy.py 5.8 KB

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