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

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.""" 

16 

17import numpy as np 

18import tensorflow.compat.v2 as tf 

19 

20from keras.src.utils import dataset_utils 

21from keras.src.utils import image_utils 

22 

23# isort: off 

24from tensorflow.python.util.tf_export import keras_export 

25 

26 

27ALLOWLIST_FORMATS = (".bmp", ".gif", ".jpeg", ".jpg", ".png") 

28 

29 

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. 

53 

54 If your directory structure is: 

55 

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 ``` 

65 

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`). 

70 

71 Supported image formats: jpeg, png, bmp, gif. 

72 Animated gifs are truncated to the first frame. 

73 

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. 

132 

133 Returns: 

134 A `tf.data.Dataset` object. 

135 

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. 

142 

143 Rules regarding labels format: 

144 

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. 

152 

153 Rules regarding number of channels in the yielded images: 

154 

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 ) 

207 

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 ) 

219 

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 ) 

225 

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) 

271 

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 ) 

285 

286 # Users may need to reference `class_names`. 

287 train_dataset.class_names = class_names 

288 val_dataset.class_names = class_names 

289 

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 ) 

303 

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) 

323 

324 # Users may need to reference `class_names`. 

325 dataset.class_names = class_names 

326 

327 # Include file paths for images as attribute. 

328 dataset.file_paths = image_paths 

329 return dataset 

330 

331 

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 

355 

356 

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 

373