from imutils import contours, perspective

import cv2
import numpy as np
import matplotlib.pyplot as plt
import imutils

def midpoint(point1, point2):
    x1 = point1[0]
    x2 = point2[0]
    y1 = point1[1]
    y2 = point2[1]
    M = ((x1 + x2) / 2, (y1 + y2) / 2)
    return M

def object_size(image):
    # Initialize arguments for the filter
    top = int(0.25 * image.shape[0])  # shape[0] = rows
    bottom = top
    left = int(0.25 * image.shape[1])  # shape[1] = cols
    right = left

    # Form a border around the image
    image = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, None, [int(image[0,0,0]), int(image[0,0,1]), int(image[0,0,2])])

    # Convert the image to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Perform edge detection, then perform a dilation + erosion to close gaps in between object edges
    edged = cv2.Canny(gray, 100, 200)
    edged = cv2.dilate(edged, np.ones((5,5),np.uint8), iterations=1)
    edged = cv2.erode(edged, np.ones((5,5),np.uint8), iterations=1)

    # Find contours in the edge map
    cnts = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # Sort the contours from left-to-right and initialize the 'pixels per metric' calibration variable
    (cnts, _) = contours.sort_contours(cnts)
    pixels_per_metric = None

    # Loop over the contours individually
    for c in cnts:
        # If the contour is not sufficiently large, ignore it
        if cv2.contourArea(c) < 100:
            continue

        # Compute the rotated bounding box of the contour
        rect = cv2.minAreaRect(c)
        box = cv2.boxPoints(rect)
        box = np.array(box, dtype='int')

        # Order the points in the contour such that they appear in top-left, top-right, bottom-right, and bottom-left order, then draw the outline of the rotated bounding box in green
        box = perspective.order_points(box)
        cv2.drawContours(image, [box.astype('int')], -1, (0, 255, 0), 2)

        # Loop over the original points and draw the vertices of the bounding box rectangle in as small, red circles
        for (x, y) in box:
            cv2.circle(image, (int(x), int(y)), 5, (0, 0, 255), -1)

        # Unpack the ordered bounding box, then compute the midpoint between the top-left and top-right coordinates, followed by the midpoint between bottom-left and bottom-right coordinates
        (tl, tr, br, bl) = box
        (midTopX, midTopY) = midpoint(tl, tr)
        (midBottomX, midBottomY) = midpoint(bl, br)

        # Compute the midpoint between the top-left and bottom-left points, followed by the midpoint between the top-right and bottom-right
        (midLeftX, midLeftY) = midpoint(tl, bl)
        (midRightX, midRightY) = midpoint(tr, br)

        # Draw the midpoints on the image in blue
        cv2.circle(image, (int(midTopX), int(midTopY)), 5, (255, 0, 0), -1)
        cv2.circle(image, (int(midBottomX), int(midBottomY)), 5, (255, 0, 0), -1)
        cv2.circle(image, (int(midLeftX), int(midLeftY)), 5, (255, 0, 0), -1)
        cv2.circle(image, (int(midRightX), int(midRightY)), 5, (255, 0, 0), -1)

        # Draw lines between the midpoints in purple
        cv2.line(image, (int(midTopX), int(midTopY)), (int(midBottomX), int(midBottomY)), (255, 0, 255), 2)
        cv2.line(image, (int(midLeftX), int(midLeftY)), (int(midRightX), int(midRightY)), (255, 0, 255), 2)

        # Compute the Euclidean distance between the midpoints
        distTopBottom = np.linalg.norm(np.array((midTopX, midTopY)) - np.array((midBottomX, midBottomY)))
        distLeftRight = np.linalg.norm(np.array((midLeftX, midLeftY)) - np.array((midRightX, midRightY)))

        # If the pixels per metric has not been initialized, then compute it as the ratio of pixels to supplied metric (in this case, millimeters)
        if pixels_per_metric is None:
            object_diameter = max(distTopBottom, distLeftRight)
            real_diameter = 17.78  # Silver Denarius Average Diameter (mm)
            pixels_per_metric = object_diameter / real_diameter

        # Compute the size of the object
        dimTopBottom = distTopBottom / pixels_per_metric
        dimLeftRight = distLeftRight / pixels_per_metric

        # Draw the object sizes on the image
        cv2.putText(image, '{:.1f}mm'.format(dimTopBottom), (int(midTopX), int(midTopY)), cv2.FONT_HERSHEY_SIMPLEX, 0.50, (0, 0, 0), 2)
        cv2.putText(image, '{:.1f}mm'.format(dimLeftRight), (int(midRightX), int(midRightY)), cv2.FONT_HERSHEY_SIMPLEX, 0.50, (0, 0, 0), 2)

    return image
