import numpy as np
import cv2 as cv
[docs]def get_centroid(bboxes):
    """
    Calculate centroids for multiple bounding boxes.
    Args:
        bboxes (numpy.ndarray): Array of shape `(n, 4)` or of shape `(4,)` where
            each row contains `(xmin, ymin, width, height)`.
    Returns:
        numpy.ndarray: Centroid (x, y) coordinates of shape `(n, 2)` or `(2,)`.
    """
    one_bbox = False
    if len(bboxes.shape) == 1:
        one_bbox = True
        bboxes = bboxes[None, :]
    xmin = bboxes[:, 0]
    ymin = bboxes[:, 1]
    w, h = bboxes[:, 2], bboxes[:, 3]
    xc = xmin + 0.5*w
    yc = ymin + 0.5*h
    x = np.hstack([xc[:, None], yc[:, None]])
    if one_bbox:
        x = x.flatten()
    return x 
[docs]def iou(bbox1, bbox2):
    """
    Calculates the intersection-over-union of two bounding boxes.
    Source: https://github.com/bochinski/iou-tracker/blob/master/util.py
    Args:
        bbox1 (numpy.array or list[floats]): Bounding box of length 4 containing
            ``(x-top-left, y-top-left, x-bottom-right, y-bottom-right)``.
        bbox2 (numpy.array or list[floats]): Bounding box of length 4 containing
            ``(x-top-left, y-top-left, x-bottom-right, y-bottom-right)``.
    Returns:
        float: intersection-over-onion of bbox1, bbox2.
    """
    bbox1 = [float(x) for x in bbox1]
    bbox2 = [float(x) for x in bbox2]
    (x0_1, y0_1, x1_1, y1_1), (x0_2, y0_2, x1_2, y1_2) = bbox1, bbox2
    # get the overlap rectangle
    overlap_x0 = max(x0_1, x0_2)
    overlap_y0 = max(y0_1, y0_2)
    overlap_x1 = min(x1_1, x1_2)
    overlap_y1 = min(y1_1, y1_2)
    # check if there is an overlap
    if overlap_x1 - overlap_x0 <= 0 or overlap_y1 - overlap_y0 <= 0:
        return 0.0
    # if yes, calculate the ratio of the overlap to each ROI size and the unified size
    size_1 = (x1_1 - x0_1) * (y1_1 - y0_1)
    size_2 = (x1_2 - x0_2) * (y1_2 - y0_2)
    size_intersection = (overlap_x1 - overlap_x0) * (overlap_y1 - overlap_y0)
    size_union = size_1 + size_2 - size_intersection
    iou_ = size_intersection / size_union
    return iou_ 
[docs]def iou_xywh(bbox1, bbox2):
    """
    Calculates the intersection-over-union of two bounding boxes.
    Source: https://github.com/bochinski/iou-tracker/blob/master/util.py
    Args:
        bbox1 (numpy.array or list[floats]): bounding box of length 4 containing ``(x-top-left, y-top-left, width, height)``.
        bbox2 (numpy.array or list[floats]): bounding box of length 4 containing ``(x-top-left, y-top-left, width, height)``.
    Returns:
        float: intersection-over-onion of bbox1, bbox2.
    """
    bbox1 = bbox1[0], bbox1[1], bbox1[0]+bbox1[2], bbox1[1]+bbox1[3]
    bbox2 = bbox2[0], bbox2[1], bbox2[0]+bbox2[2], bbox2[1]+bbox2[3]
    iou_ = iou(bbox1, bbox2)
    return iou_ 
[docs]def xyxy2xywh(xyxy):
    """
    Convert bounding box coordinates from (xmin, ymin, xmax, ymax) format to (xmin, ymin, width, height).
    Args:
        xyxy (numpy.ndarray):
    Returns:
        numpy.ndarray: Bounding box coordinates (xmin, ymin, width, height).
    """
    if len(xyxy.shape) == 2:
        w, h = xyxy[:, 2] - xyxy[:, 0] + 1, xyxy[:, 3] - xyxy[:, 1] + 1
        xywh = np.concatenate((xyxy[:, 0:2], w[:, None], h[:, None]), axis=1)
        return xywh.astype("int")
    elif len(xyxy.shape) == 1:
        (left, top, right, bottom) = xyxy
        width = right - left + 1
        height = bottom - top + 1
        return np.array([left, top, width, height]).astype('int')
    else:
        raise ValueError("Input shape not compatible.") 
[docs]def xywh2xyxy(xywh):
    """
    Convert bounding box coordinates from (xmin, ymin, width, height) to (xmin, ymin, xmax, ymax) format.
    Args:
        xywh (numpy.ndarray): Bounding box coordinates as `(xmin, ymin, width, height)`.
    Returns:
        numpy.ndarray : Bounding box coordinates as `(xmin, ymin, xmax, ymax)`.
    """
    if len(xywh.shape) == 2:
        x = xywh[:, 0] + xywh[:, 2]
        y = xywh[:, 1] + xywh[:, 3]
        xyxy = np.concatenate((xywh[:, 0:2], x[:, None], y[:, None]), axis=1).astype('int')
        return xyxy
    if len(xywh.shape) == 1:
        x, y, w, h = xywh
        xr = x + w
        yb = y + h
        return np.array([x, y, xr, yb]).astype('int') 
[docs]def midwh2xywh(midwh):
    """
    Convert bounding box coordinates from (xmid, ymid, width, height) to (xmin, ymin, width, height) format.
    Args:
        midwh (numpy.ndarray): Bounding box coordinates (xmid, ymid, width, height).
    Returns:
        numpy.ndarray: Bounding box coordinates (xmin, ymin, width, height).
    """
    if len(midwh.shape) == 2:
        xymin = midwh[:, 0:2] - midwh[:, 2:] * 0.5
        wh = midwh[:, 2:]
        xywh = np.concatenate([xymin, wh], axis=1).astype('int')
        return xywh
    if len(midwh.shape) == 1:
        xmid, ymid, w, h = midwh
        xywh = np.array([xmid-w*0.5, ymid-h*0.5, w, h]).astype('int')
        return xywh 
[docs]def intersection_complement_indices(big_set_indices, small_set_indices):
    """
    Get the complement of intersection of two sets of indices.
    Args:
        big_set_indices (numpy.ndarray): Indices of big set.
        small_set_indices (numpy.ndarray): Indices of small set.
    Returns:
        numpy.ndarray: Indices of set which is complementary to intersection of two input sets.
    """
    assert big_set_indices.shape[0] >= small_set_indices.shape[1]
    n = len(big_set_indices)
    mask = np.ones((n,), dtype=bool)
    mask[small_set_indices] = False
    intersection_complement = big_set_indices[mask]
    return intersection_complement 
[docs]def nms(boxes, scores, overlapThresh, classes=None):
    """
    Non-maximum suppression. based on Malisiewicz et al.
    Args:
        boxes (numpy.ndarray): Boxes to process (xmin, ymin, xmax, ymax)
        scores (numpy.ndarray): Corresponding scores for each box
        overlapThresh (float):  Overlap threshold for boxes to merge
        classes (numpy.ndarray, optional): Class ids for each box.
    Returns:
        tuple: a tuple containing:
            - boxes (list): nms boxes
            - scores (list): nms scores
            - classes (list, optional): nms classes if specified
    """
    if boxes.dtype.kind == "i":
        boxes = boxes.astype("float")
    if scores.dtype.kind == "i":
        scores = scores.astype("float")
    pick = []
    x1 = boxes[:, 0]
    y1 = boxes[:, 1]
    x2 = boxes[:, 2]
    y2 = boxes[:, 3]
    area = (x2 - x1 + 1) * (y2 - y1 + 1)
    idxs = np.argsort(scores)
    while len(idxs) > 0:
        last = len(idxs) - 1
        i = idxs[last]
        pick.append(i)
        xx1 = np.maximum(x1[i], x1[idxs[:last]])
        yy1 = np.maximum(y1[i], y1[idxs[:last]])
        xx2 = np.minimum(x2[i], x2[idxs[:last]])
        yy2 = np.minimum(y2[i], y2[idxs[:last]])
        w = np.maximum(0, xx2 - xx1 + 1)
        h = np.maximum(0, yy2 - yy1 + 1)
        overlap = (w * h) / area[idxs[:last]]
        # delete all indexes from the index list that have
        idxs = np.delete(idxs, np.concatenate(([last], np.where(overlap > overlapThresh)[0])))
    if classes is not None:
        return boxes[pick], scores[pick], classes[pick]
    else:
        return boxes[pick], scores[pick] 
[docs]def draw_tracks(image, tracks):
    """
    Draw on input image.
    Args:
        image (numpy.ndarray): image
        tracks (list): list of tracks to be drawn on the image.
    Returns:
        numpy.ndarray: image with the track-ids drawn on it.
    """
    for trk in tracks:
        trk_id = trk[1]
        xmin = trk[2]
        ymin = trk[3]
        width = trk[4]
        height = trk[5]
        xcentroid, ycentroid = int(xmin + 0.5*width), int(ymin + 0.5*height)
        text = "ID {}".format(trk_id)
        cv.putText(image, text, (xcentroid - 10, ycentroid - 10), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        cv.circle(image, (xcentroid, ycentroid), 4, (0, 255, 0), -1)
    return image 
[docs]def load_labelsjson(json_file):
    import json
    with open(json_file) as file:
        data = json.load(file)
    labels = {int(k): v for k, v in data.items()}
    return labels 
[docs]def dict2jsonfile(dict_data, json_file_path):
    import json
    with open(json_file_path, 'w') as outfile:
        json.dump(dict_data, outfile) 
if __name__ == '__main__':
    bb = np.random.random_integers(0, 100, size=(20,)).reshape((5, 4))
    c = get_centroid(bb)
    print(bb, c)
    
    bb2 = np.array([1, 2, 3, 4])
    c2 = get_centroid(bb2)
    print(bb2, c2)
    data = {
            0: 'background', 1: 'aeroplane', 2: 'bicycle', 3: 'bird', 4: 'boat', 5: 'bottle', 6: 'bus',
            7: 'car', 8: 'cat', 9: 'chair', 10: 'cow', 11: 'diningtable', 12: 'dog', 13: 'horse', 14: 'motorbike',
            15: 'person', 16: 'pottedplant', 17: 'sheep', 18: 'sofa', 19: 'train', 20: 'tvmonitor'
        }
    dict2jsonfile(data, '../../examples/pretrained_models/caffemodel_weights/ssd_mobilenet_caffe_names.json')