Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/keras/src/utils/image_dataset.py: 12%
90 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 2020 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"""Keras image dataset loading utilities."""
17import numpy as np
18import tensorflow.compat.v2 as tf
20from keras.src.utils import dataset_utils
21from keras.src.utils import image_utils
23# isort: off
24from tensorflow.python.util.tf_export import keras_export
27ALLOWLIST_FORMATS = (".bmp", ".gif", ".jpeg", ".jpg", ".png")
30@keras_export(
31 "keras.utils.image_dataset_from_directory",
32 "keras.preprocessing.image_dataset_from_directory",
33 v1=[],
34)
35def image_dataset_from_directory(
36 directory,
37 labels="inferred",
38 label_mode="int",
39 class_names=None,
40 color_mode="rgb",
41 batch_size=32,
42 image_size=(256, 256),
43 shuffle=True,
44 seed=None,
45 validation_split=None,
46 subset=None,
47 interpolation="bilinear",
48 follow_links=False,
49 crop_to_aspect_ratio=False,
50 **kwargs,
51):
52 """Generates a `tf.data.Dataset` from image files in a directory.
54 If your directory structure is:
56 ```
57 main_directory/
58 ...class_a/
59 ......a_image_1.jpg
60 ......a_image_2.jpg
61 ...class_b/
62 ......b_image_1.jpg
63 ......b_image_2.jpg
64 ```
66 Then calling `image_dataset_from_directory(main_directory,
67 labels='inferred')` will return a `tf.data.Dataset` that yields batches of
68 images from the subdirectories `class_a` and `class_b`, together with labels
69 0 and 1 (0 corresponding to `class_a` and 1 corresponding to `class_b`).
71 Supported image formats: jpeg, png, bmp, gif.
72 Animated gifs are truncated to the first frame.
74 Args:
75 directory: Directory where the data is located.
76 If `labels` is "inferred", it should contain
77 subdirectories, each containing images for a class.
78 Otherwise, the directory structure is ignored.
79 labels: Either "inferred"
80 (labels are generated from the directory structure),
81 None (no labels),
82 or a list/tuple of integer labels of the same size as the number of
83 image files found in the directory. Labels should be sorted
84 according to the alphanumeric order of the image file paths
85 (obtained via `os.walk(directory)` in Python).
86 label_mode: String describing the encoding of `labels`. Options are:
87 - 'int': means that the labels are encoded as integers
88 (e.g. for `sparse_categorical_crossentropy` loss).
89 - 'categorical' means that the labels are
90 encoded as a categorical vector
91 (e.g. for `categorical_crossentropy` loss).
92 - 'binary' means that the labels (there can be only 2)
93 are encoded as `float32` scalars with values 0 or 1
94 (e.g. for `binary_crossentropy`).
95 - None (no labels).
96 class_names: Only valid if "labels" is "inferred". This is the explicit
97 list of class names (must match names of subdirectories). Used
98 to control the order of the classes (otherwise alphanumerical order
99 is used).
100 color_mode: One of "grayscale", "rgb", "rgba". Default: "rgb".
101 Whether the images will be converted to have 1, 3, or 4 channels.
102 batch_size: Size of the batches of data. Default: 32.
103 If `None`, the data will not be batched
104 (the dataset will yield individual samples).
105 image_size: Size to resize images to after they are read from disk,
106 specified as `(height, width)`. Defaults to `(256, 256)`.
107 Since the pipeline processes batches of images that must all have
108 the same size, this must be provided.
109 shuffle: Whether to shuffle the data. Default: True.
110 If set to False, sorts the data in alphanumeric order.
111 seed: Optional random seed for shuffling and transformations.
112 validation_split: Optional float between 0 and 1,
113 fraction of data to reserve for validation.
114 subset: Subset of the data to return.
115 One of "training", "validation" or "both".
116 Only used if `validation_split` is set.
117 When `subset="both"`, the utility returns a tuple of two datasets
118 (the training and validation datasets respectively).
119 interpolation: String, the interpolation method used when resizing images.
120 Defaults to `bilinear`. Supports `bilinear`, `nearest`, `bicubic`,
121 `area`, `lanczos3`, `lanczos5`, `gaussian`, `mitchellcubic`.
122 follow_links: Whether to visit subdirectories pointed to by symlinks.
123 Defaults to False.
124 crop_to_aspect_ratio: If True, resize the images without aspect
125 ratio distortion. When the original aspect ratio differs from the
126 target aspect ratio, the output image will be cropped so as to
127 return the largest possible window in the image
128 (of size `image_size`) that matches the target aspect ratio. By
129 default (`crop_to_aspect_ratio=False`), aspect ratio may not be
130 preserved.
131 **kwargs: Legacy keyword arguments.
133 Returns:
134 A `tf.data.Dataset` object.
136 - If `label_mode` is None, it yields `float32` tensors of shape
137 `(batch_size, image_size[0], image_size[1], num_channels)`,
138 encoding images (see below for rules regarding `num_channels`).
139 - Otherwise, it yields a tuple `(images, labels)`, where `images` has
140 shape `(batch_size, image_size[0], image_size[1], num_channels)`,
141 and `labels` follows the format described below.
143 Rules regarding labels format:
145 - if `label_mode` is `int`, the labels are an `int32` tensor of shape
146 `(batch_size,)`.
147 - if `label_mode` is `binary`, the labels are a `float32` tensor of
148 1s and 0s of shape `(batch_size, 1)`.
149 - if `label_mode` is `categorical`, the labels are a `float32` tensor
150 of shape `(batch_size, num_classes)`, representing a one-hot
151 encoding of the class index.
153 Rules regarding number of channels in the yielded images:
155 - if `color_mode` is `grayscale`,
156 there's 1 channel in the image tensors.
157 - if `color_mode` is `rgb`,
158 there are 3 channels in the image tensors.
159 - if `color_mode` is `rgba`,
160 there are 4 channels in the image tensors.
161 """
162 if "smart_resize" in kwargs:
163 crop_to_aspect_ratio = kwargs.pop("smart_resize")
164 if kwargs:
165 raise TypeError(f"Unknown keywords argument(s): {tuple(kwargs.keys())}")
166 if labels not in ("inferred", None):
167 if not isinstance(labels, (list, tuple)):
168 raise ValueError(
169 "`labels` argument should be a list/tuple of integer labels, "
170 "of the same size as the number of image files in the target "
171 "directory. If you wish to infer the labels from the "
172 "subdirectory "
173 'names in the target directory, pass `labels="inferred"`. '
174 "If you wish to get a dataset that only contains images "
175 f"(no labels), pass `labels=None`. Received: labels={labels}"
176 )
177 if class_names:
178 raise ValueError(
179 "You can only pass `class_names` if "
180 f'`labels="inferred"`. Received: labels={labels}, and '
181 f"class_names={class_names}"
182 )
183 if label_mode not in {"int", "categorical", "binary", None}:
184 raise ValueError(
185 '`label_mode` argument must be one of "int", '
186 '"categorical", "binary", '
187 f"or None. Received: label_mode={label_mode}"
188 )
189 if labels is None or label_mode is None:
190 labels = None
191 label_mode = None
192 if color_mode == "rgb":
193 num_channels = 3
194 elif color_mode == "rgba":
195 num_channels = 4
196 elif color_mode == "grayscale":
197 num_channels = 1
198 else:
199 raise ValueError(
200 '`color_mode` must be one of {"rgb", "rgba", "grayscale"}. '
201 f"Received: color_mode={color_mode}"
202 )
203 interpolation = image_utils.get_interpolation(interpolation)
204 dataset_utils.check_validation_split_arg(
205 validation_split, subset, shuffle, seed
206 )
208 if seed is None:
209 seed = np.random.randint(1e6)
210 image_paths, labels, class_names = dataset_utils.index_directory(
211 directory,
212 labels,
213 formats=ALLOWLIST_FORMATS,
214 class_names=class_names,
215 shuffle=shuffle,
216 seed=seed,
217 follow_links=follow_links,
218 )
220 if label_mode == "binary" and len(class_names) != 2:
221 raise ValueError(
222 'When passing `label_mode="binary"`, there must be exactly 2 '
223 f"class_names. Received: class_names={class_names}"
224 )
226 if subset == "both":
227 (
228 image_paths_train,
229 labels_train,
230 ) = dataset_utils.get_training_or_validation_split(
231 image_paths, labels, validation_split, "training"
232 )
233 (
234 image_paths_val,
235 labels_val,
236 ) = dataset_utils.get_training_or_validation_split(
237 image_paths, labels, validation_split, "validation"
238 )
239 if not image_paths_train:
240 raise ValueError(
241 f"No training images found in directory {directory}. "
242 f"Allowed formats: {ALLOWLIST_FORMATS}"
243 )
244 if not image_paths_val:
245 raise ValueError(
246 f"No validation images found in directory {directory}. "
247 f"Allowed formats: {ALLOWLIST_FORMATS}"
248 )
249 train_dataset = paths_and_labels_to_dataset(
250 image_paths=image_paths_train,
251 image_size=image_size,
252 num_channels=num_channels,
253 labels=labels_train,
254 label_mode=label_mode,
255 num_classes=len(class_names),
256 interpolation=interpolation,
257 crop_to_aspect_ratio=crop_to_aspect_ratio,
258 )
259 val_dataset = paths_and_labels_to_dataset(
260 image_paths=image_paths_val,
261 image_size=image_size,
262 num_channels=num_channels,
263 labels=labels_val,
264 label_mode=label_mode,
265 num_classes=len(class_names),
266 interpolation=interpolation,
267 crop_to_aspect_ratio=crop_to_aspect_ratio,
268 )
269 train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)
270 val_dataset = val_dataset.prefetch(tf.data.AUTOTUNE)
272 if batch_size is not None:
273 if shuffle:
274 # Shuffle locally at each iteration
275 train_dataset = train_dataset.shuffle(
276 buffer_size=batch_size * 8, seed=seed
277 )
278 train_dataset = train_dataset.batch(batch_size)
279 val_dataset = val_dataset.batch(batch_size)
280 else:
281 if shuffle:
282 train_dataset = train_dataset.shuffle(
283 buffer_size=1024, seed=seed
284 )
286 # Users may need to reference `class_names`.
287 train_dataset.class_names = class_names
288 val_dataset.class_names = class_names
290 # Include file paths for images as attribute.
291 train_dataset.file_paths = image_paths_train
292 val_dataset.file_paths = image_paths_val
293 dataset = [train_dataset, val_dataset]
294 else:
295 image_paths, labels = dataset_utils.get_training_or_validation_split(
296 image_paths, labels, validation_split, subset
297 )
298 if not image_paths:
299 raise ValueError(
300 f"No images found in directory {directory}. "
301 f"Allowed formats: {ALLOWLIST_FORMATS}"
302 )
304 dataset = paths_and_labels_to_dataset(
305 image_paths=image_paths,
306 image_size=image_size,
307 num_channels=num_channels,
308 labels=labels,
309 label_mode=label_mode,
310 num_classes=len(class_names),
311 interpolation=interpolation,
312 crop_to_aspect_ratio=crop_to_aspect_ratio,
313 )
314 dataset = dataset.prefetch(tf.data.AUTOTUNE)
315 if batch_size is not None:
316 if shuffle:
317 # Shuffle locally at each iteration
318 dataset = dataset.shuffle(buffer_size=batch_size * 8, seed=seed)
319 dataset = dataset.batch(batch_size)
320 else:
321 if shuffle:
322 dataset = dataset.shuffle(buffer_size=1024, seed=seed)
324 # Users may need to reference `class_names`.
325 dataset.class_names = class_names
327 # Include file paths for images as attribute.
328 dataset.file_paths = image_paths
329 return dataset
332def paths_and_labels_to_dataset(
333 image_paths,
334 image_size,
335 num_channels,
336 labels,
337 label_mode,
338 num_classes,
339 interpolation,
340 crop_to_aspect_ratio=False,
341):
342 """Constructs a dataset of images and labels."""
343 # TODO(fchollet): consider making num_parallel_calls settable
344 path_ds = tf.data.Dataset.from_tensor_slices(image_paths)
345 args = (image_size, num_channels, interpolation, crop_to_aspect_ratio)
346 img_ds = path_ds.map(
347 lambda x: load_image(x, *args), num_parallel_calls=tf.data.AUTOTUNE
348 )
349 if label_mode:
350 label_ds = dataset_utils.labels_to_dataset(
351 labels, label_mode, num_classes
352 )
353 img_ds = tf.data.Dataset.zip((img_ds, label_ds))
354 return img_ds
357def load_image(
358 path, image_size, num_channels, interpolation, crop_to_aspect_ratio=False
359):
360 """Load an image from a path and resize it."""
361 img = tf.io.read_file(path)
362 img = tf.image.decode_image(
363 img, channels=num_channels, expand_animations=False
364 )
365 if crop_to_aspect_ratio:
366 img = image_utils.smart_resize(
367 img, image_size, interpolation=interpolation
368 )
369 else:
370 img = tf.image.resize(img, image_size, method=interpolation)
371 img.set_shape((image_size[0], image_size[1], num_channels))
372 return img