1# Copyright 2016 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"""Tests for image preprocessing utils."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import os
22import shutil
23
24import numpy as np
25
26from tensorflow.python.keras._impl import keras
27from tensorflow.python.platform import test
28
29try:
30  import PIL  # pylint:disable=g-import-not-at-top
31except ImportError:
32  PIL = None
33
34
35def _generate_test_images():
36  img_w = img_h = 20
37  rgb_images = []
38  gray_images = []
39  for _ in range(8):
40    bias = np.random.rand(img_w, img_h, 1) * 64
41    variance = np.random.rand(img_w, img_h, 1) * (255 - 64)
42    imarray = np.random.rand(img_w, img_h, 3) * variance + bias
43    im = keras.preprocessing.image.array_to_img(imarray, scale=False)
44    rgb_images.append(im)
45
46    imarray = np.random.rand(img_w, img_h, 1) * variance + bias
47    im = keras.preprocessing.image.array_to_img(imarray, scale=False)
48    gray_images.append(im)
49
50  return [rgb_images, gray_images]
51
52
53class TestImage(test.TestCase):
54
55  def test_image_data_generator(self):
56    if PIL is None:
57      return  # Skip test if PIL is not available.
58
59    for test_images in _generate_test_images():
60      img_list = []
61      for im in test_images:
62        img_list.append(keras.preprocessing.image.img_to_array(im)[None, ...])
63
64      images = np.vstack(img_list)
65      generator = keras.preprocessing.image.ImageDataGenerator(
66          featurewise_center=True,
67          samplewise_center=True,
68          featurewise_std_normalization=True,
69          samplewise_std_normalization=True,
70          zca_whitening=True,
71          rotation_range=90.,
72          width_shift_range=0.1,
73          height_shift_range=0.1,
74          shear_range=0.5,
75          zoom_range=0.2,
76          channel_shift_range=0.,
77          fill_mode='nearest',
78          cval=0.5,
79          horizontal_flip=True,
80          vertical_flip=True)
81      # Basic test before fit
82      x = np.random.random((32, 10, 10, 3))
83      generator.flow(x)
84
85      # Fit
86      generator.fit(images, augment=True)
87
88      for x, _ in generator.flow(
89          images,
90          np.arange(images.shape[0]),
91          shuffle=True):
92        self.assertEqual(x.shape[1:], images.shape[1:])
93        break
94
95  def test_image_data_generator_invalid_data(self):
96    generator = keras.preprocessing.image.ImageDataGenerator(
97        featurewise_center=True,
98        samplewise_center=True,
99        featurewise_std_normalization=True,
100        samplewise_std_normalization=True,
101        zca_whitening=True,
102        data_format='channels_last')
103
104    # Test fit with invalid data
105    with self.assertRaises(ValueError):
106      x = np.random.random((3, 10, 10))
107      generator.fit(x)
108    # Test flow with invalid data
109    with self.assertRaises(ValueError):
110      generator.flow(np.arange(5))
111    # Invalid number of channels: will work but raise a warning
112    x = np.random.random((32, 10, 10, 5))
113    generator.flow(x)
114
115    with self.assertRaises(ValueError):
116      generator = keras.preprocessing.image.ImageDataGenerator(
117          data_format='unknown')
118
119    generator = keras.preprocessing.image.ImageDataGenerator(
120        zoom_range=(2, 2))
121    with self.assertRaises(ValueError):
122      generator = keras.preprocessing.image.ImageDataGenerator(
123          zoom_range=(2, 2, 2))
124
125  def test_image_data_generator_fit(self):
126    generator = keras.preprocessing.image.ImageDataGenerator(
127        featurewise_center=True,
128        samplewise_center=True,
129        featurewise_std_normalization=True,
130        samplewise_std_normalization=True,
131        zca_whitening=True,
132        data_format='channels_last')
133    # Test grayscale
134    x = np.random.random((32, 10, 10, 1))
135    generator.fit(x)
136    # Test RBG
137    x = np.random.random((32, 10, 10, 3))
138    generator.fit(x)
139    generator = keras.preprocessing.image.ImageDataGenerator(
140        featurewise_center=True,
141        samplewise_center=True,
142        featurewise_std_normalization=True,
143        samplewise_std_normalization=True,
144        zca_whitening=True,
145        data_format='channels_first')
146    # Test grayscale
147    x = np.random.random((32, 1, 10, 10))
148    generator.fit(x)
149    # Test RBG
150    x = np.random.random((32, 3, 10, 10))
151    generator.fit(x)
152
153  def test_directory_iterator(self):
154    if PIL is None:
155      return  # Skip test if PIL is not available.
156
157    num_classes = 2
158
159    temp_dir = self.get_temp_dir()
160    self.addCleanup(shutil.rmtree, temp_dir)
161
162    # create folders and subfolders
163    paths = []
164    for cl in range(num_classes):
165      class_directory = 'class-{}'.format(cl)
166      classpaths = [
167          class_directory, os.path.join(class_directory, 'subfolder-1'),
168          os.path.join(class_directory, 'subfolder-2'), os.path.join(
169              class_directory, 'subfolder-1', 'sub-subfolder')
170      ]
171      for path in classpaths:
172        os.mkdir(os.path.join(temp_dir, path))
173      paths.append(classpaths)
174
175    # save the images in the paths
176    count = 0
177    filenames = []
178    for test_images in _generate_test_images():
179      for im in test_images:
180        # rotate image class
181        im_class = count % num_classes
182        # rotate subfolders
183        classpaths = paths[im_class]
184        filename = os.path.join(classpaths[count % len(classpaths)],
185                                'image-{}.jpg'.format(count))
186        filenames.append(filename)
187        im.save(os.path.join(temp_dir, filename))
188        count += 1
189
190    # Test image loading util
191    fname = os.path.join(temp_dir, filenames[0])
192    _ = keras.preprocessing.image.load_img(fname)
193    _ = keras.preprocessing.image.load_img(fname, grayscale=True)
194    _ = keras.preprocessing.image.load_img(fname, target_size=(10, 10))
195    _ = keras.preprocessing.image.load_img(fname, target_size=(10, 10),
196                                           interpolation='bilinear')
197
198    # create iterator
199    generator = keras.preprocessing.image.ImageDataGenerator()
200    dir_iterator = generator.flow_from_directory(temp_dir)
201
202    # check number of classes and images
203    self.assertEqual(len(dir_iterator.class_indices), num_classes)
204    self.assertEqual(len(dir_iterator.classes), count)
205    self.assertEqual(sorted(dir_iterator.filenames), sorted(filenames))
206    _ = dir_iterator.next()
207
208  def test_img_utils(self):
209    if PIL is None:
210      return  # Skip test if PIL is not available.
211
212    height, width = 10, 8
213
214    # Test channels_first data format
215    x = np.random.random((3, height, width))
216    img = keras.preprocessing.image.array_to_img(
217        x, data_format='channels_first')
218    self.assertEqual(img.size, (width, height))
219    x = keras.preprocessing.image.img_to_array(
220        img, data_format='channels_first')
221    self.assertEqual(x.shape, (3, height, width))
222    # Test 2D
223    x = np.random.random((1, height, width))
224    img = keras.preprocessing.image.array_to_img(
225        x, data_format='channels_first')
226    self.assertEqual(img.size, (width, height))
227    x = keras.preprocessing.image.img_to_array(
228        img, data_format='channels_first')
229    self.assertEqual(x.shape, (1, height, width))
230
231    # Test channels_last data format
232    x = np.random.random((height, width, 3))
233    img = keras.preprocessing.image.array_to_img(x, data_format='channels_last')
234    self.assertEqual(img.size, (width, height))
235    x = keras.preprocessing.image.img_to_array(img, data_format='channels_last')
236    self.assertEqual(x.shape, (height, width, 3))
237    # Test 2D
238    x = np.random.random((height, width, 1))
239    img = keras.preprocessing.image.array_to_img(x, data_format='channels_last')
240    self.assertEqual(img.size, (width, height))
241    x = keras.preprocessing.image.img_to_array(img, data_format='channels_last')
242    self.assertEqual(x.shape, (height, width, 1))
243
244  def test_img_transforms(self):
245    x = np.random.random((3, 200, 200))
246    _ = keras.preprocessing.image.random_rotation(x, 20)
247    _ = keras.preprocessing.image.random_shift(x, 0.2, 0.2)
248    _ = keras.preprocessing.image.random_shear(x, 2.)
249    _ = keras.preprocessing.image.random_zoom(x, (0.5, 0.5))
250    with self.assertRaises(ValueError):
251      keras.preprocessing.image.random_zoom(x, (0, 0, 0))
252    _ = keras.preprocessing.image.random_channel_shift(x, 2.)
253
254
255if __name__ == '__main__':
256  test.main()
257