first commit
This commit is contained in:
commit
54ccfb11fe
218 files changed
+19525
No files matched your search
Executable
+1
@@ -0,0 +1 @@
|
||||
from .misc import *
|
||||
Executable
+138
@@ -0,0 +1,138 @@
|
||||
import torch
|
||||
import math
|
||||
|
||||
|
||||
def convert_locations_to_boxes(locations, priors, center_variance,
|
||||
size_variance):
|
||||
"""Convert regressional location results of SSD into boxes in the form of (center_x, center_y, h, w).
|
||||
|
||||
The conversion:
|
||||
$$predicted\_center * center_variance = \frac {real\_center - prior\_center} {prior\_hw}$$
|
||||
$$exp(predicted\_hw * size_variance) = \frac {real\_hw} {prior\_hw}$$
|
||||
We do it in the inverse direction here.
|
||||
Args:
|
||||
locations (batch_size, num_priors, 4): the regression output of SSD. It will contain the outputs as well.
|
||||
priors (num_priors, 4) or (batch_size/1, num_priors, 4): prior boxes.
|
||||
center_variance: a float used to change the scale of center.
|
||||
size_variance: a float used to change of scale of size.
|
||||
Returns:
|
||||
boxes: priors: [[center_x, center_y, w, h]]. All the values
|
||||
are relative to the image size.
|
||||
"""
|
||||
# priors can have one dimension less.
|
||||
if priors.dim() + 1 == locations.dim():
|
||||
priors = priors.unsqueeze(0)
|
||||
return torch.cat([
|
||||
locations[..., :2] * center_variance * priors[..., 2:] + priors[..., :2],
|
||||
torch.exp(locations[..., 2:] * size_variance) * priors[..., 2:]
|
||||
], dim=locations.dim() - 1)
|
||||
|
||||
|
||||
def convert_boxes_to_locations(center_form_boxes, center_form_priors, center_variance, size_variance):
|
||||
# priors can have one dimension less
|
||||
if center_form_priors.dim() + 1 == center_form_boxes.dim():
|
||||
center_form_priors = center_form_priors.unsqueeze(0)
|
||||
return torch.cat([
|
||||
(center_form_boxes[..., :2] - center_form_priors[..., :2]) / center_form_priors[..., 2:] / center_variance,
|
||||
torch.log(center_form_boxes[..., 2:] / center_form_priors[..., 2:]) / size_variance
|
||||
], dim=center_form_boxes.dim() - 1)
|
||||
|
||||
|
||||
def area_of(left_top, right_bottom) -> torch.Tensor:
|
||||
"""Compute the areas of rectangles given two corners.
|
||||
|
||||
Args:
|
||||
left_top (N, 2): left top corner.
|
||||
right_bottom (N, 2): right bottom corner.
|
||||
|
||||
Returns:
|
||||
area (N): return the area.
|
||||
"""
|
||||
hw = torch.clamp(right_bottom - left_top, min=0.0)
|
||||
return hw[..., 0] * hw[..., 1]
|
||||
|
||||
|
||||
def iou_of(boxes0, boxes1, eps=1e-5):
|
||||
"""Return intersection-over-union (Jaccard index) of boxes.
|
||||
|
||||
Args:
|
||||
boxes0 (N, 4): ground truth boxes.
|
||||
boxes1 (N or 1, 4): predicted boxes.
|
||||
eps: a small number to avoid 0 as denominator.
|
||||
Returns:
|
||||
iou (N): IoU values.
|
||||
"""
|
||||
overlap_left_top = torch.max(boxes0[..., :2], boxes1[..., :2])
|
||||
overlap_right_bottom = torch.min(boxes0[..., 2:], boxes1[..., 2:])
|
||||
|
||||
overlap_area = area_of(overlap_left_top, overlap_right_bottom)
|
||||
area0 = area_of(boxes0[..., :2], boxes0[..., 2:])
|
||||
area1 = area_of(boxes1[..., :2], boxes1[..., 2:])
|
||||
return overlap_area / (area0 + area1 - overlap_area + eps)
|
||||
|
||||
|
||||
def assign_priors(gt_boxes, gt_labels, corner_form_priors,
|
||||
iou_threshold):
|
||||
"""Assign ground truth boxes and targets to priors.
|
||||
|
||||
Args:
|
||||
gt_boxes (num_targets, 4): ground truth boxes.
|
||||
gt_labels (num_targets): labels of targets.
|
||||
priors (num_priors, 4): corner form priors
|
||||
Returns:
|
||||
boxes (num_priors, 4): real values for priors.
|
||||
labels (num_priros): labels for priors.
|
||||
"""
|
||||
# size: num_priors x num_targets
|
||||
ious = iou_of(gt_boxes.unsqueeze(0), corner_form_priors.unsqueeze(1))
|
||||
# size: num_priors
|
||||
best_target_per_prior, best_target_per_prior_index = ious.max(1)
|
||||
# size: num_targets
|
||||
best_prior_per_target, best_prior_per_target_index = ious.max(0)
|
||||
|
||||
for target_index, prior_index in enumerate(best_prior_per_target_index):
|
||||
best_target_per_prior_index[prior_index] = target_index
|
||||
# 2.0 is used to make sure every target has a prior assigned
|
||||
best_target_per_prior.index_fill_(0, best_prior_per_target_index, 2)
|
||||
# size: num_priors
|
||||
labels = gt_labels[best_target_per_prior_index]
|
||||
labels[best_target_per_prior < iou_threshold] = 0 # the backgournd id
|
||||
boxes = gt_boxes[best_target_per_prior_index]
|
||||
return boxes, labels
|
||||
|
||||
|
||||
def hard_negative_mining(loss, labels, neg_pos_ratio):
|
||||
"""
|
||||
It used to suppress the presence of a large number of negative prediction.
|
||||
It works on image level not batch level.
|
||||
For any example/image, it keeps all the positive predictions and
|
||||
cut the number of negative predictions to make sure the ratio
|
||||
between the negative examples and positive examples is no more
|
||||
the given ratio for an image.
|
||||
|
||||
Args:
|
||||
loss (N, num_priors): the loss for each example.
|
||||
labels (N, num_priors): the labels.
|
||||
neg_pos_ratio: the ratio between the negative examples and positive examples.
|
||||
"""
|
||||
pos_mask = labels > 0
|
||||
num_pos = pos_mask.long().sum(dim=1, keepdim=True)
|
||||
num_neg = num_pos * neg_pos_ratio
|
||||
|
||||
loss[pos_mask] = -math.inf
|
||||
_, indexes = loss.sort(dim=1, descending=True)
|
||||
_, orders = indexes.sort(dim=1)
|
||||
neg_mask = orders < num_neg
|
||||
return pos_mask | neg_mask
|
||||
|
||||
|
||||
def center_form_to_corner_form(locations):
|
||||
return torch.cat([locations[..., :2] - locations[..., 2:] / 2,
|
||||
locations[..., :2] + locations[..., 2:] / 2], locations.dim() - 1)
|
||||
|
||||
|
||||
def corner_form_to_center_form(boxes):
|
||||
return torch.cat([
|
||||
(boxes[..., :2] + boxes[..., 2:]) / 2,
|
||||
boxes[..., 2:] - boxes[..., :2]
|
||||
], boxes.dim() - 1)
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import torch
|
||||
from torch.nn.parallel import DistributedDataParallel
|
||||
|
||||
from ssd.utils.model_zoo import cache_url
|
||||
|
||||
|
||||
class CheckPointer:
|
||||
_last_checkpoint_name = 'last_checkpoint.txt'
|
||||
|
||||
def __init__(self,
|
||||
model,
|
||||
optimizer=None,
|
||||
scheduler=None,
|
||||
save_dir="",
|
||||
save_to_disk=None,
|
||||
logger=None):
|
||||
self.model = model
|
||||
self.optimizer = optimizer
|
||||
self.scheduler = scheduler
|
||||
self.save_dir = save_dir
|
||||
self.save_to_disk = save_to_disk
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
self.logger = logger
|
||||
|
||||
def save(self, name, **kwargs):
|
||||
if not self.save_dir:
|
||||
return
|
||||
|
||||
if not self.save_to_disk:
|
||||
return
|
||||
|
||||
data = {}
|
||||
if isinstance(self.model, DistributedDataParallel):
|
||||
data['model'] = self.model.module.state_dict()
|
||||
else:
|
||||
data['model'] = self.model.state_dict()
|
||||
if self.optimizer is not None:
|
||||
data["optimizer"] = self.optimizer.state_dict()
|
||||
if self.scheduler is not None:
|
||||
data["scheduler"] = self.scheduler.state_dict()
|
||||
data.update(kwargs)
|
||||
|
||||
save_file = os.path.join(self.save_dir, "{}.pth".format(name))
|
||||
self.logger.info("Saving checkpoint to {}".format(save_file))
|
||||
torch.save(data, save_file)
|
||||
|
||||
self.tag_last_checkpoint(save_file)
|
||||
|
||||
def load(self, f=None, use_latest=True):
|
||||
if self.has_checkpoint() and use_latest:
|
||||
# override argument with existing checkpoint
|
||||
f = self.get_checkpoint_file()
|
||||
if not f:
|
||||
# no checkpoint could be found
|
||||
self.logger.info("No checkpoint found.")
|
||||
return {}
|
||||
|
||||
self.logger.info("Loading checkpoint from {}".format(f))
|
||||
checkpoint = self._load_file(f)
|
||||
model = self.model
|
||||
if isinstance(model, DistributedDataParallel):
|
||||
model = self.model.module
|
||||
|
||||
model.load_state_dict(checkpoint.pop("model"))
|
||||
if "optimizer" in checkpoint and self.optimizer:
|
||||
self.logger.info("Loading optimizer from {}".format(f))
|
||||
self.optimizer.load_state_dict(checkpoint.pop("optimizer"))
|
||||
if "scheduler" in checkpoint and self.scheduler:
|
||||
self.logger.info("Loading scheduler from {}".format(f))
|
||||
self.scheduler.load_state_dict(checkpoint.pop("scheduler"))
|
||||
|
||||
# return any further checkpoint data
|
||||
return checkpoint
|
||||
|
||||
def get_checkpoint_file(self):
|
||||
save_file = os.path.join(self.save_dir, self._last_checkpoint_name)
|
||||
try:
|
||||
with open(save_file, "r") as f:
|
||||
last_saved = f.read()
|
||||
last_saved = last_saved.strip()
|
||||
except IOError:
|
||||
# if file doesn't exist, maybe because it has just been
|
||||
# deleted by a separate process
|
||||
last_saved = ""
|
||||
return last_saved
|
||||
|
||||
def has_checkpoint(self):
|
||||
save_file = os.path.join(self.save_dir, self._last_checkpoint_name)
|
||||
return os.path.exists(save_file)
|
||||
|
||||
def tag_last_checkpoint(self, last_filename):
|
||||
save_file = os.path.join(self.save_dir, self._last_checkpoint_name)
|
||||
with open(save_file, "w") as f:
|
||||
f.write(last_filename)
|
||||
|
||||
def _load_file(self, f):
|
||||
# download url files
|
||||
if f.startswith("http"):
|
||||
# if the file is a url path, download it and cache it
|
||||
cached_f = cache_url(f)
|
||||
self.logger.info("url {} cached in {}".format(f, cached_f))
|
||||
f = cached_f
|
||||
return torch.load(f, map_location=torch.device("cpu"))
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
import pickle
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
|
||||
|
||||
def get_world_size():
|
||||
if not dist.is_available():
|
||||
return 1
|
||||
if not dist.is_initialized():
|
||||
return 1
|
||||
return dist.get_world_size()
|
||||
|
||||
|
||||
def get_rank():
|
||||
if not dist.is_available():
|
||||
return 0
|
||||
if not dist.is_initialized():
|
||||
return 0
|
||||
return dist.get_rank()
|
||||
|
||||
|
||||
def is_main_process():
|
||||
return get_rank() == 0
|
||||
|
||||
|
||||
def synchronize():
|
||||
"""
|
||||
Helper function to synchronize (barrier) among all processes when
|
||||
using distributed training
|
||||
"""
|
||||
if not dist.is_available():
|
||||
return
|
||||
if not dist.is_initialized():
|
||||
return
|
||||
world_size = dist.get_world_size()
|
||||
if world_size == 1:
|
||||
return
|
||||
dist.barrier()
|
||||
|
||||
|
||||
def _encode(encoded_data, data):
|
||||
# gets a byte representation for the data
|
||||
encoded_bytes = pickle.dumps(data)
|
||||
# convert this byte string into a byte tensor
|
||||
storage = torch.ByteStorage.from_buffer(encoded_bytes)
|
||||
tensor = torch.ByteTensor(storage).to("cuda")
|
||||
# encoding: first byte is the size and then rest is the data
|
||||
s = tensor.numel()
|
||||
assert s <= 255, "Can't encode data greater than 255 bytes"
|
||||
# put the encoded data in encoded_data
|
||||
encoded_data[0] = s
|
||||
encoded_data[1: (s + 1)] = tensor
|
||||
|
||||
|
||||
def all_gather(data):
|
||||
"""
|
||||
Run all_gather on arbitrary picklable data (not necessarily tensors)
|
||||
Args:
|
||||
data: any picklable object
|
||||
Returns:
|
||||
list[data]: list of data gathered from each rank
|
||||
"""
|
||||
world_size = get_world_size()
|
||||
if world_size == 1:
|
||||
return [data]
|
||||
|
||||
# serialized to a Tensor
|
||||
buffer = pickle.dumps(data)
|
||||
storage = torch.ByteStorage.from_buffer(buffer)
|
||||
tensor = torch.ByteTensor(storage).to("cuda")
|
||||
|
||||
# obtain Tensor size of each rank
|
||||
local_size = torch.LongTensor([tensor.numel()]).to("cuda")
|
||||
size_list = [torch.LongTensor([0]).to("cuda") for _ in range(world_size)]
|
||||
dist.all_gather(size_list, local_size)
|
||||
size_list = [int(size.item()) for size in size_list]
|
||||
max_size = max(size_list)
|
||||
|
||||
# receiving Tensor from all ranks
|
||||
# we pad the tensor because torch all_gather does not support
|
||||
# gathering tensors of different shapes
|
||||
tensor_list = []
|
||||
for _ in size_list:
|
||||
tensor_list.append(torch.ByteTensor(size=(max_size,)).to("cuda"))
|
||||
if local_size != max_size:
|
||||
padding = torch.ByteTensor(size=(max_size - local_size,)).to("cuda")
|
||||
tensor = torch.cat((tensor, padding), dim=0)
|
||||
dist.all_gather(tensor_list, tensor)
|
||||
|
||||
data_list = []
|
||||
for size, tensor in zip(size_list, tensor_list):
|
||||
buffer = tensor.cpu().numpy().tobytes()[:size]
|
||||
data_list.append(pickle.loads(buffer))
|
||||
|
||||
return data_list
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def setup_logger(name, distributed_rank, save_dir=None):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# don't log results for the non-master process
|
||||
if distributed_rank > 0:
|
||||
return logger
|
||||
stream_handler = logging.StreamHandler(stream=sys.stdout)
|
||||
stream_handler.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s")
|
||||
stream_handler.setFormatter(formatter)
|
||||
logger.addHandler(stream_handler)
|
||||
if save_dir:
|
||||
fh = logging.FileHandler(os.path.join(save_dir, 'log.txt'))
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
return logger
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
from collections import deque, defaultdict
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class SmoothedValue:
|
||||
"""Track a series of values and provide access to smoothed values over a
|
||||
window or the global series average.
|
||||
"""
|
||||
|
||||
def __init__(self, window_size=10):
|
||||
self.deque = deque(maxlen=window_size)
|
||||
self.value = np.nan
|
||||
self.series = []
|
||||
self.total = 0.0
|
||||
self.count = 0
|
||||
|
||||
def update(self, value):
|
||||
self.deque.append(value)
|
||||
self.series.append(value)
|
||||
self.count += 1
|
||||
self.total += value
|
||||
self.value = value
|
||||
|
||||
@property
|
||||
def median(self):
|
||||
values = np.array(self.deque)
|
||||
return np.median(values)
|
||||
|
||||
@property
|
||||
def avg(self):
|
||||
values = np.array(self.deque)
|
||||
return np.mean(values)
|
||||
|
||||
@property
|
||||
def global_avg(self):
|
||||
return self.total / self.count
|
||||
|
||||
|
||||
class MetricLogger:
|
||||
def __init__(self, delimiter=", "):
|
||||
self.meters = defaultdict(SmoothedValue)
|
||||
self.delimiter = delimiter
|
||||
|
||||
def update(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(v, torch.Tensor):
|
||||
v = v.item()
|
||||
assert isinstance(v, (float, int))
|
||||
self.meters[k].update(v)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.meters:
|
||||
return self.meters[attr]
|
||||
if attr in self.__dict__:
|
||||
return self.__dict__[attr]
|
||||
raise AttributeError("'{}' object has no attribute '{}'".format(
|
||||
type(self).__name__, attr))
|
||||
|
||||
def __str__(self):
|
||||
loss_str = []
|
||||
for name, meter in self.meters.items():
|
||||
loss_str.append(
|
||||
"{}: {:.3f} ({:.3f})".format(name, meter.avg, meter.global_avg)
|
||||
)
|
||||
return self.delimiter.join(loss_str)
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
import errno
|
||||
import os
|
||||
|
||||
|
||||
def str2bool(s):
|
||||
return s.lower() in ('true', '1')
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
import os
|
||||
import sys
|
||||
|
||||
import torch
|
||||
|
||||
from ssd.utils.dist_util import is_main_process, synchronize
|
||||
|
||||
try:
|
||||
from torch.hub import _download_url_to_file
|
||||
from torch.hub import urlparse
|
||||
from torch.hub import HASH_REGEX
|
||||
except ImportError:
|
||||
from torch.utils.model_zoo import _download_url_to_file
|
||||
from torch.utils.model_zoo import urlparse
|
||||
from torch.utils.model_zoo import HASH_REGEX
|
||||
|
||||
|
||||
# very similar to https://github.com/pytorch/pytorch/blob/master/torch/utils/model_zoo.py
|
||||
# but with a few improvements and modifications
|
||||
def cache_url(url, model_dir=None, progress=True):
|
||||
r"""Loads the Torch serialized object at the given URL.
|
||||
If the object is already present in `model_dir`, it's deserialized and
|
||||
returned. The filename part of the URL should follow the naming convention
|
||||
``filename-<sha256>.ext`` where ``<sha256>`` is the first eight or more
|
||||
digits of the SHA256 hash of the contents of the file. The hash is used to
|
||||
ensure unique names and to verify the contents of the file.
|
||||
The default value of `model_dir` is ``$TORCH_HOME/models`` where
|
||||
``$TORCH_HOME`` defaults to ``~/.torch``. The default directory can be
|
||||
overridden with the ``$TORCH_MODEL_ZOO`` environment variable.
|
||||
Args:
|
||||
url (string): URL of the object to download
|
||||
model_dir (string, optional): directory in which to save the object
|
||||
progress (bool, optional): whether or not to display a progress bar to stderr
|
||||
Example:
|
||||
>>> cached_file = maskrcnn_benchmark.utils.model_zoo.cache_url('https://s3.amazonaws.com/pytorch/models/resnet18-5c106cde.pth')
|
||||
"""
|
||||
if model_dir is None:
|
||||
torch_home = os.path.expanduser(os.getenv("TORCH_HOME", "~/.torch"))
|
||||
model_dir = os.getenv("TORCH_MODEL_ZOO", os.path.join(torch_home, "models"))
|
||||
if not os.path.exists(model_dir):
|
||||
os.makedirs(model_dir)
|
||||
parts = urlparse(url)
|
||||
filename = os.path.basename(parts.path)
|
||||
if filename == "model_final.pkl":
|
||||
# workaround as pre-trained Caffe2 models from Detectron have all the same filename
|
||||
# so make the full path the filename by replacing / with _
|
||||
filename = parts.path.replace("/", "_")
|
||||
cached_file = os.path.join(model_dir, filename)
|
||||
if not os.path.exists(cached_file) and is_main_process():
|
||||
sys.stderr.write('Downloading: "{}" to {}\n'.format(url, cached_file))
|
||||
hash_prefix = HASH_REGEX.search(filename)
|
||||
if hash_prefix is not None:
|
||||
hash_prefix = hash_prefix.group(1)
|
||||
# workaround: Caffe2 models don't have a hash, but follow the R-50 convention,
|
||||
# which matches the hash PyTorch uses. So we skip the hash matching
|
||||
# if the hash_prefix is less than 6 characters
|
||||
if len(hash_prefix) < 6:
|
||||
hash_prefix = None
|
||||
_download_url_to_file(url, cached_file, hash_prefix, progress=progress)
|
||||
synchronize()
|
||||
return cached_file
|
||||
|
||||
|
||||
def load_state_dict_from_url(url, map_location='cpu'):
|
||||
cached_file = cache_url(url)
|
||||
return torch.load(cached_file, map_location=map_location)
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
import warnings
|
||||
import torchvision
|
||||
|
||||
try:
|
||||
import torch_extension
|
||||
|
||||
_nms = torch_extension.nms
|
||||
except ImportError:
|
||||
if torchvision.__version__ >= '0.3.0':
|
||||
_nms = torchvision.ops.nms
|
||||
else:
|
||||
from .python_nms import python_nms
|
||||
|
||||
_nms = python_nms
|
||||
warnings.warn('You are using python version NMS, which is very very slow. Try compile c++ NMS '
|
||||
'using `cd ext & python build.py build_ext develop`')
|
||||
|
||||
|
||||
def boxes_nms(boxes, scores, nms_thresh, max_count=-1):
|
||||
""" Performs non-maximum suppression, run on GPU or CPU according to
|
||||
boxes's device.
|
||||
Args:
|
||||
boxes(Tensor): `xyxy` mode boxes, use absolute coordinates(or relative coordinates), shape is (n, 4)
|
||||
scores(Tensor): scores, shape is (n, )
|
||||
nms_thresh(float): thresh
|
||||
max_count (int): if > 0, then only the top max_proposals are kept after non-maximum suppression
|
||||
Returns:
|
||||
indices kept.
|
||||
"""
|
||||
keep = _nms(boxes, scores, nms_thresh)
|
||||
if max_count > 0:
|
||||
keep = keep[:max_count]
|
||||
return keep
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
def python_nms(boxes, scores, nms_thresh):
|
||||
""" Performs non-maximum suppression using numpy
|
||||
Args:
|
||||
boxes(Tensor): `xyxy` mode boxes, use absolute coordinates(not support relative coordinates),
|
||||
shape is (n, 4)
|
||||
scores(Tensor): scores, shape is (n, )
|
||||
nms_thresh(float): thresh
|
||||
Returns:
|
||||
indices kept.
|
||||
"""
|
||||
if boxes.numel() == 0:
|
||||
return torch.empty((0,), dtype=torch.long)
|
||||
# Use numpy to run nms. Running nms in PyTorch code on CPU is really slow.
|
||||
origin_device = boxes.device
|
||||
cpu_device = torch.device('cpu')
|
||||
boxes = boxes.to(cpu_device).numpy()
|
||||
scores = scores.to(cpu_device).numpy()
|
||||
|
||||
x1 = boxes[:, 0]
|
||||
y1 = boxes[:, 1]
|
||||
x2 = boxes[:, 2]
|
||||
y2 = boxes[:, 3]
|
||||
areas = (x2 - x1) * (y2 - y1)
|
||||
order = np.argsort(scores)[::-1]
|
||||
num_detections = boxes.shape[0]
|
||||
suppressed = np.zeros((num_detections,), dtype=np.bool)
|
||||
for _i in range(num_detections):
|
||||
i = order[_i]
|
||||
if suppressed[i]:
|
||||
continue
|
||||
ix1 = x1[i]
|
||||
iy1 = y1[i]
|
||||
ix2 = x2[i]
|
||||
iy2 = y2[i]
|
||||
iarea = areas[i]
|
||||
|
||||
for _j in range(_i + 1, num_detections):
|
||||
j = order[_j]
|
||||
if suppressed[j]:
|
||||
continue
|
||||
|
||||
xx1 = max(ix1, x1[j])
|
||||
yy1 = max(iy1, y1[j])
|
||||
xx2 = min(ix2, x2[j])
|
||||
yy2 = min(iy2, y2[j])
|
||||
w = max(0, xx2 - xx1)
|
||||
h = max(0, yy2 - yy1)
|
||||
|
||||
inter = w * h
|
||||
ovr = inter / (iarea + areas[j] - inter)
|
||||
if ovr >= nms_thresh:
|
||||
suppressed[j] = True
|
||||
keep = np.nonzero(suppressed == 0)[0]
|
||||
keep = torch.from_numpy(keep).to(origin_device)
|
||||
return keep
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
def _register_generic(module_dict, module_name, module):
|
||||
assert module_name not in module_dict
|
||||
module_dict[module_name] = module
|
||||
|
||||
|
||||
class Registry(dict):
|
||||
"""
|
||||
A helper class for managing registering modules, it extends a dictionary
|
||||
and provides a register functions.
|
||||
Eg. creating a registry:
|
||||
some_registry = Registry({"default": default_module})
|
||||
There're two ways of registering new modules:
|
||||
1): normal way is just calling register function:
|
||||
def foo():
|
||||
...
|
||||
some_registry.register("foo_module", foo)
|
||||
2): used as decorator when declaring the module:
|
||||
@some_registry.register("foo_module")
|
||||
@some_registry.register("foo_module_nickname")
|
||||
def foo():
|
||||
...
|
||||
Access of module is just like using a dictionary, eg:
|
||||
f = some_registry["foo_module"]
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Registry, self).__init__(*args, **kwargs)
|
||||
|
||||
def register(self, module_name, module=None):
|
||||
# used as function call
|
||||
if module is not None:
|
||||
_register_generic(self, module_name, module)
|
||||
return
|
||||
|
||||
# used as decorator
|
||||
def register_fn(fn):
|
||||
_register_generic(self, module_name, fn)
|
||||
return fn
|
||||
|
||||
return register_fn
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
|
||||
import time
|
||||
import datetime
|
||||
|
||||
|
||||
class Timer(object):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
@property
|
||||
def average_time(self):
|
||||
return self.total_time / self.calls if self.calls > 0 else 0.0
|
||||
|
||||
def tic(self):
|
||||
# using time.time instead of time.clock because time time.clock
|
||||
# does not normalize for multithreading
|
||||
self.start_time = time.time()
|
||||
|
||||
def toc(self, average=True):
|
||||
self.add(time.time() - self.start_time)
|
||||
if average:
|
||||
return self.average_time
|
||||
else:
|
||||
return self.diff
|
||||
|
||||
def add(self, time_diff):
|
||||
self.diff = time_diff
|
||||
self.total_time += self.diff
|
||||
self.calls += 1
|
||||
|
||||
def reset(self):
|
||||
self.total_time = 0.0
|
||||
self.calls = 0
|
||||
self.start_time = 0.0
|
||||
self.diff = 0.0
|
||||
|
||||
def avg_time_str(self):
|
||||
time_str = str(datetime.timedelta(seconds=self.average_time))
|
||||
return time_str
|
||||
|
||||
|
||||
def get_time_str(time_diff):
|
||||
time_str = str(datetime.timedelta(seconds=time_diff))
|
||||
return time_str
|
||||
Reference in new issue
Block a user