|
|
import math |
|
|
import numpy as np |
|
|
import torch |
|
|
import torch.nn.functional as F |
|
|
from torch import optim |
|
|
from torch.optim import Optimizer |
|
|
|
|
|
class AverageMeter(object): |
|
|
"""Computes and stores the average and current value""" |
|
|
def __init__(self): |
|
|
self.reset() |
|
|
|
|
|
def reset(self): |
|
|
self.val = 0 |
|
|
self.avg = 0 |
|
|
self.sum = 0 |
|
|
self.count = 0 |
|
|
|
|
|
def update(self, val, n=1): |
|
|
self.val = val |
|
|
self.sum += val * n |
|
|
self.count += n |
|
|
self.avg = self.sum / self.count |
|
|
|
|
|
def xyxy2xywh(x): |
|
|
y = torch.zeros(x.shape) if x.dtype is torch.float32 else np.zeros(x.shape) |
|
|
y[:, 0] = (x[:, 0] + x[:, 2]) / 2 |
|
|
y[:, 1] = (x[:, 1] + x[:, 3]) / 2 |
|
|
y[:, 2] = x[:, 2] - x[:, 0] |
|
|
y[:, 3] = x[:, 3] - x[:, 1] |
|
|
return y |
|
|
|
|
|
|
|
|
def xywh2xyxy(x): |
|
|
y = torch.zeros(x.shape) if x.dtype is torch.float32 else np.zeros(x.shape) |
|
|
y[:, 0] = (x[:, 0] - x[:, 2] / 2) |
|
|
y[:, 1] = (x[:, 1] - x[:, 3] / 2) |
|
|
y[:, 2] = (x[:, 0] + x[:, 2] / 2) |
|
|
y[:, 3] = (x[:, 1] + x[:, 3] / 2) |
|
|
return y |
|
|
|
|
|
def bbox_iou_numpy(box1, box2): |
|
|
"""Computes IoU between bounding boxes. |
|
|
Parameters |
|
|
---------- |
|
|
box1 : ndarray |
|
|
(N, 4) shaped array with bboxes |
|
|
box2 : ndarray |
|
|
(M, 4) shaped array with bboxes |
|
|
Returns |
|
|
------- |
|
|
: ndarray |
|
|
(N, M) shaped array with IoUs |
|
|
""" |
|
|
area = (box2[:, 2] - box2[:, 0]) * (box2[:, 3] - box2[:, 1]) |
|
|
|
|
|
iw = np.minimum(np.expand_dims(box1[:, 2], axis=1), box2[:, 2]) - np.maximum( |
|
|
np.expand_dims(box1[:, 0], 1), box2[:, 0] |
|
|
) |
|
|
ih = np.minimum(np.expand_dims(box1[:, 3], axis=1), box2[:, 3]) - np.maximum( |
|
|
np.expand_dims(box1[:, 1], 1), box2[:, 1] |
|
|
) |
|
|
|
|
|
iw = np.maximum(iw, 0) |
|
|
ih = np.maximum(ih, 0) |
|
|
|
|
|
ua = np.expand_dims((box1[:, 2] - box1[:, 0]) * (box1[:, 3] - box1[:, 1]), axis=1) + area - iw * ih |
|
|
|
|
|
ua = np.maximum(ua, np.finfo(float).eps) |
|
|
|
|
|
intersection = iw * ih |
|
|
|
|
|
return intersection / ua |
|
|
|
|
|
|
|
|
def bbox_iou(box1, box2, x1y1x2y2=True): |
|
|
""" |
|
|
Returns the IoU of two bounding boxes |
|
|
""" |
|
|
if x1y1x2y2: |
|
|
|
|
|
b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3] |
|
|
b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3] |
|
|
else: |
|
|
|
|
|
b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2 |
|
|
b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2 |
|
|
b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2 |
|
|
b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2 |
|
|
|
|
|
|
|
|
inter_rect_x1 = torch.max(b1_x1, b2_x1) |
|
|
inter_rect_y1 = torch.max(b1_y1, b2_y1) |
|
|
inter_rect_x2 = torch.min(b1_x2, b2_x2) |
|
|
inter_rect_y2 = torch.min(b1_y2, b2_y2) |
|
|
|
|
|
inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1, 0) * torch.clamp(inter_rect_y2 - inter_rect_y1, 0) |
|
|
|
|
|
b1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) |
|
|
b2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) |
|
|
|
|
|
|
|
|
|
|
|
return inter_area / (b1_area + b2_area - inter_area + 1e-16) |
|
|
|
|
|
def multiclass_metrics(pred, gt): |
|
|
""" |
|
|
check precision and recall for predictions. |
|
|
Output: overall = {precision, recall, f1} |
|
|
""" |
|
|
eps=1e-6 |
|
|
overall = {'precision': -1, 'recall': -1, 'f1': -1} |
|
|
NP, NR, NC = 0, 0, 0 |
|
|
for ii in range(pred.shape[0]): |
|
|
pred_ind = np.array(pred[ii]>0.5, dtype=int) |
|
|
gt_ind = np.array(gt[ii]>0.5, dtype=int) |
|
|
inter = pred_ind * gt_ind |
|
|
|
|
|
NC += np.sum(inter) |
|
|
NP += np.sum(pred_ind) |
|
|
NR += np.sum(gt_ind) |
|
|
if NP > 0: |
|
|
overall['precision'] = float(NC)/NP |
|
|
if NR > 0: |
|
|
overall['recall'] = float(NC)/NR |
|
|
if NP > 0 and NR > 0: |
|
|
overall['f1'] = 2*overall['precision']*overall['recall']/(overall['precision']+overall['recall']+eps) |
|
|
return overall |
|
|
|
|
|
def compute_ap(recall, precision): |
|
|
""" Compute the average precision, given the recall and precision curves. |
|
|
Code originally from https://github.com/rbgirshick/py-faster-rcnn. |
|
|
# Arguments |
|
|
recall: The recall curve (list). |
|
|
precision: The precision curve (list). |
|
|
# Returns |
|
|
The average precision as computed in py-faster-rcnn. |
|
|
""" |
|
|
|
|
|
|
|
|
mrec = np.concatenate(([0.0], recall, [1.0])) |
|
|
mpre = np.concatenate(([0.0], precision, [0.0])) |
|
|
|
|
|
|
|
|
for i in range(mpre.size - 1, 0, -1): |
|
|
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i]) |
|
|
|
|
|
|
|
|
|
|
|
i = np.where(mrec[1:] != mrec[:-1])[0] |
|
|
|
|
|
|
|
|
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) |
|
|
return ap |
|
|
|
|
|
def concat_coord(x): |
|
|
ins_feat = x |
|
|
batch_size, c, h, w = x.size() |
|
|
|
|
|
float_h = float(h) |
|
|
float_w = float(w) |
|
|
|
|
|
y_range = torch.arange(0., float_h, dtype=torch.float32) |
|
|
y_range = 2.0 * y_range / (float_h - 1.0) - 1.0 |
|
|
x_range = torch.arange(0., float_w, dtype=torch.float32) |
|
|
x_range = 2.0 * x_range / (float_w - 1.0) - 1.0 |
|
|
x_range = x_range[None, :] |
|
|
y_range = y_range[:, None] |
|
|
x = x_range.repeat(h, 1) |
|
|
y = y_range.repeat(1, w) |
|
|
|
|
|
x = x[None, None, :, :] |
|
|
y = y[None, None, :, :] |
|
|
x = x.repeat(batch_size, 1, 1, 1) |
|
|
y = y.repeat(batch_size, 1, 1, 1) |
|
|
x = x.cuda() |
|
|
y = y.cuda() |
|
|
|
|
|
ins_feat_out = torch.cat((ins_feat, x, x, x, y, y, y), 1) |
|
|
|
|
|
return ins_feat_out |
|
|
|
|
|
|
|
|
def get_cosine_schedule_with_warmup(optimizer: Optimizer, num_warmup_steps: int, num_training_steps: int, |
|
|
num_cycles: float = 0.5, last_epoch: int = -1): |
|
|
""" |
|
|
Implementation by Huggingface: |
|
|
https://github.com/huggingface/transformers/blob/v4.16.2/src/transformers/optimization.py |
|
|
|
|
|
Create a schedule with a learning rate that decreases following the values |
|
|
of the cosine function between the initial lr set in the optimizer to 0, |
|
|
after a warmup period during which it increases linearly between 0 and the |
|
|
initial lr set in the optimizer. |
|
|
Args: |
|
|
optimizer ([`~torch.optim.Optimizer`]): |
|
|
The optimizer for which to schedule the learning rate. |
|
|
num_warmup_steps (`int`): |
|
|
The number of steps for the warmup phase. |
|
|
num_training_steps (`int`): |
|
|
The total number of training steps. |
|
|
num_cycles (`float`, *optional*, defaults to 0.5): |
|
|
The number of waves in the cosine schedule (the defaults is to just |
|
|
decrease from the max value to 0 following a half-cosine). |
|
|
last_epoch (`int`, *optional*, defaults to -1): |
|
|
The index of the last epoch when resuming training. |
|
|
Return: |
|
|
`torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule. |
|
|
""" |
|
|
|
|
|
def lr_lambda(current_step): |
|
|
if current_step < num_warmup_steps: |
|
|
return max(1e-6, float(current_step) / float(max(1, num_warmup_steps))) |
|
|
progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps)) |
|
|
return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress))) |
|
|
|
|
|
return optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch) |
|
|
|
|
|
def dice_loss(inputs, targets): |
|
|
""" |
|
|
Compute the DICE loss, similar to generalized IOU for masks |
|
|
Args: |
|
|
inputs: A float tensor of arbitrary shape. |
|
|
The predictions for each example. |
|
|
targets: A float tensor with the same shape as inputs. Stores the binary |
|
|
classification label for each element in inputs |
|
|
(0 for the negative class and 1 for the positive class). |
|
|
""" |
|
|
|
|
|
inputs = inputs.sigmoid() |
|
|
inputs = inputs.flatten(1) |
|
|
targets = targets.flatten(1) |
|
|
numerator = 2 * (inputs * targets).sum(1) |
|
|
denominator = inputs.sum(-1) + targets.sum(-1) |
|
|
loss = 1 - (numerator + 1) / (denominator + 1) |
|
|
return loss.mean() |
|
|
|
|
|
def sigmoid_focal_loss(inputs, targets, alpha: float = -1, gamma: float = 0): |
|
|
""" |
|
|
Loss used in RetinaNet for dense detection: https://arxiv.org/abs/1708.02002. |
|
|
Args: |
|
|
inputs: A float tensor of arbitrary shape. |
|
|
The predictions for each example. |
|
|
targets: A float tensor with the same shape as inputs. Stores the binary |
|
|
classification label for each element in inputs |
|
|
(0 for the negative class and 1 for the positive class). |
|
|
alpha: (optional) Weighting factor in range (0,1) to balance |
|
|
positive vs negative examples. Default = -1 (no weighting). |
|
|
gamma: Exponent of the modulating factor (1 - p_t) to |
|
|
balance easy vs hard examples. |
|
|
Returns: |
|
|
Loss tensor |
|
|
""" |
|
|
|
|
|
prob = inputs.sigmoid() |
|
|
ce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction="none") |
|
|
p_t = prob * targets + (1 - prob) * (1 - targets) |
|
|
loss = ce_loss * ((1 - p_t) ** gamma) |
|
|
|
|
|
if alpha >= 0: |
|
|
alpha_t = alpha * targets + (1 - alpha) * (1 - targets) |
|
|
loss = alpha_t * loss |
|
|
return loss.mean() |