44from pathlib import Path
55
66import numpy as np
7- import PIL
8- import PIL .Image
97
108from dreadnode .data_types .base import DataType
119
10+ if t .TYPE_CHECKING :
11+ import numpy as np
12+
1213ImageDataType = t .Union [t .Any , "np.ndarray[t.Any, t.Any]" ]
1314ImageDataOrPathType = str | Path | bytes | ImageDataType
1415
1516
17+ def check_imports () -> None :
18+ try :
19+ import PIL # type: ignore[import-not-found]
20+ except ImportError as e :
21+ raise ImportError (
22+ "Image processing requires Pillow. Install with: pip install dreadnode[multimodal]"
23+ ) from e
24+
25+ try :
26+ import numpy as np # type: ignore[import-not-found]
27+ except ImportError as e :
28+ raise ImportError (
29+ "Image processing requires NumPy. Install with: pip install dreadnode[multimodal]"
30+ ) from e
31+
32+
1633class Image (DataType ):
1734 """
1835 Image media type for Dreadnode logging.
@@ -45,6 +62,7 @@ def __init__(
4562 caption: Optional caption for the image
4663 format: Optional format to use when saving (png, jpg, etc.)
4764 """
65+ check_imports ()
4866 self ._data = data
4967 self ._mode = mode
5068 self ._caption = caption
@@ -66,6 +84,8 @@ def _process_image_data(self) -> tuple[bytes, str, str | None, int | None, int |
6684 Returns:
6785 A tuple of (image_bytes, image_format, mode, width, height)
6886 """
87+ import numpy as np # type: ignore[import-not-found]
88+ import PIL .Image # type: ignore[import-not-found]
6989
7090 if isinstance (self ._data , str | Path ) and Path (self ._data ).exists ():
7191 return self ._process_file_path ()
@@ -85,6 +105,7 @@ def _process_file_path(self) -> tuple[bytes, str, str | None, int | None, int |
85105 Returns:
86106 A tuple of (image_bytes, image_format, mode, width, height)
87107 """
108+ import PIL .Image # type: ignore[import-not-found]
88109
89110 path_str = str (self ._data )
90111 image_bytes = Path (path_str ).read_bytes ()
@@ -102,6 +123,7 @@ def _process_pil_image(self) -> tuple[bytes, str, str | None, int | None, int |
102123 Returns:
103124 A tuple of (image_bytes, image_format, mode, width, height)
104125 """
126+ import PIL .Image # type: ignore[import-not-found]
105127
106128 if not isinstance (self ._data , PIL .Image .Image ):
107129 raise TypeError (f"Expected PIL.Image, got { type (self ._data )} " )
@@ -139,6 +161,8 @@ def _process_numpy_array(self) -> tuple[bytes, str, str | None, int | None, int
139161 Returns:
140162 A tuple of (image_bytes, image_format, mode, width, height)
141163 """
164+ import numpy as np # type: ignore[import-not-found]
165+ import PIL .Image # type: ignore[import-not-found]
142166
143167 buffer = io .BytesIO ()
144168 image_format = self ._format or "png"
@@ -168,6 +192,7 @@ def _process_raw_bytes(self) -> tuple[bytes, str, str | None, int | None, int |
168192 Returns:
169193 A tuple of (image_bytes, image_format, mode, width, height)
170194 """
195+ import PIL .Image # type: ignore[import-not-found]
171196
172197 if not isinstance (self ._data , bytes ):
173198 raise TypeError (f"Expected bytes, got { type (self ._data )} " )
@@ -192,6 +217,7 @@ def _process_base64_string(self) -> tuple[bytes, str, str | None, int | None, in
192217 Returns:
193218 A tuple of (image_bytes, image_format, mode, width, height)
194219 """
220+ import PIL .Image # type: ignore[import-not-found]
195221
196222 if not isinstance (self ._data , str ):
197223 raise TypeError (f"Expected str, got { type (self ._data )} " )
@@ -228,6 +254,8 @@ def _generate_metadata(
228254 self , image_format : str , mode : str | None , width : int | None , height : int | None
229255 ) -> dict [str , str | int | None ]:
230256 """Generate metadata for the image."""
257+ import numpy as np # type: ignore[import-not-found]
258+ import PIL .Image # type: ignore[import-not-found]
231259
232260 metadata : dict [str , str | int | None ] = {
233261 "extension" : image_format .lower (),
@@ -286,6 +314,7 @@ def _ensure_valid_image_array(
286314 self , array : "np.ndarray[t.Any, np.dtype[t.Any]]"
287315 ) -> "np.ndarray[t.Any, np.dtype[t.Any]]" :
288316 """Convert numpy array to a format suitable for PIL."""
317+ import numpy as np # type: ignore[import-not-found]
289318
290319 grayscale_dim = 2
291320 rgb_dim = 3
0 commit comments