Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/keras/src/utils/image_utils.py: 16%
164 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
1# Copyright 2022 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Utilities related to image handling."""
18import io
19import pathlib
20import warnings
22import numpy as np
23import tensorflow.compat.v2 as tf
25from keras.src import backend
27# isort: off
28from tensorflow.python.util.tf_export import keras_export
30try:
31 from PIL import Image as pil_image
33 try:
34 pil_image_resampling = pil_image.Resampling
35 except AttributeError:
36 pil_image_resampling = pil_image
37except ImportError:
38 pil_image = None
39 pil_image_resampling = None
42if pil_image_resampling is not None:
43 _PIL_INTERPOLATION_METHODS = {
44 "nearest": pil_image_resampling.NEAREST,
45 "bilinear": pil_image_resampling.BILINEAR,
46 "bicubic": pil_image_resampling.BICUBIC,
47 "hamming": pil_image_resampling.HAMMING,
48 "box": pil_image_resampling.BOX,
49 "lanczos": pil_image_resampling.LANCZOS,
50 }
52ResizeMethod = tf.image.ResizeMethod
54_TF_INTERPOLATION_METHODS = {
55 "bilinear": ResizeMethod.BILINEAR,
56 "nearest": ResizeMethod.NEAREST_NEIGHBOR,
57 "bicubic": ResizeMethod.BICUBIC,
58 "area": ResizeMethod.AREA,
59 "lanczos3": ResizeMethod.LANCZOS3,
60 "lanczos5": ResizeMethod.LANCZOS5,
61 "gaussian": ResizeMethod.GAUSSIAN,
62 "mitchellcubic": ResizeMethod.MITCHELLCUBIC,
63}
66@keras_export("keras.preprocessing.image.smart_resize", v1=[])
67def smart_resize(x, size, interpolation="bilinear"):
68 """Resize images to a target size without aspect ratio distortion.
70 Warning: `tf.keras.preprocessing.image.smart_resize` is not recommended for
71 new code. Prefer `tf.keras.layers.Resizing`, which provides the same
72 functionality as a preprocessing layer and adds `tf.RaggedTensor` support.
73 See the [preprocessing layer guide](
74 https://www.tensorflow.org/guide/keras/preprocessing_layers)
75 for an overview of preprocessing layers.
77 TensorFlow image datasets typically yield images that have each a different
78 size. However, these images need to be batched before they can be
79 processed by Keras layers. To be batched, images need to share the same
80 height and width.
82 You could simply do:
84 ```python
85 size = (200, 200)
86 ds = ds.map(lambda img: tf.image.resize(img, size))
87 ```
89 However, if you do this, you distort the aspect ratio of your images, since
90 in general they do not all have the same aspect ratio as `size`. This is
91 fine in many cases, but not always (e.g. for GANs this can be a problem).
93 Note that passing the argument `preserve_aspect_ratio=True` to `resize`
94 will preserve the aspect ratio, but at the cost of no longer respecting the
95 provided target size. Because `tf.image.resize` doesn't crop images,
96 your output images will still have different sizes.
98 This calls for:
100 ```python
101 size = (200, 200)
102 ds = ds.map(lambda img: smart_resize(img, size))
103 ```
105 Your output images will actually be `(200, 200)`, and will not be distorted.
106 Instead, the parts of the image that do not fit within the target size
107 get cropped out.
109 The resizing process is:
111 1. Take the largest centered crop of the image that has the same aspect
112 ratio as the target size. For instance, if `size=(200, 200)` and the input
113 image has size `(340, 500)`, we take a crop of `(340, 340)` centered along
114 the width.
115 2. Resize the cropped image to the target size. In the example above,
116 we resize the `(340, 340)` crop to `(200, 200)`.
118 Args:
119 x: Input image or batch of images (as a tensor or NumPy array). Must be in
120 format `(height, width, channels)` or `(batch_size, height, width,
121 channels)`.
122 size: Tuple of `(height, width)` integer. Target size.
123 interpolation: String, interpolation to use for resizing. Defaults to
124 `'bilinear'`. Supports `bilinear`, `nearest`, `bicubic`, `area`,
125 `lanczos3`, `lanczos5`, `gaussian`, `mitchellcubic`.
127 Returns:
128 Array with shape `(size[0], size[1], channels)`. If the input image was a
129 NumPy array, the output is a NumPy array, and if it was a TF tensor,
130 the output is a TF tensor.
131 """
132 if len(size) != 2:
133 raise ValueError(
134 f"Expected `size` to be a tuple of 2 integers, but got: {size}."
135 )
136 img = tf.convert_to_tensor(x)
137 if img.shape.rank is not None:
138 if img.shape.rank < 3 or img.shape.rank > 4:
139 raise ValueError(
140 "Expected an image array with shape `(height, width, "
141 "channels)`, or `(batch_size, height, width, channels)`, but "
142 f"got input with incorrect rank, of shape {img.shape}."
143 )
144 shape = tf.shape(img)
145 height, width = shape[-3], shape[-2]
146 target_height, target_width = size
147 if img.shape.rank is not None:
148 static_num_channels = img.shape[-1]
149 else:
150 static_num_channels = None
152 crop_height = tf.cast(
153 tf.cast(width * target_height, "float32") / target_width, "int32"
154 )
155 crop_width = tf.cast(
156 tf.cast(height * target_width, "float32") / target_height, "int32"
157 )
159 # Set back to input height / width if crop_height / crop_width is not
160 # smaller.
161 crop_height = tf.minimum(height, crop_height)
162 crop_width = tf.minimum(width, crop_width)
164 crop_box_hstart = tf.cast(
165 tf.cast(height - crop_height, "float32") / 2, "int32"
166 )
167 crop_box_wstart = tf.cast(
168 tf.cast(width - crop_width, "float32") / 2, "int32"
169 )
171 if img.shape.rank == 4:
172 crop_box_start = tf.stack([0, crop_box_hstart, crop_box_wstart, 0])
173 crop_box_size = tf.stack([-1, crop_height, crop_width, -1])
174 else:
175 crop_box_start = tf.stack([crop_box_hstart, crop_box_wstart, 0])
176 crop_box_size = tf.stack([crop_height, crop_width, -1])
178 img = tf.slice(img, crop_box_start, crop_box_size)
179 img = tf.image.resize(images=img, size=size, method=interpolation)
180 # Apparent bug in resize_images_v2 may cause shape to be lost
181 if img.shape.rank is not None:
182 if img.shape.rank == 4:
183 img.set_shape((None, None, None, static_num_channels))
184 if img.shape.rank == 3:
185 img.set_shape((None, None, static_num_channels))
186 if isinstance(x, np.ndarray):
187 return img.numpy()
188 return img
191def get_interpolation(interpolation):
192 interpolation = interpolation.lower()
193 if interpolation not in _TF_INTERPOLATION_METHODS:
194 raise NotImplementedError(
195 "Value not recognized for `interpolation`: {}. Supported values "
196 "are: {}".format(interpolation, _TF_INTERPOLATION_METHODS.keys())
197 )
198 return _TF_INTERPOLATION_METHODS[interpolation]
201@keras_export(
202 "keras.utils.array_to_img", "keras.preprocessing.image.array_to_img"
203)
204def array_to_img(x, data_format=None, scale=True, dtype=None):
205 """Converts a 3D Numpy array to a PIL Image instance.
207 Usage:
209 ```python
210 from PIL import Image
211 img = np.random.random(size=(100, 100, 3))
212 pil_img = tf.keras.utils.array_to_img(img)
213 ```
216 Args:
217 x: Input data, in any form that can be converted to a Numpy array.
218 data_format: Image data format, can be either `"channels_first"` or
219 `"channels_last"`. Defaults to `None`, in which case the global
220 setting `tf.keras.backend.image_data_format()` is used (unless you
221 changed it, it defaults to `"channels_last"`).
222 scale: Whether to rescale the image such that minimum and maximum values
223 are 0 and 255 respectively. Defaults to `True`.
224 dtype: Dtype to use. Default to `None`, in which case the global setting
225 `tf.keras.backend.floatx()` is used (unless you changed it, it
226 defaults to `"float32"`)
228 Returns:
229 A PIL Image instance.
231 Raises:
232 ImportError: if PIL is not available.
233 ValueError: if invalid `x` or `data_format` is passed.
234 """
236 if data_format is None:
237 data_format = backend.image_data_format()
238 if dtype is None:
239 dtype = backend.floatx()
240 if pil_image is None:
241 raise ImportError(
242 "Could not import PIL.Image. "
243 "The use of `array_to_img` requires PIL."
244 )
245 x = np.asarray(x, dtype=dtype)
246 if x.ndim != 3:
247 raise ValueError(
248 "Expected image array to have rank 3 (single image). "
249 f"Got array with shape: {x.shape}"
250 )
252 if data_format not in {"channels_first", "channels_last"}:
253 raise ValueError(f"Invalid data_format: {data_format}")
255 # Original Numpy array x has format (height, width, channel)
256 # or (channel, height, width)
257 # but target PIL image has format (width, height, channel)
258 if data_format == "channels_first":
259 x = x.transpose(1, 2, 0)
260 if scale:
261 x = x - np.min(x)
262 x_max = np.max(x)
263 if x_max != 0:
264 x /= x_max
265 x *= 255
266 if x.shape[2] == 4:
267 # RGBA
268 return pil_image.fromarray(x.astype("uint8"), "RGBA")
269 elif x.shape[2] == 3:
270 # RGB
271 return pil_image.fromarray(x.astype("uint8"), "RGB")
272 elif x.shape[2] == 1:
273 # grayscale
274 if np.max(x) > 255:
275 # 32-bit signed integer grayscale image. PIL mode "I"
276 return pil_image.fromarray(x[:, :, 0].astype("int32"), "I")
277 return pil_image.fromarray(x[:, :, 0].astype("uint8"), "L")
278 else:
279 raise ValueError(f"Unsupported channel number: {x.shape[2]}")
282@keras_export(
283 "keras.utils.img_to_array", "keras.preprocessing.image.img_to_array"
284)
285def img_to_array(img, data_format=None, dtype=None):
286 """Converts a PIL Image instance to a Numpy array.
288 Usage:
290 ```python
291 from PIL import Image
292 img_data = np.random.random(size=(100, 100, 3))
293 img = tf.keras.utils.array_to_img(img_data)
294 array = tf.keras.utils.image.img_to_array(img)
295 ```
298 Args:
299 img: Input PIL Image instance.
300 data_format: Image data format, can be either `"channels_first"` or
301 `"channels_last"`. Defaults to `None`, in which case the global
302 setting `tf.keras.backend.image_data_format()` is used (unless you
303 changed it, it defaults to `"channels_last"`).
304 dtype: Dtype to use. Default to `None`, in which case the global setting
305 `tf.keras.backend.floatx()` is used (unless you changed it, it
306 defaults to `"float32"`).
308 Returns:
309 A 3D Numpy array.
311 Raises:
312 ValueError: if invalid `img` or `data_format` is passed.
313 """
315 if data_format is None:
316 data_format = backend.image_data_format()
317 if dtype is None:
318 dtype = backend.floatx()
319 if data_format not in {"channels_first", "channels_last"}:
320 raise ValueError(f"Unknown data_format: {data_format}")
321 # Numpy array x has format (height, width, channel)
322 # or (channel, height, width)
323 # but original PIL image has format (width, height, channel)
324 x = np.asarray(img, dtype=dtype)
325 if len(x.shape) == 3:
326 if data_format == "channels_first":
327 x = x.transpose(2, 0, 1)
328 elif len(x.shape) == 2:
329 if data_format == "channels_first":
330 x = x.reshape((1, x.shape[0], x.shape[1]))
331 else:
332 x = x.reshape((x.shape[0], x.shape[1], 1))
333 else:
334 raise ValueError(f"Unsupported image shape: {x.shape}")
335 return x
338@keras_export("keras.utils.save_img", "keras.preprocessing.image.save_img")
339def save_img(path, x, data_format=None, file_format=None, scale=True, **kwargs):
340 """Saves an image stored as a Numpy array to a path or file object.
342 Args:
343 path: Path or file object.
344 x: Numpy array.
345 data_format: Image data format, either `"channels_first"` or
346 `"channels_last"`.
347 file_format: Optional file format override. If omitted, the format to
348 use is determined from the filename extension. If a file object was
349 used instead of a filename, this parameter should always be used.
350 scale: Whether to rescale image values to be within `[0, 255]`.
351 **kwargs: Additional keyword arguments passed to `PIL.Image.save()`.
352 """
353 if data_format is None:
354 data_format = backend.image_data_format()
355 img = array_to_img(x, data_format=data_format, scale=scale)
356 if img.mode == "RGBA" and (file_format == "jpg" or file_format == "jpeg"):
357 warnings.warn(
358 "The JPG format does not support RGBA images, converting to RGB."
359 )
360 img = img.convert("RGB")
361 img.save(path, format=file_format, **kwargs)
364@keras_export("keras.utils.load_img", "keras.preprocessing.image.load_img")
365def load_img(
366 path,
367 grayscale=False,
368 color_mode="rgb",
369 target_size=None,
370 interpolation="nearest",
371 keep_aspect_ratio=False,
372):
373 """Loads an image into PIL format.
375 Usage:
377 ```python
378 image = tf.keras.utils.load_img(image_path)
379 input_arr = tf.keras.utils.img_to_array(image)
380 input_arr = np.array([input_arr]) # Convert single image to a batch.
381 predictions = model.predict(input_arr)
382 ```
384 Args:
385 path: Path to image file.
386 grayscale: DEPRECATED use `color_mode="grayscale"`.
387 color_mode: One of `"grayscale"`, `"rgb"`, `"rgba"`. Default: `"rgb"`.
388 The desired image format.
389 target_size: Either `None` (default to original size) or tuple of ints
390 `(img_height, img_width)`.
391 interpolation: Interpolation method used to resample the image if the
392 target size is different from that of the loaded image. Supported
393 methods are `"nearest"`, `"bilinear"`, and `"bicubic"`. If PIL version
394 1.1.3 or newer is installed, `"lanczos"` is also supported. If PIL
395 version 3.4.0 or newer is installed, `"box"` and `"hamming"` are also
396 supported. By default, `"nearest"` is used.
397 keep_aspect_ratio: Boolean, whether to resize images to a target
398 size without aspect ratio distortion. The image is cropped in
399 the center with target aspect ratio before resizing.
401 Returns:
402 A PIL Image instance.
404 Raises:
405 ImportError: if PIL is not available.
406 ValueError: if interpolation method is not supported.
407 """
408 if grayscale:
409 warnings.warn(
410 'grayscale is deprecated. Please use color_mode = "grayscale"'
411 )
412 color_mode = "grayscale"
413 if pil_image is None:
414 raise ImportError(
415 "Could not import PIL.Image. The use of `load_img` requires PIL."
416 )
417 if isinstance(path, io.BytesIO):
418 img = pil_image.open(path)
419 elif isinstance(path, (pathlib.Path, bytes, str)):
420 if isinstance(path, pathlib.Path):
421 path = str(path.resolve())
422 with open(path, "rb") as f:
423 img = pil_image.open(io.BytesIO(f.read()))
424 else:
425 raise TypeError(
426 f"path should be path-like or io.BytesIO, not {type(path)}"
427 )
429 if color_mode == "grayscale":
430 # if image is not already an 8-bit, 16-bit or 32-bit grayscale image
431 # convert it to an 8-bit grayscale image.
432 if img.mode not in ("L", "I;16", "I"):
433 img = img.convert("L")
434 elif color_mode == "rgba":
435 if img.mode != "RGBA":
436 img = img.convert("RGBA")
437 elif color_mode == "rgb":
438 if img.mode != "RGB":
439 img = img.convert("RGB")
440 else:
441 raise ValueError('color_mode must be "grayscale", "rgb", or "rgba"')
442 if target_size is not None:
443 width_height_tuple = (target_size[1], target_size[0])
444 if img.size != width_height_tuple:
445 if interpolation not in _PIL_INTERPOLATION_METHODS:
446 raise ValueError(
447 "Invalid interpolation method {} specified. Supported "
448 "methods are {}".format(
449 interpolation,
450 ", ".join(_PIL_INTERPOLATION_METHODS.keys()),
451 )
452 )
453 resample = _PIL_INTERPOLATION_METHODS[interpolation]
455 if keep_aspect_ratio:
456 width, height = img.size
457 target_width, target_height = width_height_tuple
459 crop_height = (width * target_height) // target_width
460 crop_width = (height * target_width) // target_height
462 # Set back to input height / width
463 # if crop_height / crop_width is not smaller.
464 crop_height = min(height, crop_height)
465 crop_width = min(width, crop_width)
467 crop_box_hstart = (height - crop_height) // 2
468 crop_box_wstart = (width - crop_width) // 2
469 crop_box_wend = crop_box_wstart + crop_width
470 crop_box_hend = crop_box_hstart + crop_height
471 crop_box = [
472 crop_box_wstart,
473 crop_box_hstart,
474 crop_box_wend,
475 crop_box_hend,
476 ]
477 img = img.resize(width_height_tuple, resample, box=crop_box)
478 else:
479 img = img.resize(width_height_tuple, resample)
480 return img