




import numpy as np
import torch
import sys
import unittest
from scipy import interpolate

import caffe2.python.hypothesis_test_util as hu
from caffe2.python import core, utils
from caffe2.proto import caffe2_pb2

import caffe2.python.operator_test.detectron_keypoints as keypoint_utils

NUM_TEST_ROI = 14
NUM_KEYPOINTS = 19
HEATMAP_SIZE = 56


def heatmap_FAIR_keypoint_ref(maps, rois):
    return [keypoint_utils.heatmaps_to_keypoints(maps, rois)]


def heatmap_approx_keypoint_ref(maps, rois):
    return [keypoint_utils.approx_heatmap_keypoint(maps, rois)]


def c10_op_ref(maps, rois):
    keypoints = torch.ops._caffe2.HeatmapMaxKeypoint(
        torch.tensor(maps),
        torch.tensor(rois),
        should_output_softmax=True,
    )
    return [keypoints.numpy()]


class TestHeatmapMaxKeypointOp(hu.HypothesisTestCase):
    def setUp(self):
        super().setUp()
        np.random.seed(0)

        # initial coordinates and interpolate HEATMAP_SIZE from it
        HEATMAP_SMALL_SIZE = 4
        bboxes_in = 500 * np.random.rand(NUM_TEST_ROI, 4).astype(np.float32)
        # only bbox with smaller first coordinates
        for i in range(NUM_TEST_ROI):
            if bboxes_in[i][0] > bboxes_in[i][2]:
                tmp = bboxes_in[i][2]
                bboxes_in[i][2] = bboxes_in[i][0]
                bboxes_in[i][0] = tmp
            if bboxes_in[i][1] > bboxes_in[i][3]:
                tmp = bboxes_in[i][3]
                bboxes_in[i][3] = bboxes_in[i][1]
                bboxes_in[i][1] = tmp

        # initial randomized coordinates for heatmaps and expand it with interpolation
        init = np.random.rand(
            NUM_TEST_ROI,
            NUM_KEYPOINTS,
            HEATMAP_SMALL_SIZE,
            HEATMAP_SMALL_SIZE).astype(np.float32)
        heatmaps_in = np.zeros(
            (NUM_TEST_ROI, NUM_KEYPOINTS, HEATMAP_SIZE, HEATMAP_SIZE)
        ).astype(np.float32)
        for roi in range(NUM_TEST_ROI):
            for keyp in range(NUM_KEYPOINTS):
                f = interpolate.interp2d(
                    np.arange(0, 1, 1.0 / HEATMAP_SMALL_SIZE),
                    np.arange(0, 1, 1.0 / HEATMAP_SMALL_SIZE),
                    init[roi][keyp],
                    kind='cubic')
                heatmaps_in[roi][keyp] = f(
                    np.arange(0, 1, 1.0 / HEATMAP_SIZE),
                    np.arange(0, 1, 1.0 / HEATMAP_SIZE))

        self.heatmaps_in = heatmaps_in
        self.bboxes_in = bboxes_in

        self.op = core.CreateOperator(
            'HeatmapMaxKeypoint',
            ['heatmaps_in', 'bboxes_in'],
            ['keypoints_out'],
            arg=[
                utils.MakeArgument("should_output_softmax", True),
            ],
            device_option=caffe2_pb2.DeviceOption())

    @unittest.skipIf('cv2' not in sys.modules, 'python-opencv is not installed')
    def test_close_to_FAIR(self):
        # 10 pixel error in scale of 500px bbox
        self.assertReferenceChecks(
            device_option=caffe2_pb2.DeviceOption(),
            op=self.op,
            inputs=[self.heatmaps_in, self.bboxes_in],
            reference=heatmap_FAIR_keypoint_ref,
            threshold=10,
        )

    def test_approx_heatmap_keypoint(self):
        # C++/Python implementation should be bit-wise equal
        self.assertReferenceChecks(
            device_option=caffe2_pb2.DeviceOption(),
            op=self.op,
            inputs=[self.heatmaps_in, self.bboxes_in],
            reference=heatmap_approx_keypoint_ref,
        )

    def test_special_cases(self):
        example_bboxes = np.array([[0, 0, 100, 100]]).astype(np.float32)
        heatmap_tests = []
        # special case #1
        heatmap_tests.append(np.array([
            [0.14722, 0.807823, 0.447052],
            [0.652919, 0.850923, -0.225462],
            [0.805912, 0.75778, -0.563371],
        ]).astype(np.float32).reshape((1, 1, 3, 3)))
        # special case #2
        heatmap_tests.append(np.array([
            [3.19541, 3.69551, 3.87579],
            [3.63094, 3.89978, 3.67606],
            [3.78555, 3.87291, 3.28083],
        ]).astype(np.float32).reshape((1, 1, 3, 3)))

        for heatmap_test in heatmap_tests:
            self.assertReferenceChecks(
                device_option=caffe2_pb2.DeviceOption(),
                op=self.op,
                inputs=[heatmap_test, example_bboxes],
                reference=heatmap_approx_keypoint_ref,
            )

    def test_caffe2_pytorch_eq(self):
        self.assertReferenceChecks(
            device_option=caffe2_pb2.DeviceOption(),
            op=self.op,
            inputs=[self.heatmaps_in, self.bboxes_in],
            reference=c10_op_ref,
        )


if __name__ == "__main__":
    unittest.main()
