• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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 python distort_image_ops."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import time
22
23import numpy as np
24from six.moves import xrange  # pylint: disable=redefined-builtin
25
26from tensorflow.contrib.image.python.ops import distort_image_ops
27from tensorflow.core.protobuf import config_pb2
28from tensorflow.python.client import session
29from tensorflow.python.framework import constant_op
30from tensorflow.python.framework import dtypes
31from tensorflow.python.framework import ops
32from tensorflow.python.framework import test_util
33from tensorflow.python.ops import control_flow_ops
34from tensorflow.python.ops import random_ops
35from tensorflow.python.ops import variables
36from tensorflow.python.platform import googletest
37from tensorflow.python.platform import test
38
39
40# TODO(huangyp): also measure the differences between AdjustHsvInYiq and
41# AdjustHsv in core.
42class AdjustHueInYiqTest(test_util.TensorFlowTestCase):
43
44  def _adjust_hue_in_yiq_np(self, x_np, delta_h):
45    """Rotate hue in YIQ space.
46
47    Mathematically we first convert rgb color to yiq space, rotate the hue
48    degrees, and then convert back to rgb.
49
50    Args:
51      x_np: input x with last dimension = 3.
52      delta_h: degree of hue rotation, in radians.
53
54    Returns:
55      Adjusted y with the same shape as x_np.
56    """
57    self.assertEqual(x_np.shape[-1], 3)
58    x_v = x_np.reshape([-1, 3])
59    y_v = np.ndarray(x_v.shape, dtype=x_v.dtype)
60    u = np.cos(delta_h)
61    w = np.sin(delta_h)
62    # Projection matrix from RGB to YIQ. Numbers from wikipedia
63    # https://en.wikipedia.org/wiki/YIQ
64    tyiq = np.array([[0.299, 0.587, 0.114], [0.596, -0.274, -0.322],
65                     [0.211, -0.523, 0.312]])
66    y_v = np.dot(x_v, tyiq.T)
67    # Hue rotation matrix in YIQ space.
68    hue_rotation = np.array([[1.0, 0.0, 0.0], [0.0, u, -w], [0.0, w, u]])
69    y_v = np.dot(y_v, hue_rotation.T)
70    # Projecting back to RGB space.
71    y_v = np.dot(y_v, np.linalg.inv(tyiq).T)
72    return y_v.reshape(x_np.shape)
73
74  def _adjust_hue_in_yiq_tf(self, x_np, delta_h):
75    with self.test_session(use_gpu=True):
76      x = constant_op.constant(x_np)
77      y = distort_image_ops.adjust_hsv_in_yiq(x, delta_h, 1, 1)
78      y_tf = y.eval()
79    return y_tf
80
81  def test_adjust_random_hue_in_yiq(self):
82    x_shapes = [
83        [2, 2, 3],
84        [4, 2, 3],
85        [2, 4, 3],
86        [2, 5, 3],
87        [1000, 1, 3],
88    ]
89    test_styles = [
90        'all_random',
91        'rg_same',
92        'rb_same',
93        'gb_same',
94        'rgb_same',
95    ]
96    for x_shape in x_shapes:
97      for test_style in test_styles:
98        x_np = np.random.rand(*x_shape) * 255.
99        delta_h = (np.random.rand() * 2.0 - 1.0) * np.pi
100        if test_style == 'all_random':
101          pass
102        elif test_style == 'rg_same':
103          x_np[..., 1] = x_np[..., 0]
104        elif test_style == 'rb_same':
105          x_np[..., 2] = x_np[..., 0]
106        elif test_style == 'gb_same':
107          x_np[..., 2] = x_np[..., 1]
108        elif test_style == 'rgb_same':
109          x_np[..., 1] = x_np[..., 0]
110          x_np[..., 2] = x_np[..., 0]
111        else:
112          raise AssertionError('Invalid test style: %s' % (test_style))
113        y_np = self._adjust_hue_in_yiq_np(x_np, delta_h)
114        y_tf = self._adjust_hue_in_yiq_tf(x_np, delta_h)
115        self.assertAllClose(y_tf, y_np, rtol=2e-4, atol=1e-4)
116
117  def test_invalid_shapes(self):
118    x_np = np.random.rand(2, 3) * 255.
119    delta_h = np.random.rand() * 2.0 - 1.0
120    with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'):
121      self._adjust_hue_in_yiq_tf(x_np, delta_h)
122    x_np = np.random.rand(4, 2, 4) * 255.
123    delta_h = np.random.rand() * 2.0 - 1.0
124    with self.assertRaisesOpError('input must have 3 channels but instead has '
125                                  '4 channels'):
126      self._adjust_hue_in_yiq_tf(x_np, delta_h)
127
128
129class AdjustValueInYiqTest(test_util.TensorFlowTestCase):
130
131  def _adjust_value_in_yiq_np(self, x_np, scale):
132    return x_np * scale
133
134  def _adjust_value_in_yiq_tf(self, x_np, scale):
135    with self.test_session(use_gpu=True):
136      x = constant_op.constant(x_np)
137      y = distort_image_ops.adjust_hsv_in_yiq(x, 0, 1, scale)
138      y_tf = y.eval()
139    return y_tf
140
141  def test_adjust_random_value_in_yiq(self):
142    x_shapes = [
143        [2, 2, 3],
144        [4, 2, 3],
145        [2, 4, 3],
146        [2, 5, 3],
147        [1000, 1, 3],
148    ]
149    test_styles = [
150        'all_random',
151        'rg_same',
152        'rb_same',
153        'gb_same',
154        'rgb_same',
155    ]
156    for x_shape in x_shapes:
157      for test_style in test_styles:
158        x_np = np.random.rand(*x_shape) * 255.
159        scale = np.random.rand() * 2.0 - 1.0
160        if test_style == 'all_random':
161          pass
162        elif test_style == 'rg_same':
163          x_np[..., 1] = x_np[..., 0]
164        elif test_style == 'rb_same':
165          x_np[..., 2] = x_np[..., 0]
166        elif test_style == 'gb_same':
167          x_np[..., 2] = x_np[..., 1]
168        elif test_style == 'rgb_same':
169          x_np[..., 1] = x_np[..., 0]
170          x_np[..., 2] = x_np[..., 0]
171        else:
172          raise AssertionError('Invalid test style: %s' % (test_style))
173        y_np = self._adjust_value_in_yiq_np(x_np, scale)
174        y_tf = self._adjust_value_in_yiq_tf(x_np, scale)
175        self.assertAllClose(y_tf, y_np, rtol=2e-4, atol=1e-4)
176
177  def test_invalid_shapes(self):
178    x_np = np.random.rand(2, 3) * 255.
179    scale = np.random.rand() * 2.0 - 1.0
180    with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'):
181      self._adjust_value_in_yiq_tf(x_np, scale)
182    x_np = np.random.rand(4, 2, 4) * 255.
183    scale = np.random.rand() * 2.0 - 1.0
184    with self.assertRaisesOpError('input must have 3 channels but instead has '
185                                  '4 channels'):
186      self._adjust_value_in_yiq_tf(x_np, scale)
187
188
189class AdjustSaturationInYiqTest(test_util.TensorFlowTestCase):
190
191  def _adjust_saturation_in_yiq_tf(self, x_np, scale):
192    with self.test_session(use_gpu=True):
193      x = constant_op.constant(x_np)
194      y = distort_image_ops.adjust_hsv_in_yiq(x, 0, scale, 1)
195      y_tf = y.eval()
196    return y_tf
197
198  def _adjust_saturation_in_yiq_np(self, x_np, scale):
199    """Adjust saturation using linear interpolation."""
200    rgb_weights = np.array([0.299, 0.587, 0.114])
201    gray = np.sum(x_np * rgb_weights, axis=-1, keepdims=True)
202    y_v = x_np * scale + gray * (1 - scale)
203    return y_v
204
205  def test_adjust_random_saturation_in_yiq(self):
206    x_shapes = [
207        [2, 2, 3],
208        [4, 2, 3],
209        [2, 4, 3],
210        [2, 5, 3],
211        [1000, 1, 3],
212    ]
213    test_styles = [
214        'all_random',
215        'rg_same',
216        'rb_same',
217        'gb_same',
218        'rgb_same',
219    ]
220    with self.cached_session():
221      for x_shape in x_shapes:
222        for test_style in test_styles:
223          x_np = np.random.rand(*x_shape) * 255.
224          scale = np.random.rand() * 2.0 - 1.0
225          if test_style == 'all_random':
226            pass
227          elif test_style == 'rg_same':
228            x_np[..., 1] = x_np[..., 0]
229          elif test_style == 'rb_same':
230            x_np[..., 2] = x_np[..., 0]
231          elif test_style == 'gb_same':
232            x_np[..., 2] = x_np[..., 1]
233          elif test_style == 'rgb_same':
234            x_np[..., 1] = x_np[..., 0]
235            x_np[..., 2] = x_np[..., 0]
236          else:
237            raise AssertionError('Invalid test style: %s' % (test_style))
238          y_baseline = self._adjust_saturation_in_yiq_np(x_np, scale)
239          y_tf = self._adjust_saturation_in_yiq_tf(x_np, scale)
240          self.assertAllClose(y_tf, y_baseline, rtol=2e-4, atol=1e-4)
241
242  def test_invalid_shapes(self):
243    x_np = np.random.rand(2, 3) * 255.
244    scale = np.random.rand() * 2.0 - 1.0
245    with self.assertRaisesRegexp(ValueError, 'Shape must be at least rank 3'):
246      self._adjust_saturation_in_yiq_tf(x_np, scale)
247    x_np = np.random.rand(4, 2, 4) * 255.
248    scale = np.random.rand() * 2.0 - 1.0
249    with self.assertRaisesOpError('input must have 3 channels but instead has '
250                                  '4 channels'):
251      self._adjust_saturation_in_yiq_tf(x_np, scale)
252
253
254class AdjustHueInYiqBenchmark(test.Benchmark):
255
256  def _benchmark_adjust_hue_in_yiq(self, device, cpu_count):
257    image_shape = [299, 299, 3]
258    warmup_rounds = 100
259    benchmark_rounds = 1000
260    config = config_pb2.ConfigProto()
261    if cpu_count is not None:
262      config.inter_op_parallelism_threads = 1
263      config.intra_op_parallelism_threads = cpu_count
264    with session.Session('', graph=ops.Graph(), config=config) as sess:
265      with ops.device(device):
266        inputs = variables.Variable(
267            random_ops.random_uniform(image_shape, dtype=dtypes.float32) * 255,
268            trainable=False,
269            dtype=dtypes.float32)
270        delta = constant_op.constant(0.1, dtype=dtypes.float32)
271        outputs = distort_image_ops.adjust_hsv_in_yiq(inputs, delta, 1, 1)
272        run_op = control_flow_ops.group(outputs)
273        sess.run(variables.global_variables_initializer())
274        for i in xrange(warmup_rounds + benchmark_rounds):
275          if i == warmup_rounds:
276            start = time.time()
277          sess.run(run_op)
278    end = time.time()
279    step_time = (end - start) / benchmark_rounds
280    tag = device + '_%s' % (cpu_count if cpu_count is not None else 'all')
281    print('benchmarkadjust_hue_in_yiq_299_299_3_%s step_time: %.2f us' %
282          (tag, step_time * 1e6))
283    self.report_benchmark(
284        name='benchmarkadjust_hue_in_yiq_299_299_3_%s' % (tag),
285        iters=benchmark_rounds,
286        wall_time=step_time)
287
288  def benchmark_adjust_hue_in_yiqCpu1(self):
289    self._benchmark_adjust_hue_in_yiq('/cpu:0', 1)
290
291  def benchmark_adjust_hue_in_yiqCpuAll(self):
292    self._benchmark_adjust_hue_in_yiq('/cpu:0', None)
293
294  def benchmark_adjust_hue_in_yiq_gpu_all(self):
295    self._benchmark_adjust_hue_in_yiq(test.gpu_device_name(), None)
296
297
298class AdjustSaturationInYiqBenchmark(test.Benchmark):
299
300  def _benchmark_adjust_saturation_in_yiq(self, device, cpu_count):
301    image_shape = [299, 299, 3]
302    warmup_rounds = 100
303    benchmark_rounds = 1000
304    config = config_pb2.ConfigProto()
305    if cpu_count is not None:
306      config.inter_op_parallelism_threads = 1
307      config.intra_op_parallelism_threads = cpu_count
308    with session.Session('', graph=ops.Graph(), config=config) as sess:
309      with ops.device(device):
310        inputs = variables.Variable(
311            random_ops.random_uniform(image_shape, dtype=dtypes.float32) * 255,
312            trainable=False,
313            dtype=dtypes.float32)
314        scale = constant_op.constant(0.1, dtype=dtypes.float32)
315        outputs = distort_image_ops.adjust_hsv_in_yiq(inputs, 0, scale, 1)
316        run_op = control_flow_ops.group(outputs)
317        sess.run(variables.global_variables_initializer())
318        for _ in xrange(warmup_rounds):
319          sess.run(run_op)
320        start = time.time()
321        for _ in xrange(benchmark_rounds):
322          sess.run(run_op)
323    end = time.time()
324    step_time = (end - start) / benchmark_rounds
325    tag = '%s' % (cpu_count) if cpu_count is not None else '_all'
326    print('benchmarkAdjustSaturationInYiq_299_299_3_cpu%s step_time: %.2f us' %
327          (tag, step_time * 1e6))
328    self.report_benchmark(
329        name='benchmarkAdjustSaturationInYiq_299_299_3_cpu%s' % (tag),
330        iters=benchmark_rounds,
331        wall_time=step_time)
332
333  def benchmark_adjust_saturation_in_yiq_cpu1(self):
334    self._benchmark_adjust_saturation_in_yiq('/cpu:0', 1)
335
336  def benchmark_adjust_saturation_in_yiq_cpu_all(self):
337    self._benchmark_adjust_saturation_in_yiq('/cpu:0', None)
338
339  def benchmark_adjust_saturation_in_yiq_gpu_all(self):
340    self._benchmark_adjust_saturation_in_yiq(test.gpu_device_name(), None)
341
342
343if __name__ == '__main__':
344  googletest.main()
345