# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for DecodeJpegOp."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import time

from six.moves import xrange  # pylint: disable=redefined-builtin
from tensorflow.python.client import session
from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import control_flow_ops
from tensorflow.python.ops import image_ops
from tensorflow.python.ops import io_ops
from tensorflow.python.ops import variable_scope
from tensorflow.python.ops import variables
from tensorflow.python.platform import test

prefix_path = 'third_party/tensorflow/core/lib/jpeg/testdata'


class DecodeJpegBenchmark(test.Benchmark):
  """Evaluate tensorflow DecodeJpegOp performance."""

  def _evalDecodeJpeg(self,
                      image_name,
                      parallelism,
                      num_iters,
                      crop_during_decode=None,
                      crop_window=None,
                      tile=None):
    """Evaluate DecodeJpegOp for the given image.

    TODO(tanmingxing): add decoding+cropping as well.

    Args:
      image_name: a string of image file name (without suffix).
      parallelism: the number of concurrent decode_jpeg ops to be run.
      num_iters: number of iterations for evaluation.
      crop_during_decode: If true, use fused DecodeAndCropJpeg instead of
          separate decode and crop ops. It is ignored if crop_window is None.
      crop_window: if not None, crop the decoded image. Depending on
          crop_during_decode, cropping could happen during or after decoding.
      tile: if not None, tile the image to composite a larger fake image.

    Returns:
      The duration of the run in seconds.
    """
    ops.reset_default_graph()

    image_file_path = os.path.join(prefix_path, image_name)

    if tile is None:
      image_content = variable_scope.get_variable(
          'image_%s' % image_name,
          initializer=io_ops.read_file(image_file_path))
    else:
      single_image = image_ops.decode_jpeg(
          io_ops.read_file(image_file_path), channels=3, name='single_image')
      # Tile the image to composite a new larger image.
      tiled_image = array_ops.tile(single_image, tile)
      image_content = variable_scope.get_variable(
          'tiled_image_%s' % image_name,
          initializer=image_ops.encode_jpeg(tiled_image))

    with session.Session() as sess:
      self.evaluate(variables.global_variables_initializer())
      images = []
      for _ in xrange(parallelism):
        if crop_window is None:
          # No crop.
          image = image_ops.decode_jpeg(image_content, channels=3)
        elif crop_during_decode:
          # combined decode and crop.
          image = image_ops.decode_and_crop_jpeg(
              image_content, crop_window, channels=3)
        else:
          # separate decode and crop.
          image = image_ops.decode_jpeg(image_content, channels=3)
          image = image_ops.crop_to_bounding_box(
              image,
              offset_height=crop_window[0],
              offset_width=crop_window[1],
              target_height=crop_window[2],
              target_width=crop_window[3])

        images.append(image)
      r = control_flow_ops.group(*images)

      for _ in xrange(3):
        # Skip warm up time.
        self.evaluate(r)

      start_time = time.time()
      for _ in xrange(num_iters):
        self.evaluate(r)
      end_time = time.time()
    return end_time - start_time

  def benchmarkDecodeJpegSmall(self):
    """Evaluate single DecodeImageOp for small size image."""
    num_iters = 10
    crop_window = [10, 10, 50, 50]
    for parallelism in [1, 100]:
      duration_decode = self._evalDecodeJpeg('small.jpg', parallelism,
                                             num_iters)
      duration_decode_crop = self._evalDecodeJpeg('small.jpg', parallelism,
                                                  num_iters, False, crop_window)
      duration_decode_after_crop = self._evalDecodeJpeg(
          'small.jpg', parallelism, num_iters, True, crop_window)
      self.report_benchmark(
          name='decode_jpeg_small_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode)
      self.report_benchmark(
          name='decode_crop_jpeg_small_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_crop)
      self.report_benchmark(
          name='decode_after_crop_jpeg_small_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_after_crop)

  def benchmarkDecodeJpegMedium(self):
    """Evaluate single DecodeImageOp for medium size image."""
    num_iters = 10
    crop_window = [10, 10, 50, 50]
    for parallelism in [1, 100]:
      duration_decode = self._evalDecodeJpeg('medium.jpg', parallelism,
                                             num_iters)
      duration_decode_crop = self._evalDecodeJpeg('medium.jpg', parallelism,
                                                  num_iters, False, crop_window)
      duration_decode_after_crop = self._evalDecodeJpeg(
          'medium.jpg', parallelism, num_iters, True, crop_window)
      self.report_benchmark(
          name='decode_jpeg_medium_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode)
      self.report_benchmark(
          name='decode_crop_jpeg_medium_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_crop)
      self.report_benchmark(
          name='decode_after_crop_jpeg_medium_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_after_crop)

  def benchmarkDecodeJpegLarge(self):
    """Evaluate single DecodeImageOp for large size image."""
    num_iters = 10
    crop_window = [10, 10, 50, 50]
    tile = [4, 4, 1]
    for parallelism in [1, 100]:
      # Tile the medium size image to composite a larger fake image.
      duration_decode = self._evalDecodeJpeg('medium.jpg', parallelism,
                                             num_iters, tile)
      duration_decode_crop = self._evalDecodeJpeg(
          'medium.jpg', parallelism, num_iters, False, crop_window, tile)
      duration_decode_after_crop = self._evalDecodeJpeg(
          'medium.jpg', parallelism, num_iters, True, crop_window, tile)
      self.report_benchmark(
          name='decode_jpeg_large_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode)
      self.report_benchmark(
          name='decode_crop_jpeg_large_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_crop)
      self.report_benchmark(
          name='decode_after_crop_jpeg_large_p%d' % (parallelism),
          iters=num_iters,
          wall_time=duration_decode_after_crop)


if __name__ == '__main__':
  test.main()
