#coding=utf-8
import tensorflow as tf
import tfop

contrib_image = tf.contrib.image

def blend(image1, image2, factor):
  """Blend image1 and image2 using 'factor'.

  Factor can be above 0.0.  A value of 0.0 means only image1 is used.
  A value of 1.0 means only image2 is used.  A value between 0.0 and
  1.0 means we linearly interpolate the pixel values between the two
  images.  A value greater than 1.0 "extrapolates" the difference
  between the two pixel values, and we clip the results to values
  between 0 and 255.

  Args:
    image1: An image Tensor of type uint8.
    image2: An image Tensor of type uint8.
    factor: A floating point value above 0.0.

  Returns:
    A blended image Tensor of type uint8.
  """
  dtype = image1.dtype
  if factor == 0.0:
    return tf.convert_to_tensor(image1)
  if factor == 1.0:
    return tf.convert_to_tensor(image2)

  image1 = tf.to_float(image1)
  image2 = tf.to_float(image2)

  difference = image2 - image1
  scaled = factor * difference

  # Do addition in float.
  temp = tf.to_float(image1) + scaled

  # Interpolate
  if factor > 0.0 and factor < 1.0:
    # Interpolation means we always stay within 0 and 255.
    return tf.cast(temp, tf.uint8)

  # Extrapolate:
  #
  # We need to clip and then cast.
  return tf.cast(tf.clip_by_value(temp, 0.0, 255.0), dtype)

def color(image, factor):
  """Equivalent of PIL Color."""
  degenerate = tf.image.grayscale_to_rgb(tf.image.rgb_to_grayscale(image))
  return blend(degenerate, image, factor)

def unwrap(image, replace):
  """Unwraps an image produced by wrap.

  Where there is a 0 in the last channel for every spatial position,
  the rest of the three channels in that spatial dimension are grayed
  (set to 128).  Operations like translate and shear on a wrapped
  Tensor will leave 0s in empty locations.  Some transformations look
  at the intensity of values to do preprocessing, and we want these
  empty pixels to assume the 'average' value, rather than pure black.


  Args:
    image: A 3D Image Tensor with 4 channels.
    replace: A one or three value 1D tensor to fill empty pixels.

  Returns:
    image: A 3D image Tensor with 3 channels.
  """
  image_shape = tf.shape(image)
  # Flatten the spatial dimensions.
  flattened_image = tf.reshape(image, [-1, image_shape[2]])

  # Find all pixels where the last channel is zero.
  alpha_channel = flattened_image[:, -1]

  replace = tf.concat([replace, tf.ones([1], image.dtype)], 0)

  # Where they are zero, fill them in with 'replace'.
  flattened_image = tf.where(
      tf.equal(alpha_channel, 0),
      tf.ones_like(flattened_image, dtype=image.dtype) * replace,
      flattened_image)

  image = tf.reshape(flattened_image, image_shape)
  image = tf.slice(image, [0, 0, 0], [image_shape[0], image_shape[1], 3])
  return image

def wrap(image):
  """Returns 'image' with an extra channel set to all 1s."""
  shape = tf.shape(image)
  extended_channel = tf.ones([shape[0], shape[1], 1], image.dtype)
  extended = tf.concat([image, extended_channel], 2)
  return extended

def translate_x(image, pixels, replace):
  """Equivalent of PIL Translate in X dimension."""
  if image is None:
    return None
  image = contrib_image.translate(wrap(image), [-pixels, 0])
  return unwrap(image, replace)


def translate_y(image, pixels, replace):
  """Equivalent of PIL Translate in Y dimension."""
  if image is None:
    return None
  image = contrib_image.translate(wrap(image), [0, -pixels])
  return unwrap(image, replace)

def _clip_bbox(min_y, min_x, max_y, max_x):
  """Clip bounding box coordinates between 0 and 1.

  Args:
    min_y: Normalized bbox coordinate of type float between 0 and 1.
    min_x: Normalized bbox coordinate of type float between 0 and 1.
    max_y: Normalized bbox coordinate of type float between 0 and 1.
    max_x: Normalized bbox coordinate of type float between 0 and 1.

  Returns:
    Clipped coordinate values between 0 and 1.
  """
  min_y = tf.clip_by_value(min_y, 0.0, 1.0)
  min_x = tf.clip_by_value(min_x, 0.0, 1.0)
  max_y = tf.clip_by_value(max_y, 0.0, 1.0)
  max_x = tf.clip_by_value(max_x, 0.0, 1.0)
  return min_y, min_x, max_y, max_x


def _check_bbox_area(min_y, min_x, max_y, max_x, delta=0.05):
  """Adjusts bbox coordinates to make sure the area is > 0.

  Args:
    min_y: Normalized bbox coordinate of type float between 0 and 1.
    min_x: Normalized bbox coordinate of type float between 0 and 1.
    max_y: Normalized bbox coordinate of type float between 0 and 1.
    max_x: Normalized bbox coordinate of type float between 0 and 1.
    delta: Float, this is used to create a gap of size 2 * delta between
      bbox min/max coordinates that are the same on the boundary.
      This prevents the bbox from having an area of zero.

  Returns:
    Tuple of new bbox coordinates between 0 and 1 that will now have a
    guaranteed area > 0.
  """
  height = max_y - min_y
  width = max_x - min_x
  def _adjust_bbox_boundaries(min_coord, max_coord):
    # Make sure max is never 0 and min is never 1.
    max_coord = tf.maximum(max_coord, 0.0 + delta)
    min_coord = tf.minimum(min_coord, 1.0 - delta)
    return min_coord, max_coord
  min_y, max_y = tf.cond(tf.equal(height, 0.0),
                         lambda: _adjust_bbox_boundaries(min_y, max_y),
                         lambda: (min_y, max_y))
  min_x, max_x = tf.cond(tf.equal(width, 0.0),
                         lambda: _adjust_bbox_boundaries(min_x, max_x),
                         lambda: (min_x, max_x))
  return min_y, min_x, max_y, max_x

def _shift_bbox(bbox, image_height, image_width, pixels, shift_horizontal):
  """Shifts the bbox coordinates by pixels.

  Args:
    bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x)
      of type float that represents the normalized coordinates between 0 and 1.
    image_height: Int, height of the image.
    image_width: Int, width of the image.
    pixels: An int. How many pixels to shift the bbox.
    shift_horizontal: Boolean. If true then shift in X dimension else shift in
      Y dimension.

  Returns:
    A tensor of the same shape as bbox, but now with the shifted coordinates.
  """
  pixels = tf.to_int32(pixels)
  # Convert bbox to integer pixel locations.
  min_y = tf.to_int32(tf.to_float(image_height) * bbox[0])
  min_x = tf.to_int32(tf.to_float(image_width) * bbox[1])
  max_y = tf.to_int32(tf.to_float(image_height) * bbox[2])
  max_x = tf.to_int32(tf.to_float(image_width) * bbox[3])

  if shift_horizontal:
    min_x = tf.maximum(0, min_x - pixels)
    max_x = tf.minimum(image_width, max_x - pixels)
  else:
    min_y = tf.maximum(0, min_y - pixels)
    max_y = tf.minimum(image_height, max_y - pixels)

  # Convert bbox back to floats.
  min_y = tf.to_float(min_y) / tf.to_float(image_height)
  min_x = tf.to_float(min_x) / tf.to_float(image_width)
  max_y = tf.to_float(max_y) / tf.to_float(image_height)
  max_x = tf.to_float(max_x) / tf.to_float(image_width)

  # Clip the bboxes to be sure the fall between [0, 1].
  min_y, min_x, max_y, max_x = _clip_bbox(min_y, min_x, max_y, max_x)
  min_y, min_x, max_y, max_x = _check_bbox_area(min_y, min_x, max_y, max_x)
  return tf.stack([min_y, min_x, max_y, max_x])


def translate_bbox(image, mask,bboxes, pixels, replace, shift_horizontal):
  """Equivalent of PIL Translate in X/Y dimension that shifts image and bbox.

  Args:
    image: 3D uint8 Tensor.
    bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox
      has 4 elements (min_y, min_x, max_y, max_x) of type float with values
      between [0, 1].
    pixels: An int. How many pixels to shift the image and bboxes
    replace: A one or three value 1D tensor to fill empty pixels.
    shift_horizontal: Boolean. If true then shift in X dimension else shift in
      Y dimension.

  Returns:
    A tuple containing a 3D uint8 Tensor that will be the result of translating
    image by pixels. The second element of the tuple is bboxes, where now
    the coordinates will be shifted to reflect the shifted image.
  """
  if shift_horizontal:
    image = translate_x(image, pixels, replace)
    mask = translate_x(mask, pixels, 0)
  else:
    image = translate_y(image, pixels, replace)
    mask = translate_y(mask, pixels, 0)

  # Convert bbox coordinates to pixel values.
  image_height = tf.shape(image)[0]
  image_width = tf.shape(image)[1]
  # pylint:disable=g-long-lambda
  wrapped_shift_bbox = lambda bbox: _shift_bbox(
      bbox, image_height, image_width, pixels, shift_horizontal)
  # pylint:enable=g-long-lambda
  bboxes = tf.map_fn(wrapped_shift_bbox, bboxes)
  return image, mask,bboxes

def equalize(image):
  """Implements Equalize function from PIL using TF ops."""
  dtype = image.dtype
  def scale_channel(im, c):
    """Scale the data in the channel to implement equalize."""
    im = tf.cast(im[:, :, c], tf.int32)
    # Compute the histogram of the image channel.
    histo = tf.histogram_fixed_width(im, [0, 255], nbins=256)

    # For the purposes of computing the step, filter out the nonzeros.
    nonzero = tf.where(tf.not_equal(histo, 0))
    nonzero_histo = tf.reshape(tf.gather(histo, nonzero), [-1])
    step = (tf.reduce_sum(nonzero_histo) - nonzero_histo[-1]) // 255

    def build_lut(histo, step):
      # Compute the cumulative sum, shifting by step // 2
      # and then normalization by step.
      lut = (tf.cumsum(histo) + (step // 2)) // step
      # Shift lut, prepending with 0.
      lut = tf.concat([[0], lut[:-1]], 0)
      # Clip the counts to be in range.  This is done
      # in the C code for image.point.
      return tf.clip_by_value(lut, 0, 255)

    # If step is zero, return the original image.  Otherwise, build
    # lut from the full histogram and step and then index from it.
    result = tf.cond(tf.equal(step, 0),
                     lambda: im,
                     lambda: tf.gather(build_lut(histo, step), im))

    return tf.cast(result, dtype)

  # Assumes RGB for now.  Scales each channel independently
  # and then stacks the result.
  s1 = scale_channel(image, 0)
  s2 = scale_channel(image, 1)
  s3 = scale_channel(image, 2)
  image = tf.stack([s1, s2, s3], 2)
  return image

def cutout(image, pad_size, replace=0):
  """Apply cutout (https://arxiv.org/abs/1708.04552) to image.

  This operation applies a (2*pad_size x 2*pad_size) mask of zeros to
  a random location within `img`. The pixel values filled in will be of the
  value `replace`. The located where the mask will be applied is randomly
  chosen uniformly over the whole image.

  Args:
    image: An image Tensor of type uint8.
    pad_size: Specifies how big the zero mask that will be generated is that
      is applied to the image. The mask will be of size
      (2*pad_size x 2*pad_size).
    replace: What pixel value to fill in the image in the area that has
      the cutout mask applied to it.

  Returns:
    An image Tensor that is of type uint8.
  """
  image_height = tf.shape(image)[0]
  image_width = tf.shape(image)[1]

  # Sample the center location in the image where the zero mask will be applied.
  cutout_center_height = tf.random_uniform(
      shape=[], minval=0, maxval=image_height,
      dtype=tf.int32)

  cutout_center_width = tf.random_uniform(
      shape=[], minval=0, maxval=image_width,
      dtype=tf.int32)

  lower_pad = tf.maximum(0, cutout_center_height - pad_size)
  upper_pad = tf.maximum(0, image_height - cutout_center_height - pad_size)
  left_pad = tf.maximum(0, cutout_center_width - pad_size)
  right_pad = tf.maximum(0, image_width - cutout_center_width - pad_size)

  cutout_shape = [image_height - (lower_pad + upper_pad),
                  image_width - (left_pad + right_pad)]
  padding_dims = [[lower_pad, upper_pad], [left_pad, right_pad]]
  mask = tf.pad(
      tf.zeros(cutout_shape, dtype=image.dtype),
      padding_dims, constant_values=1)
  mask = tf.expand_dims(mask, -1)
  mask = tf.tile(mask, [1, 1, 3])
  image = tf.where(
      tf.equal(mask, 0),
      tf.ones_like(image, dtype=image.dtype) * replace,
      image)
  return image

def shear_x(image, level, replace):
  """Equivalent of PIL Shearing in X dimension."""
  # Shear parallel to x axis is a projective transform
  # with a matrix form of:
  # [1  level
  #  0  1].
  if replace is not None:
    image = contrib_image.transform(
      wrap(image), [1., level, 0., 0., 1., 0., 0., 0.])
    return unwrap(image, replace)
  else:
    image = contrib_image.transform(
      image, [1., level, 0., 0., 1., 0., 0., 0.])
    return image


def shear_y(image, level, replace):
  """Equivalent of PIL Shearing in Y dimension."""
  # Shear parallel to y axis is a projective transform
  # with a matrix form of:
  # [1  0
  #  level  1].
  if replace is not None:
    image = contrib_image.transform(
        wrap(image), [1., 0., 0., level, 1., 0., 0., 0.])
    return unwrap(image, replace)
  else:
    image = contrib_image.transform(
      image, [1., 0., 0., level, 1., 0., 0., 0.])
    return image

def _shear_bbox(bbox, image_height, image_width, level, shear_horizontal):
  """Shifts the bbox according to how the image was sheared.

  Args:
    bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x), absolute coordinate
    image_height: Int, height of the image.
    image_width: Int, height of the image.
    level: Float. How much to shear the image.
    shear_horizontal: If true then shear in X dimension else shear in
      the Y dimension.

  Returns:
    A tensor of the same shape as bbox, but now with the shifted coordinates.
  """
  image_height, image_width = (
      tf.to_float(image_height), tf.to_float(image_width))

  # Change bbox coordinates to be pixels.
  min_y = bbox[0]
  min_x = bbox[1]
  max_y = bbox[2]
  max_x = bbox[3]
  coordinates = tf.stack(
      [[min_y, min_x], [min_y, max_x], [max_y, min_x], [max_y, max_x]])
  coordinates = coordinates

  # Shear the coordinates according to the translation matrix.
  if shear_horizontal:
    translation_matrix = tf.stack(
        [[1, 0], [-level, 1]])
  else:
    translation_matrix = tf.stack(
        [[1, -level], [0, 1]])
  translation_matrix = tf.cast(translation_matrix, tf.float32)
  new_coords = tf.cast(
      tf.matmul(translation_matrix, tf.transpose(coordinates)), tf.int32)

  # Find min/max values and convert them back to floats.
  min_y = tf.to_float(tf.reduce_min(new_coords[0, :]))
  min_x = tf.to_float(tf.reduce_min(new_coords[1, :]))
  max_y = tf.to_float(tf.reduce_max(new_coords[0, :]))
  max_x = tf.to_float(tf.reduce_max(new_coords[1, :]))

  # Clip the bboxes to be sure the fall between [0, 1].
  min_y = tf.clip_by_value(min_y,clip_value_min=0,clip_value_max=image_height-1)
  max_y = tf.clip_by_value(max_y,clip_value_min=0,clip_value_max=image_height-1)
  min_x = tf.clip_by_value(min_x,clip_value_min=0,clip_value_max=image_width-1)
  max_x = tf.clip_by_value(max_x,clip_value_min=0,clip_value_max=image_width-1)
  return tf.stack([min_y, min_x, max_y, max_x])

def shear_with_bboxes(image, bboxes, mask,level, replace, shear_horizontal):
  """Applies Shear Transformation to the image and shifts the bboxes.

  Args:
    image: 3D uint8 Tensor.
    bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox
      has 4 elements (min_y, min_x, max_y, max_x) of type float with values
      between [0, 1].
    level: Float. How much to shear the image. This value will be between
      -0.3 to 0.3.
    replace: A one or three value 1D tensor to fill empty pixels.
    shear_horizontal: Boolean. If true then shear in X dimension else shear in
      the Y dimension.

  Returns:
    A tuple containing a 3D uint8 Tensor that will be the result of shearing
    image by level. The second element of the tuple is bboxes, where now
    the coordinates will be shifted to reflect the sheared image.
  """
  if shear_horizontal:
    image = shear_x(image, level, replace)
  else:
    image = shear_y(image, level, replace)

  if mask is not None:
    mask = tf.transpose(mask,[1,2,0])
    if shear_horizontal:
      mask = shear_x(mask,level,None)
    else:
      mask = shear_y(mask,level,None)
    mask = tf.transpose(mask,[2,0,1])
    bboxes = tfop.get_bboxes_from_mask(mask,stride=4)
  else:
    # Convert bbox coordinates to pixel values.
    image_height = tf.shape(image)[0]
    image_width = tf.shape(image)[1]
    # pylint:disable=g-long-lambda
    wrapped_shear_bbox = lambda bbox: _shear_bbox(
        bbox, image_height, image_width, level, shear_horizontal)
    # pylint:enable=g-long-lambda
    bboxes = tf.map_fn(wrapped_shear_bbox, bboxes)
  return image, bboxes, mask

#coding=utf-8
import tensorflow as tf
import tfop

contrib_image = tf.contrib.image

def blend(image1, image2, factor):
  """Blend image1 and image2 using 'factor'.

  Factor can be above 0.0.  A value of 0.0 means only image1 is used.
  A value of 1.0 means only image2 is used.  A value between 0.0 and
  1.0 means we linearly interpolate the pixel values between the two
  images.  A value greater than 1.0 "extrapolates" the difference
  between the two pixel values, and we clip the results to values
  between 0 and 255.

  Args:
    image1: An image Tensor of type uint8.
    image2: An image Tensor of type uint8.
    factor: A floating point value above 0.0.

  Returns:
    A blended image Tensor of type uint8.
  """
  dtype = image1.dtype
  if factor == 0.0:
    return tf.convert_to_tensor(image1)
  if factor == 1.0:
    return tf.convert_to_tensor(image2)

  image1 = tf.to_float(image1)
  image2 = tf.to_float(image2)

  difference = image2 - image1
  scaled = factor * difference

  # Do addition in float.
  temp = tf.to_float(image1) + scaled

  # Interpolate
  if factor > 0.0 and factor < 1.0:
    # Interpolation means we always stay within 0 and 255.
    return tf.cast(temp, tf.uint8)

  # Extrapolate:
  #
  # We need to clip and then cast.
  return tf.cast(tf.clip_by_value(temp, 0.0, 255.0), dtype)

def color(image, factor):
  """Equivalent of PIL Color."""
  degenerate = tf.image.grayscale_to_rgb(tf.image.rgb_to_grayscale(image))
  return blend(degenerate, image, factor)

def unwrap(image, replace):
  """Unwraps an image produced by wrap.

  Where there is a 0 in the last channel for every spatial position,
  the rest of the three channels in that spatial dimension are grayed
  (set to 128).  Operations like translate and shear on a wrapped
  Tensor will leave 0s in empty locations.  Some transformations look
  at the intensity of values to do preprocessing, and we want these
  empty pixels to assume the 'average' value, rather than pure black.


  Args:
    image: A 3D Image Tensor with 4 channels.
    replace: A one or three value 1D tensor to fill empty pixels.

  Returns:
    image: A 3D image Tensor with 3 channels.
  """
  image_shape = tf.shape(image)
  # Flatten the spatial dimensions.
  flattened_image = tf.reshape(image, [-1, image_shape[2]])

  # Find all pixels where the last channel is zero.
  alpha_channel = flattened_image[:, -1]

  replace = tf.concat([replace, tf.ones([1], image.dtype)], 0)

  # Where they are zero, fill them in with 'replace'.
  flattened_image = tf.where(
      tf.equal(alpha_channel, 0),
      tf.ones_like(flattened_image, dtype=image.dtype) * replace,
      flattened_image)

  image = tf.reshape(flattened_image, image_shape)
  image = tf.slice(image, [0, 0, 0], [image_shape[0], image_shape[1], 3])
  return image

def wrap(image):
  """Returns 'image' with an extra channel set to all 1s."""
  shape = tf.shape(image)
  extended_channel = tf.ones([shape[0], shape[1], 1], image.dtype)
  extended = tf.concat([image, extended_channel], 2)
  return extended

def translate_x(image, pixels, replace):
  """Equivalent of PIL Translate in X dimension."""
  if image is None:
    return None
  image = contrib_image.translate(wrap(image), [-pixels, 0])
  return unwrap(image, replace)


def translate_y(image, pixels, replace):
  """Equivalent of PIL Translate in Y dimension."""
  if image is None:
    return None
  image = contrib_image.translate(wrap(image), [0, -pixels])
  return unwrap(image, replace)

def _clip_bbox(min_y, min_x, max_y, max_x):
  """Clip bounding box coordinates between 0 and 1.

  Args:
    min_y: Normalized bbox coordinate of type float between 0 and 1.
    min_x: Normalized bbox coordinate of type float between 0 and 1.
    max_y: Normalized bbox coordinate of type float between 0 and 1.
    max_x: Normalized bbox coordinate of type float between 0 and 1.

  Returns:
    Clipped coordinate values between 0 and 1.
  """
  min_y = tf.clip_by_value(min_y, 0.0, 1.0)
  min_x = tf.clip_by_value(min_x, 0.0, 1.0)
  max_y = tf.clip_by_value(max_y, 0.0, 1.0)
  max_x = tf.clip_by_value(max_x, 0.0, 1.0)
  return min_y, min_x, max_y, max_x


def _check_bbox_area(min_y, min_x, max_y, max_x, delta=0.05):
  """Adjusts bbox coordinates to make sure the area is > 0.

  Args:
    min_y: Normalized bbox coordinate of type float between 0 and 1.
    min_x: Normalized bbox coordinate of type float between 0 and 1.
    max_y: Normalized bbox coordinate of type float between 0 and 1.
    max_x: Normalized bbox coordinate of type float between 0 and 1.
    delta: Float, this is used to create a gap of size 2 * delta between
      bbox min/max coordinates that are the same on the boundary.
      This prevents the bbox from having an area of zero.

  Returns:
    Tuple of new bbox coordinates between 0 and 1 that will now have a
    guaranteed area > 0.
  """
  height = max_y - min_y
  width = max_x - min_x
  def _adjust_bbox_boundaries(min_coord, max_coord):
    # Make sure max is never 0 and min is never 1.
    max_coord = tf.maximum(max_coord, 0.0 + delta)
    min_coord = tf.minimum(min_coord, 1.0 - delta)
    return min_coord, max_coord
  min_y, max_y = tf.cond(tf.equal(height, 0.0),
                         lambda: _adjust_bbox_boundaries(min_y, max_y),
                         lambda: (min_y, max_y))
  min_x, max_x = tf.cond(tf.equal(width, 0.0),
                         lambda: _adjust_bbox_boundaries(min_x, max_x),
                         lambda: (min_x, max_x))
  return min_y, min_x, max_y, max_x

def _shift_bbox(bbox, image_height, image_width, pixels, shift_horizontal):
  """Shifts the bbox coordinates by pixels.

  Args:
    bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x)
      of type float that represents the normalized coordinates between 0 and 1.
    image_height: Int, height of the image.
    image_width: Int, width of the image.
    pixels: An int. How many pixels to shift the bbox.
    shift_horizontal: Boolean. If true then shift in X dimension else shift in
      Y dimension.

  Returns:
    A tensor of the same shape as bbox, but now with the shifted coordinates.
  """
  pixels = tf.to_int32(pixels)
  # Convert bbox to integer pixel locations.
  min_y = tf.to_int32(tf.to_float(image_height) * bbox[0])
  min_x = tf.to_int32(tf.to_float(image_width) * bbox[1])
  max_y = tf.to_int32(tf.to_float(image_height) * bbox[2])
  max_x = tf.to_int32(tf.to_float(image_width) * bbox[3])

  if shift_horizontal:
    min_x = tf.maximum(0, min_x - pixels)
    max_x = tf.minimum(image_width, max_x - pixels)
  else:
    min_y = tf.maximum(0, min_y - pixels)
    max_y = tf.minimum(image_height, max_y - pixels)

  # Convert bbox back to floats.
  min_y = tf.to_float(min_y) / tf.to_float(image_height)
  min_x = tf.to_float(min_x) / tf.to_float(image_width)
  max_y = tf.to_float(max_y) / tf.to_float(image_height)
  max_x = tf.to_float(max_x) / tf.to_float(image_width)

  # Clip the bboxes to be sure the fall between [0, 1].
  min_y, min_x, max_y, max_x = _clip_bbox(min_y, min_x, max_y, max_x)
  min_y, min_x, max_y, max_x = _check_bbox_area(min_y, min_x, max_y, max_x)
  return tf.stack([min_y, min_x, max_y, max_x])


def translate_bbox(image, mask,bboxes, pixels, replace, shift_horizontal):
  """Equivalent of PIL Translate in X/Y dimension that shifts image and bbox.

  Args:
    image: 3D uint8 Tensor.
    bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox
      has 4 elements (min_y, min_x, max_y, max_x) of type float with values
      between [0, 1].
    pixels: An int. How many pixels to shift the image and bboxes
    replace: A one or three value 1D tensor to fill empty pixels.
    shift_horizontal: Boolean. If true then shift in X dimension else shift in
      Y dimension.

  Returns:
    A tuple containing a 3D uint8 Tensor that will be the result of translating
    image by pixels. The second element of the tuple is bboxes, where now
    the coordinates will be shifted to reflect the shifted image.
  """
  if shift_horizontal:
    image = translate_x(image, pixels, replace)
    mask = translate_x(mask, pixels, 0)
  else:
    image = translate_y(image, pixels, replace)
    mask = translate_y(mask, pixels, 0)

  # Convert bbox coordinates to pixel values.
  image_height = tf.shape(image)[0]
  image_width = tf.shape(image)[1]
  # pylint:disable=g-long-lambda
  wrapped_shift_bbox = lambda bbox: _shift_bbox(
      bbox, image_height, image_width, pixels, shift_horizontal)
  # pylint:enable=g-long-lambda
  bboxes = tf.map_fn(wrapped_shift_bbox, bboxes)
  return image, mask,bboxes

def equalize(image):
  """Implements Equalize function from PIL using TF ops."""
  dtype = image.dtype
  def scale_channel(im, c):
    """Scale the data in the channel to implement equalize."""
    im = tf.cast(im[:, :, c], tf.int32)
    # Compute the histogram of the image channel.
    histo = tf.histogram_fixed_width(im, [0, 255], nbins=256)

    # For the purposes of computing the step, filter out the nonzeros.
    nonzero = tf.where(tf.not_equal(histo, 0))
    nonzero_histo = tf.reshape(tf.gather(histo, nonzero), [-1])
    step = (tf.reduce_sum(nonzero_histo) - nonzero_histo[-1]) // 255

    def build_lut(histo, step):
      # Compute the cumulative sum, shifting by step // 2
      # and then normalization by step.
      lut = (tf.cumsum(histo) + (step // 2)) // step
      # Shift lut, prepending with 0.
      lut = tf.concat([[0], lut[:-1]], 0)
      # Clip the counts to be in range.  This is done
      # in the C code for image.point.
      return tf.clip_by_value(lut, 0, 255)

    # If step is zero, return the original image.  Otherwise, build
    # lut from the full histogram and step and then index from it.
    result = tf.cond(tf.equal(step, 0),
                     lambda: im,
                     lambda: tf.gather(build_lut(histo, step), im))

    return tf.cast(result, dtype)

  # Assumes RGB for now.  Scales each channel independently
  # and then stacks the result.
  s1 = scale_channel(image, 0)
  s2 = scale_channel(image, 1)
  s3 = scale_channel(image, 2)
  image = tf.stack([s1, s2, s3], 2)
  return image

def cutout(image, pad_size, replace=0):
  """Apply cutout (https://arxiv.org/abs/1708.04552) to image.

  This operation applies a (2*pad_size x 2*pad_size) mask of zeros to
  a random location within `img`. The pixel values filled in will be of the
  value `replace`. The located where the mask will be applied is randomly
  chosen uniformly over the whole image.

  Args:
    image: An image Tensor of type uint8.
    pad_size: Specifies how big the zero mask that will be generated is that
      is applied to the image. The mask will be of size
      (2*pad_size x 2*pad_size).
    replace: What pixel value to fill in the image in the area that has
      the cutout mask applied to it.

  Returns:
    An image Tensor that is of type uint8.
  """
  image_height = tf.shape(image)[0]
  image_width = tf.shape(image)[1]

  # Sample the center location in the image where the zero mask will be applied.
  cutout_center_height = tf.random_uniform(
      shape=[], minval=0, maxval=image_height,
      dtype=tf.int32)

  cutout_center_width = tf.random_uniform(
      shape=[], minval=0, maxval=image_width,
      dtype=tf.int32)

  lower_pad = tf.maximum(0, cutout_center_height - pad_size)
  upper_pad = tf.maximum(0, image_height - cutout_center_height - pad_size)
  left_pad = tf.maximum(0, cutout_center_width - pad_size)
  right_pad = tf.maximum(0, image_width - cutout_center_width - pad_size)

  cutout_shape = [image_height - (lower_pad + upper_pad),
                  image_width - (left_pad + right_pad)]
  padding_dims = [[lower_pad, upper_pad], [left_pad, right_pad]]
  mask = tf.pad(
      tf.zeros(cutout_shape, dtype=image.dtype),
      padding_dims, constant_values=1)
  mask = tf.expand_dims(mask, -1)
  mask = tf.tile(mask, [1, 1, 3])
  image = tf.where(
      tf.equal(mask, 0),
      tf.ones_like(image, dtype=image.dtype) * replace,
      image)
  return image

def shear_x(image, level, replace):
  """Equivalent of PIL Shearing in X dimension."""
  # Shear parallel to x axis is a projective transform
  # with a matrix form of:
  # [1  level
  #  0  1].
  if replace is not None:
    image = contrib_image.transform(
      wrap(image), [1., level, 0., 0., 1., 0., 0., 0.])
    return unwrap(image, replace)
  else:
    image = contrib_image.transform(
      image, [1., level, 0., 0., 1., 0., 0., 0.])
    return image


def shear_y(image, level, replace):
  """Equivalent of PIL Shearing in Y dimension."""
  # Shear parallel to y axis is a projective transform
  # with a matrix form of:
  # [1  0
  #  level  1].
  if replace is not None:
    image = contrib_image.transform(
        wrap(image), [1., 0., 0., level, 1., 0., 0., 0.])
    return unwrap(image, replace)
  else:
    image = contrib_image.transform(
      image, [1., 0., 0., level, 1., 0., 0., 0.])
    return image

def _shear_bbox(bbox, image_height, image_width, level, shear_horizontal):
  """Shifts the bbox according to how the image was sheared.

  Args:
    bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x), absolute coordinate
    image_height: Int, height of the image.
    image_width: Int, height of the image.
    level: Float. How much to shear the image.
    shear_horizontal: If true then shear in X dimension else shear in
      the Y dimension.

  Returns:
    A tensor of the same shape as bbox, but now with the shifted coordinates.
  """
  image_height, image_width = (
      tf.to_float(image_height), tf.to_float(image_width))

  # Change bbox coordinates to be pixels.
  min_y = bbox[0]
  min_x = bbox[1]
  max_y = bbox[2]
  max_x = bbox[3]
  coordinates = tf.stack(
      [[min_y, min_x], [min_y, max_x], [max_y, min_x], [max_y, max_x]])
  coordinates = coordinates

  # Shear the coordinates according to the translation matrix.
  if shear_horizontal:
    translation_matrix = tf.stack(
        [[1, 0], [-level, 1]])
  else:
    translation_matrix = tf.stack(
        [[1, -level], [0, 1]])
  translation_matrix = tf.cast(translation_matrix, tf.float32)
  new_coords = tf.cast(
      tf.matmul(translation_matrix, tf.transpose(coordinates)), tf.int32)

  # Find min/max values and convert them back to floats.
  min_y = tf.to_float(tf.reduce_min(new_coords[0, :]))
  min_x = tf.to_float(tf.reduce_min(new_coords[1, :]))
  max_y = tf.to_float(tf.reduce_max(new_coords[0, :]))
  max_x = tf.to_float(tf.reduce_max(new_coords[1, :]))

  # Clip the bboxes to be sure the fall between [0, 1].
  min_y = tf.clip_by_value(min_y,clip_value_min=0,clip_value_max=image_height-1)
  max_y = tf.clip_by_value(max_y,clip_value_min=0,clip_value_max=image_height-1)
  min_x = tf.clip_by_value(min_x,clip_value_min=0,clip_value_max=image_width-1)
  max_x = tf.clip_by_value(max_x,clip_value_min=0,clip_value_max=image_width-1)
  return tf.stack([min_y, min_x, max_y, max_x])

def shear_with_bboxes(image, bboxes, mask,level, replace, shear_horizontal):
  """Applies Shear Transformation to the image and shifts the bboxes.

  Args:
    image: 3D uint8 Tensor.
    bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox
      has 4 elements (min_y, min_x, max_y, max_x) of type float with values
      between [0, 1].
    level: Float. How much to shear the image. This value will be between
      -0.3 to 0.3.
    replace: A one or three value 1D tensor to fill empty pixels.
    shear_horizontal: Boolean. If true then shear in X dimension else shear in
      the Y dimension.

  Returns:
    A tuple containing a 3D uint8 Tensor that will be the result of shearing
    image by level. The second element of the tuple is bboxes, where now
    the coordinates will be shifted to reflect the sheared image.
  """
  if shear_horizontal:
    image = shear_x(image, level, replace)
  else:
    image = shear_y(image, level, replace)

  if mask is not None:
    mask = tf.transpose(mask,[1,2,0])
    if shear_horizontal:
      mask = shear_x(mask,level,None)
    else:
      mask = shear_y(mask,level,None)
    mask = tf.transpose(mask,[2,0,1])
    bboxes = tfop.get_bboxes_from_mask(mask,stride=4)
  else:
    # Convert bbox coordinates to pixel values.
    image_height = tf.shape(image)[0]
    image_width = tf.shape(image)[1]
    # pylint:disable=g-long-lambda
    wrapped_shear_bbox = lambda bbox: _shear_bbox(
        bbox, image_height, image_width, level, shear_horizontal)
    # pylint:enable=g-long-lambda
    bboxes = tf.map_fn(wrapped_shear_bbox, bboxes)
  return image, bboxes, mask

#coding=utf-8 Blend image1 and image2 using 'factor'.    Factor can be above 0.0.  A value of 0.0 means only image1 is used.   A value of 1.0 means only image2 is used.  A value between 0.0 and   1.0 means we linearly interpolate the pixel values between the two   images.  A value greater than 1.0 "extrapolates" the difference   between the two pixel values, and we clip the results to values   between 0 and 255.    Args:     image1: An image Tensor of type uint8.     image2: An image Tensor of type uint8.     factor: A floating point value above 0.0.    Returns:     A blended image Tensor of type uint8. # Do addition in float. # Interpolate # Interpolation means we always stay within 0 and 255. # Extrapolate: # # We need to clip and then cast. Equivalent of PIL Color. Unwraps an image produced by wrap.    Where there is a 0 in the last channel for every spatial position,   the rest of the three channels in that spatial dimension are grayed   (set to 128).  Operations like translate and shear on a wrapped   Tensor will leave 0s in empty locations.  Some transformations look   at the intensity of values to do preprocessing, and we want these   empty pixels to assume the 'average' value, rather than pure black.     Args:     image: A 3D Image Tensor with 4 channels.     replace: A one or three value 1D tensor to fill empty pixels.    Returns:     image: A 3D image Tensor with 3 channels. # Flatten the spatial dimensions. # Find all pixels where the last channel is zero. # Where they are zero, fill them in with 'replace'. Returns 'image' with an extra channel set to all 1s. Equivalent of PIL Translate in X dimension. Equivalent of PIL Translate in Y dimension. Clip bounding box coordinates between 0 and 1.    Args:     min_y: Normalized bbox coordinate of type float between 0 and 1.     min_x: Normalized bbox coordinate of type float between 0 and 1.     max_y: Normalized bbox coordinate of type float between 0 and 1.     max_x: Normalized bbox coordinate of type float between 0 and 1.    Returns:     Clipped coordinate values between 0 and 1. Adjusts bbox coordinates to make sure the area is > 0.    Args:     min_y: Normalized bbox coordinate of type float between 0 and 1.     min_x: Normalized bbox coordinate of type float between 0 and 1.     max_y: Normalized bbox coordinate of type float between 0 and 1.     max_x: Normalized bbox coordinate of type float between 0 and 1.     delta: Float, this is used to create a gap of size 2 * delta between       bbox min/max coordinates that are the same on the boundary.       This prevents the bbox from having an area of zero.    Returns:     Tuple of new bbox coordinates between 0 and 1 that will now have a     guaranteed area > 0. # Make sure max is never 0 and min is never 1. Shifts the bbox coordinates by pixels.    Args:     bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x)       of type float that represents the normalized coordinates between 0 and 1.     image_height: Int, height of the image.     image_width: Int, width of the image.     pixels: An int. How many pixels to shift the bbox.     shift_horizontal: Boolean. If true then shift in X dimension else shift in       Y dimension.    Returns:     A tensor of the same shape as bbox, but now with the shifted coordinates. # Convert bbox to integer pixel locations. # Convert bbox back to floats. # Clip the bboxes to be sure the fall between [0, 1]. Equivalent of PIL Translate in X/Y dimension that shifts image and bbox.    Args:     image: 3D uint8 Tensor.     bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox       has 4 elements (min_y, min_x, max_y, max_x) of type float with values       between [0, 1].     pixels: An int. How many pixels to shift the image and bboxes     replace: A one or three value 1D tensor to fill empty pixels.     shift_horizontal: Boolean. If true then shift in X dimension else shift in       Y dimension.    Returns:     A tuple containing a 3D uint8 Tensor that will be the result of translating     image by pixels. The second element of the tuple is bboxes, where now     the coordinates will be shifted to reflect the shifted image. # Convert bbox coordinates to pixel values. # pylint:disable=g-long-lambda # pylint:enable=g-long-lambda Implements Equalize function from PIL using TF ops. Scale the data in the channel to implement equalize. # Compute the histogram of the image channel. # For the purposes of computing the step, filter out the nonzeros. # Compute the cumulative sum, shifting by step // 2 # and then normalization by step. # Shift lut, prepending with 0. # Clip the counts to be in range.  This is done # in the C code for image.point. # If step is zero, return the original image.  Otherwise, build # lut from the full histogram and step and then index from it. # Assumes RGB for now.  Scales each channel independently # and then stacks the result. Apply cutout (https://arxiv.org/abs/1708.04552) to image.    This operation applies a (2*pad_size x 2*pad_size) mask of zeros to   a random location within `img`. The pixel values filled in will be of the   value `replace`. The located where the mask will be applied is randomly   chosen uniformly over the whole image.    Args:     image: An image Tensor of type uint8.     pad_size: Specifies how big the zero mask that will be generated is that       is applied to the image. The mask will be of size       (2*pad_size x 2*pad_size).     replace: What pixel value to fill in the image in the area that has       the cutout mask applied to it.    Returns:     An image Tensor that is of type uint8. # Sample the center location in the image where the zero mask will be applied. Equivalent of PIL Shearing in X dimension. # Shear parallel to x axis is a projective transform # with a matrix form of: # [1  level #  0  1]. Equivalent of PIL Shearing in Y dimension. # Shear parallel to y axis is a projective transform # with a matrix form of: # [1  0 #  level  1]. Shifts the bbox according to how the image was sheared.    Args:     bbox: 1D Tensor that has 4 elements (min_y, min_x, max_y, max_x), absolute coordinate     image_height: Int, height of the image.     image_width: Int, height of the image.     level: Float. How much to shear the image.     shear_horizontal: If true then shear in X dimension else shear in       the Y dimension.    Returns:     A tensor of the same shape as bbox, but now with the shifted coordinates. # Change bbox coordinates to be pixels. # Shear the coordinates according to the translation matrix. # Find min/max values and convert them back to floats. # Clip the bboxes to be sure the fall between [0, 1]. Applies Shear Transformation to the image and shifts the bboxes.    Args:     image: 3D uint8 Tensor.     bboxes: 2D Tensor that is a list of the bboxes in the image. Each bbox       has 4 elements (min_y, min_x, max_y, max_x) of type float with values       between [0, 1].     level: Float. How much to shear the image. This value will be between       -0.3 to 0.3.     replace: A one or three value 1D tensor to fill empty pixels.     shear_horizontal: Boolean. If true then shear in X dimension else shear in       the Y dimension.    Returns:     A tuple containing a 3D uint8 Tensor that will be the result of shearing     image by level. The second element of the tuple is bboxes, where now     the coordinates will be shifted to reflect the sheared image. # Convert bbox coordinates to pixel values. # pylint:disable=g-long-lambda # pylint:enable=g-long-lambda