# Copyright 2018 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.
# ==============================================================================
"""COCO-style evaluation metrics.

Implements the interface of COCO API and metric_fn in tf.TPUEstimator.

COCO API: github.com/cocodataset/cocoapi/
"""

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

import numpy as np
import pycocotools.mask as maskUtils
import cv2


def expand_boxes(boxes, scale):
  """Expands an array of boxes by a given scale."""
  # Reference: https://github.com/facebookresearch/Detectron/blob/master/detectron/utils/boxes.py#L227  # pylint: disable=line-too-long
  # The `boxes` in the reference implementation is in [x1, y1, x2, y2] form,
  # whereas the input `boxes` is in [x1, y1, w, h] form.
  w_half = boxes[:, 2] * .5
  h_half = boxes[:, 3] * .5
  x_c = boxes[:, 0] + w_half
  y_c = boxes[:, 1] + h_half

  w_half *= scale
  h_half *= scale

  # Outputs are in [x1, y1, x2, y2] form.
  boxes_exp = np.zeros(boxes.shape)
  boxes_exp[:, 0] = x_c - w_half
  boxes_exp[:, 2] = x_c + w_half
  boxes_exp[:, 1] = y_c - h_half
  boxes_exp[:, 3] = y_c + h_half

  return boxes_exp


def segm_results(masks, boxes, image_height, image_width):
  """Generates segmentation results."""

  # Reference: https://github.com/facebookresearch/Detectron/blob/master/detectron/core/test.py#L812  # pylint: disable=line-too-long
  # To work around an issue with cv2.resize (it seems to automatically pad
  # with repeated border values), we manually zero-pad the masks by 1 pixel
  # prior to resizing back to the original image resolution. This prevents
  # "top hat" artifacts. We therefore need to expand the reference boxes by an
  # appropriate factor.
  mask_size = masks.shape[2]
  scale = (mask_size + 2.0) / mask_size

  ref_boxes = expand_boxes(boxes, scale)
  ref_boxes = ref_boxes.astype(np.int32)

  padded_mask = np.zeros((mask_size + 2, mask_size + 2), dtype=np.float32)
  segms = []
  for mask_ind, mask in enumerate(masks):
    padded_mask[1:-1, 1:-1] = mask[:, :]

    ref_box = ref_boxes[mask_ind, :]
    w = ref_box[2] - ref_box[0] + 1
    h = ref_box[3] - ref_box[1] + 1
    w = np.maximum(w, 1)
    h = np.maximum(h, 1)

    mask = cv2.resize(padded_mask, (w, h))
    mask = np.array(mask > 0.5, dtype=np.uint8)
    im_mask = np.zeros((image_height, image_width), dtype=np.uint8)

    x_0 = max(ref_box[0], 0)
    x_1 = min(ref_box[2] + 1, image_width)
    y_0 = max(ref_box[1], 0)
    y_1 = min(ref_box[3] + 1, image_height)

    im_mask[y_0:y_1, x_0:x_1] = mask[
        (y_0 - ref_box[1]):(y_1 - ref_box[1]),
        (x_0 - ref_box[0]):(x_1 - ref_box[0])
    ]
    # Get RLE encoding used by the COCO evaluation API
    rle = maskUtils.encode(
        np.array(im_mask[:, :, np.newaxis], order='F')
    )[0]
    segms.append(rle)

  segms = np.array(segms)
  assert masks.shape[0] == segms.shape[0]
  return segms
