birth
This commit is contained in:
9
segmentation/mmseg_custom/core/__init__.py
Normal file
9
segmentation/mmseg_custom/core/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from mmseg.core.evaluation import * # noqa: F401, F403
|
||||
from mmseg.core.seg import * # noqa: F401, F403
|
||||
|
||||
from .anchor import * # noqa: F401,F403
|
||||
from .box import * # noqa: F401,F403
|
||||
from .evaluation import * # noqa: F401,F403
|
||||
from .mask import * # noqa: F401,F403
|
||||
from .utils import * # noqa: F401, F403
|
||||
2
segmentation/mmseg_custom/core/anchor/__init__.py
Normal file
2
segmentation/mmseg_custom/core/anchor/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .point_generator import MlvlPointGenerator # noqa: F401,F403
|
||||
19
segmentation/mmseg_custom/core/anchor/builder.py
Normal file
19
segmentation/mmseg_custom/core/anchor/builder.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv.utils import Registry, build_from_cfg
|
||||
|
||||
PRIOR_GENERATORS = Registry('Generator for anchors and points')
|
||||
|
||||
ANCHOR_GENERATORS = PRIOR_GENERATORS
|
||||
|
||||
|
||||
def build_prior_generator(cfg, default_args=None):
|
||||
return build_from_cfg(cfg, PRIOR_GENERATORS, default_args)
|
||||
|
||||
|
||||
def build_anchor_generator(cfg, default_args=None):
|
||||
warnings.warn(
|
||||
'``build_anchor_generator`` would be deprecated soon, please use '
|
||||
'``build_prior_generator`` ')
|
||||
return build_prior_generator(cfg, default_args=default_args)
|
||||
260
segmentation/mmseg_custom/core/anchor/point_generator.py
Normal file
260
segmentation/mmseg_custom/core/anchor/point_generator.py
Normal file
@@ -0,0 +1,260 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch.nn.modules.utils import _pair
|
||||
|
||||
from .builder import PRIOR_GENERATORS
|
||||
|
||||
|
||||
@PRIOR_GENERATORS.register_module()
|
||||
class PointGenerator:
|
||||
def _meshgrid(self, x, y, row_major=True):
|
||||
xx = x.repeat(len(y))
|
||||
yy = y.view(-1, 1).repeat(1, len(x)).view(-1)
|
||||
if row_major:
|
||||
return xx, yy
|
||||
else:
|
||||
return yy, xx
|
||||
|
||||
def grid_points(self, featmap_size, stride=16, device='cuda'):
|
||||
feat_h, feat_w = featmap_size
|
||||
shift_x = torch.arange(0., feat_w, device=device) * stride
|
||||
shift_y = torch.arange(0., feat_h, device=device) * stride
|
||||
shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)
|
||||
stride = shift_x.new_full((shift_xx.shape[0], ), stride)
|
||||
shifts = torch.stack([shift_xx, shift_yy, stride], dim=-1)
|
||||
all_points = shifts.to(device)
|
||||
return all_points
|
||||
|
||||
def valid_flags(self, featmap_size, valid_size, device='cuda'):
|
||||
feat_h, feat_w = featmap_size
|
||||
valid_h, valid_w = valid_size
|
||||
assert valid_h <= feat_h and valid_w <= feat_w
|
||||
valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)
|
||||
valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)
|
||||
valid_x[:valid_w] = 1
|
||||
valid_y[:valid_h] = 1
|
||||
valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)
|
||||
valid = valid_xx & valid_yy
|
||||
return valid
|
||||
|
||||
|
||||
@PRIOR_GENERATORS.register_module()
|
||||
class MlvlPointGenerator:
|
||||
"""Standard points generator for multi-level (Mlvl) feature maps in 2D
|
||||
points-based detectors.
|
||||
|
||||
Args:
|
||||
strides (list[int] | list[tuple[int, int]]): Strides of anchors
|
||||
in multiple feature levels in order (w, h).
|
||||
offset (float): The offset of points, the value is normalized with
|
||||
corresponding stride. Defaults to 0.5.
|
||||
"""
|
||||
def __init__(self, strides, offset=0.5):
|
||||
self.strides = [_pair(stride) for stride in strides]
|
||||
self.offset = offset
|
||||
|
||||
@property
|
||||
def num_levels(self):
|
||||
"""int: number of feature levels that the generator will be applied"""
|
||||
return len(self.strides)
|
||||
|
||||
@property
|
||||
def num_base_priors(self):
|
||||
"""list[int]: The number of priors (points) at a point
|
||||
on the feature grid"""
|
||||
return [1 for _ in range(len(self.strides))]
|
||||
|
||||
def _meshgrid(self, x, y, row_major=True):
|
||||
yy, xx = torch.meshgrid(y, x)
|
||||
if row_major:
|
||||
# warning .flatten() would cause error in ONNX exporting
|
||||
# have to use reshape here
|
||||
return xx.reshape(-1), yy.reshape(-1)
|
||||
|
||||
else:
|
||||
return yy.reshape(-1), xx.reshape(-1)
|
||||
|
||||
def grid_priors(self,
|
||||
featmap_sizes,
|
||||
dtype=torch.float32,
|
||||
device='cuda',
|
||||
with_stride=False):
|
||||
"""Generate grid points of multiple feature levels.
|
||||
|
||||
Args:
|
||||
featmap_sizes (list[tuple]): List of feature map sizes in
|
||||
multiple feature levels, each size arrange as
|
||||
as (h, w).
|
||||
dtype (:obj:`dtype`): Dtype of priors. Default: torch.float32.
|
||||
device (str): The device where the anchors will be put on.
|
||||
with_stride (bool): Whether to concatenate the stride to
|
||||
the last dimension of points.
|
||||
|
||||
Return:
|
||||
list[torch.Tensor]: Points of multiple feature levels.
|
||||
The sizes of each tensor should be (N, 2) when with stride is
|
||||
``False``, where N = width * height, width and height
|
||||
are the sizes of the corresponding feature level,
|
||||
and the last dimension 2 represent (coord_x, coord_y),
|
||||
otherwise the shape should be (N, 4),
|
||||
and the last dimension 4 represent
|
||||
(coord_x, coord_y, stride_w, stride_h).
|
||||
"""
|
||||
|
||||
assert self.num_levels == len(featmap_sizes)
|
||||
multi_level_priors = []
|
||||
for i in range(self.num_levels):
|
||||
priors = self.single_level_grid_priors(featmap_sizes[i],
|
||||
level_idx=i,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
with_stride=with_stride)
|
||||
multi_level_priors.append(priors)
|
||||
return multi_level_priors
|
||||
|
||||
def single_level_grid_priors(self,
|
||||
featmap_size,
|
||||
level_idx,
|
||||
dtype=torch.float32,
|
||||
device='cuda',
|
||||
with_stride=False):
|
||||
"""Generate grid Points of a single level.
|
||||
|
||||
Note:
|
||||
This function is usually called by method ``self.grid_priors``.
|
||||
|
||||
Args:
|
||||
featmap_size (tuple[int]): Size of the feature maps, arrange as
|
||||
(h, w).
|
||||
level_idx (int): The index of corresponding feature map level.
|
||||
dtype (:obj:`dtype`): Dtype of priors. Default: torch.float32.
|
||||
device (str, optional): The device the tensor will be put on.
|
||||
Defaults to 'cuda'.
|
||||
with_stride (bool): Concatenate the stride to the last dimension
|
||||
of points.
|
||||
|
||||
Return:
|
||||
Tensor: Points of single feature levels.
|
||||
The shape of tensor should be (N, 2) when with stride is
|
||||
``False``, where N = width * height, width and height
|
||||
are the sizes of the corresponding feature level,
|
||||
and the last dimension 2 represent (coord_x, coord_y),
|
||||
otherwise the shape should be (N, 4),
|
||||
and the last dimension 4 represent
|
||||
(coord_x, coord_y, stride_w, stride_h).
|
||||
"""
|
||||
feat_h, feat_w = featmap_size
|
||||
stride_w, stride_h = self.strides[level_idx]
|
||||
shift_x = (torch.arange(0, feat_w, device=device) +
|
||||
self.offset) * stride_w
|
||||
# keep featmap_size as Tensor instead of int, so that we
|
||||
# can convert to ONNX correctly
|
||||
shift_x = shift_x.to(dtype)
|
||||
|
||||
shift_y = (torch.arange(0, feat_h, device=device) +
|
||||
self.offset) * stride_h
|
||||
# keep featmap_size as Tensor instead of int, so that we
|
||||
# can convert to ONNX correctly
|
||||
shift_y = shift_y.to(dtype)
|
||||
shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)
|
||||
if not with_stride:
|
||||
shifts = torch.stack([shift_xx, shift_yy], dim=-1)
|
||||
else:
|
||||
# use `shape[0]` instead of `len(shift_xx)` for ONNX export
|
||||
stride_w = shift_xx.new_full((shift_xx.shape[0], ),
|
||||
stride_w).to(dtype)
|
||||
stride_h = shift_xx.new_full((shift_yy.shape[0], ),
|
||||
stride_h).to(dtype)
|
||||
shifts = torch.stack([shift_xx, shift_yy, stride_w, stride_h],
|
||||
dim=-1)
|
||||
all_points = shifts.to(device)
|
||||
return all_points
|
||||
|
||||
def valid_flags(self, featmap_sizes, pad_shape, device='cuda'):
|
||||
"""Generate valid flags of points of multiple feature levels.
|
||||
|
||||
Args:
|
||||
featmap_sizes (list(tuple)): List of feature map sizes in
|
||||
multiple feature levels, each size arrange as
|
||||
as (h, w).
|
||||
pad_shape (tuple(int)): The padded shape of the image,
|
||||
arrange as (h, w).
|
||||
device (str): The device where the anchors will be put on.
|
||||
|
||||
Return:
|
||||
list(torch.Tensor): Valid flags of points of multiple levels.
|
||||
"""
|
||||
assert self.num_levels == len(featmap_sizes)
|
||||
multi_level_flags = []
|
||||
for i in range(self.num_levels):
|
||||
point_stride = self.strides[i]
|
||||
feat_h, feat_w = featmap_sizes[i]
|
||||
h, w = pad_shape[:2]
|
||||
valid_feat_h = min(int(np.ceil(h / point_stride[1])), feat_h)
|
||||
valid_feat_w = min(int(np.ceil(w / point_stride[0])), feat_w)
|
||||
flags = self.single_level_valid_flags((feat_h, feat_w),
|
||||
(valid_feat_h, valid_feat_w),
|
||||
device=device)
|
||||
multi_level_flags.append(flags)
|
||||
return multi_level_flags
|
||||
|
||||
def single_level_valid_flags(self,
|
||||
featmap_size,
|
||||
valid_size,
|
||||
device='cuda'):
|
||||
"""Generate the valid flags of points of a single feature map.
|
||||
|
||||
Args:
|
||||
featmap_size (tuple[int]): The size of feature maps, arrange as
|
||||
as (h, w).
|
||||
valid_size (tuple[int]): The valid size of the feature maps.
|
||||
The size arrange as as (h, w).
|
||||
device (str, optional): The device where the flags will be put on.
|
||||
Defaults to 'cuda'.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The valid flags of each points in a single level \
|
||||
feature map.
|
||||
"""
|
||||
feat_h, feat_w = featmap_size
|
||||
valid_h, valid_w = valid_size
|
||||
assert valid_h <= feat_h and valid_w <= feat_w
|
||||
valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device)
|
||||
valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device)
|
||||
valid_x[:valid_w] = 1
|
||||
valid_y[:valid_h] = 1
|
||||
valid_xx, valid_yy = self._meshgrid(valid_x, valid_y)
|
||||
valid = valid_xx & valid_yy
|
||||
return valid
|
||||
|
||||
def sparse_priors(self,
|
||||
prior_idxs,
|
||||
featmap_size,
|
||||
level_idx,
|
||||
dtype=torch.float32,
|
||||
device='cuda'):
|
||||
"""Generate sparse points according to the ``prior_idxs``.
|
||||
|
||||
Args:
|
||||
prior_idxs (Tensor): The index of corresponding anchors
|
||||
in the feature map.
|
||||
featmap_size (tuple[int]): feature map size arrange as (w, h).
|
||||
level_idx (int): The level index of corresponding feature
|
||||
map.
|
||||
dtype (obj:`torch.dtype`): Date type of points. Defaults to
|
||||
``torch.float32``.
|
||||
device (obj:`torch.device`): The device where the points is
|
||||
located.
|
||||
Returns:
|
||||
Tensor: Anchor with shape (N, 2), N should be equal to
|
||||
the length of ``prior_idxs``. And last dimension
|
||||
2 represent (coord_x, coord_y).
|
||||
"""
|
||||
height, width = featmap_size
|
||||
x = (prior_idxs % width + self.offset) * self.strides[level_idx][0]
|
||||
y = ((prior_idxs // width) % height +
|
||||
self.offset) * self.strides[level_idx][1]
|
||||
prioris = torch.stack([x, y], 1).to(dtype)
|
||||
prioris = prioris.to(device)
|
||||
return prioris
|
||||
3
segmentation/mmseg_custom/core/box/__init__.py
Normal file
3
segmentation/mmseg_custom/core/box/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .builder import * # noqa: F401,F403
|
||||
from .samplers import MaskPseudoSampler # noqa: F401,F403
|
||||
15
segmentation/mmseg_custom/core/box/builder.py
Normal file
15
segmentation/mmseg_custom/core/box/builder.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmcv.utils import Registry, build_from_cfg
|
||||
|
||||
BBOX_SAMPLERS = Registry('bbox_sampler')
|
||||
BBOX_CODERS = Registry('bbox_coder')
|
||||
|
||||
|
||||
def build_sampler(cfg, **default_args):
|
||||
"""Builder of box sampler."""
|
||||
return build_from_cfg(cfg, BBOX_SAMPLERS, default_args)
|
||||
|
||||
|
||||
def build_bbox_coder(cfg, **default_args):
|
||||
"""Builder of box coder."""
|
||||
return build_from_cfg(cfg, BBOX_CODERS, default_args)
|
||||
2
segmentation/mmseg_custom/core/box/samplers/__init__.py
Normal file
2
segmentation/mmseg_custom/core/box/samplers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .mask_pseudo_sampler import MaskPseudoSampler # noqa: F401,F403
|
||||
105
segmentation/mmseg_custom/core/box/samplers/base_sampler.py
Normal file
105
segmentation/mmseg_custom/core/box/samplers/base_sampler.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import torch
|
||||
|
||||
from .sampling_result import SamplingResult
|
||||
|
||||
|
||||
class BaseSampler(metaclass=ABCMeta):
|
||||
"""Base class of samplers."""
|
||||
def __init__(self,
|
||||
num,
|
||||
pos_fraction,
|
||||
neg_pos_ub=-1,
|
||||
add_gt_as_proposals=True,
|
||||
**kwargs):
|
||||
self.num = num
|
||||
self.pos_fraction = pos_fraction
|
||||
self.neg_pos_ub = neg_pos_ub
|
||||
self.add_gt_as_proposals = add_gt_as_proposals
|
||||
self.pos_sampler = self
|
||||
self.neg_sampler = self
|
||||
|
||||
@abstractmethod
|
||||
def _sample_pos(self, assign_result, num_expected, **kwargs):
|
||||
"""Sample positive samples."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _sample_neg(self, assign_result, num_expected, **kwargs):
|
||||
"""Sample negative samples."""
|
||||
pass
|
||||
|
||||
def sample(self,
|
||||
assign_result,
|
||||
bboxes,
|
||||
gt_bboxes,
|
||||
gt_labels=None,
|
||||
**kwargs):
|
||||
"""Sample positive and negative bboxes.
|
||||
|
||||
This is a simple implementation of bbox sampling given candidates,
|
||||
assigning results and ground truth bboxes.
|
||||
|
||||
Args:
|
||||
assign_result (:obj:`AssignResult`): Bbox assigning results.
|
||||
bboxes (Tensor): Boxes to be sampled from.
|
||||
gt_bboxes (Tensor): Ground truth bboxes.
|
||||
gt_labels (Tensor, optional): Class labels of ground truth bboxes.
|
||||
|
||||
Returns:
|
||||
:obj:`SamplingResult`: Sampling result.
|
||||
|
||||
Example:
|
||||
>>> from mmdet.core.bbox import RandomSampler
|
||||
>>> from mmdet.core.bbox import AssignResult
|
||||
>>> from mmdet.core.bbox.demodata import ensure_rng, random_boxes
|
||||
>>> rng = ensure_rng(None)
|
||||
>>> assign_result = AssignResult.random(rng=rng)
|
||||
>>> bboxes = random_boxes(assign_result.num_preds, rng=rng)
|
||||
>>> gt_bboxes = random_boxes(assign_result.num_gts, rng=rng)
|
||||
>>> gt_labels = None
|
||||
>>> self = RandomSampler(num=32, pos_fraction=0.5, neg_pos_ub=-1,
|
||||
>>> add_gt_as_proposals=False)
|
||||
>>> self = self.sample(assign_result, bboxes, gt_bboxes, gt_labels)
|
||||
"""
|
||||
if len(bboxes.shape) < 2:
|
||||
bboxes = bboxes[None, :]
|
||||
|
||||
bboxes = bboxes[:, :4]
|
||||
|
||||
gt_flags = bboxes.new_zeros((bboxes.shape[0], ), dtype=torch.uint8)
|
||||
if self.add_gt_as_proposals and len(gt_bboxes) > 0:
|
||||
if gt_labels is None:
|
||||
raise ValueError(
|
||||
'gt_labels must be given when add_gt_as_proposals is True')
|
||||
bboxes = torch.cat([gt_bboxes, bboxes], dim=0)
|
||||
assign_result.add_gt_(gt_labels)
|
||||
gt_ones = bboxes.new_ones(gt_bboxes.shape[0], dtype=torch.uint8)
|
||||
gt_flags = torch.cat([gt_ones, gt_flags])
|
||||
|
||||
num_expected_pos = int(self.num * self.pos_fraction)
|
||||
pos_inds = self.pos_sampler._sample_pos(assign_result,
|
||||
num_expected_pos,
|
||||
bboxes=bboxes,
|
||||
**kwargs)
|
||||
# We found that sampled indices have duplicated items occasionally.
|
||||
# (may be a bug of PyTorch)
|
||||
pos_inds = pos_inds.unique()
|
||||
num_sampled_pos = pos_inds.numel()
|
||||
num_expected_neg = self.num - num_sampled_pos
|
||||
if self.neg_pos_ub >= 0:
|
||||
_pos = max(1, num_sampled_pos)
|
||||
neg_upper_bound = int(self.neg_pos_ub * _pos)
|
||||
if num_expected_neg > neg_upper_bound:
|
||||
num_expected_neg = neg_upper_bound
|
||||
neg_inds = self.neg_sampler._sample_neg(assign_result,
|
||||
num_expected_neg,
|
||||
bboxes=bboxes,
|
||||
**kwargs)
|
||||
neg_inds = neg_inds.unique()
|
||||
|
||||
sampling_result = SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,
|
||||
assign_result, gt_flags)
|
||||
return sampling_result
|
||||
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
"""copy from
|
||||
https://github.com/ZwwWayne/K-Net/blob/main/knet/det/mask_pseudo_sampler.py."""
|
||||
|
||||
import torch
|
||||
|
||||
from ..builder import BBOX_SAMPLERS
|
||||
from .base_sampler import BaseSampler
|
||||
from .mask_sampling_result import MaskSamplingResult
|
||||
|
||||
|
||||
@BBOX_SAMPLERS.register_module()
|
||||
class MaskPseudoSampler(BaseSampler):
|
||||
"""A pseudo sampler that does not do sampling actually."""
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def _sample_pos(self, **kwargs):
|
||||
"""Sample positive samples."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _sample_neg(self, **kwargs):
|
||||
"""Sample negative samples."""
|
||||
raise NotImplementedError
|
||||
|
||||
def sample(self, assign_result, masks, gt_masks, **kwargs):
|
||||
"""Directly returns the positive and negative indices of samples.
|
||||
|
||||
Args:
|
||||
assign_result (:obj:`AssignResult`): Assigned results
|
||||
masks (torch.Tensor): Bounding boxes
|
||||
gt_masks (torch.Tensor): Ground truth boxes
|
||||
Returns:
|
||||
:obj:`SamplingResult`: sampler results
|
||||
"""
|
||||
pos_inds = torch.nonzero(assign_result.gt_inds > 0,
|
||||
as_tuple=False).squeeze(-1).unique()
|
||||
neg_inds = torch.nonzero(assign_result.gt_inds == 0,
|
||||
as_tuple=False).squeeze(-1).unique()
|
||||
gt_flags = masks.new_zeros(masks.shape[0], dtype=torch.uint8)
|
||||
sampling_result = MaskSamplingResult(pos_inds, neg_inds, masks,
|
||||
gt_masks, assign_result, gt_flags)
|
||||
return sampling_result
|
||||
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
"""copy from
|
||||
https://github.com/ZwwWayne/K-Net/blob/main/knet/det/mask_pseudo_sampler.py."""
|
||||
|
||||
import torch
|
||||
|
||||
from .sampling_result import SamplingResult
|
||||
|
||||
|
||||
class MaskSamplingResult(SamplingResult):
|
||||
"""Mask sampling result."""
|
||||
def __init__(self, pos_inds, neg_inds, masks, gt_masks, assign_result,
|
||||
gt_flags):
|
||||
self.pos_inds = pos_inds
|
||||
self.neg_inds = neg_inds
|
||||
self.pos_masks = masks[pos_inds]
|
||||
self.neg_masks = masks[neg_inds]
|
||||
self.pos_is_gt = gt_flags[pos_inds]
|
||||
|
||||
self.num_gts = gt_masks.shape[0]
|
||||
self.pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1
|
||||
|
||||
if gt_masks.numel() == 0:
|
||||
# hack for index error case
|
||||
assert self.pos_assigned_gt_inds.numel() == 0
|
||||
self.pos_gt_masks = torch.empty_like(gt_masks)
|
||||
else:
|
||||
self.pos_gt_masks = gt_masks[self.pos_assigned_gt_inds, :]
|
||||
|
||||
if assign_result.labels is not None:
|
||||
self.pos_gt_labels = assign_result.labels[pos_inds]
|
||||
else:
|
||||
self.pos_gt_labels = None
|
||||
|
||||
@property
|
||||
def masks(self):
|
||||
"""torch.Tensor: concatenated positive and negative boxes"""
|
||||
return torch.cat([self.pos_masks, self.neg_masks])
|
||||
|
||||
def __nice__(self):
|
||||
data = self.info.copy()
|
||||
data['pos_masks'] = data.pop('pos_masks').shape
|
||||
data['neg_masks'] = data.pop('neg_masks').shape
|
||||
parts = [f"'{k}': {v!r}" for k, v in sorted(data.items())]
|
||||
body = ' ' + ',\n '.join(parts)
|
||||
return '{\n' + body + '\n}'
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""Returns a dictionary of info about the object."""
|
||||
return {
|
||||
'pos_inds': self.pos_inds,
|
||||
'neg_inds': self.neg_inds,
|
||||
'pos_masks': self.pos_masks,
|
||||
'neg_masks': self.neg_masks,
|
||||
'pos_is_gt': self.pos_is_gt,
|
||||
'num_gts': self.num_gts,
|
||||
'pos_assigned_gt_inds': self.pos_assigned_gt_inds,
|
||||
}
|
||||
150
segmentation/mmseg_custom/core/box/samplers/sampling_result.py
Normal file
150
segmentation/mmseg_custom/core/box/samplers/sampling_result.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from mmdet.utils import util_mixins
|
||||
|
||||
|
||||
class SamplingResult(util_mixins.NiceRepr):
|
||||
"""Bbox sampling result.
|
||||
|
||||
Example:
|
||||
>>> # xdoctest: +IGNORE_WANT
|
||||
>>> from mmdet.core.bbox.samplers.sampling_result import * # NOQA
|
||||
>>> self = SamplingResult.random(rng=10)
|
||||
>>> print(f'self = {self}')
|
||||
self = <SamplingResult({
|
||||
'neg_bboxes': torch.Size([12, 4]),
|
||||
'neg_inds': tensor([ 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
|
||||
'num_gts': 4,
|
||||
'pos_assigned_gt_inds': tensor([], dtype=torch.int64),
|
||||
'pos_bboxes': torch.Size([0, 4]),
|
||||
'pos_inds': tensor([], dtype=torch.int64),
|
||||
'pos_is_gt': tensor([], dtype=torch.uint8)
|
||||
})>
|
||||
"""
|
||||
def __init__(self, pos_inds, neg_inds, bboxes, gt_bboxes, assign_result,
|
||||
gt_flags):
|
||||
self.pos_inds = pos_inds
|
||||
self.neg_inds = neg_inds
|
||||
self.pos_bboxes = bboxes[pos_inds]
|
||||
self.neg_bboxes = bboxes[neg_inds]
|
||||
self.pos_is_gt = gt_flags[pos_inds]
|
||||
|
||||
self.num_gts = gt_bboxes.shape[0]
|
||||
self.pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1
|
||||
|
||||
if gt_bboxes.numel() == 0:
|
||||
# hack for index error case
|
||||
assert self.pos_assigned_gt_inds.numel() == 0
|
||||
self.pos_gt_bboxes = torch.empty_like(gt_bboxes).view(-1, 4)
|
||||
else:
|
||||
if len(gt_bboxes.shape) < 2:
|
||||
gt_bboxes = gt_bboxes.view(-1, 4)
|
||||
|
||||
self.pos_gt_bboxes = gt_bboxes[self.pos_assigned_gt_inds.long(), :]
|
||||
|
||||
if assign_result.labels is not None:
|
||||
self.pos_gt_labels = assign_result.labels[pos_inds]
|
||||
else:
|
||||
self.pos_gt_labels = None
|
||||
|
||||
@property
|
||||
def bboxes(self):
|
||||
"""torch.Tensor: concatenated positive and negative boxes"""
|
||||
return torch.cat([self.pos_bboxes, self.neg_bboxes])
|
||||
|
||||
def to(self, device):
|
||||
"""Change the device of the data inplace.
|
||||
|
||||
Example:
|
||||
>>> self = SamplingResult.random()
|
||||
>>> print(f'self = {self.to(None)}')
|
||||
>>> # xdoctest: +REQUIRES(--gpu)
|
||||
>>> print(f'self = {self.to(0)}')
|
||||
"""
|
||||
_dict = self.__dict__
|
||||
for key, value in _dict.items():
|
||||
if isinstance(value, torch.Tensor):
|
||||
_dict[key] = value.to(device)
|
||||
return self
|
||||
|
||||
def __nice__(self):
|
||||
data = self.info.copy()
|
||||
data['pos_bboxes'] = data.pop('pos_bboxes').shape
|
||||
data['neg_bboxes'] = data.pop('neg_bboxes').shape
|
||||
parts = [f"'{k}': {v!r}" for k, v in sorted(data.items())]
|
||||
body = ' ' + ',\n '.join(parts)
|
||||
return '{\n' + body + '\n}'
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""Returns a dictionary of info about the object."""
|
||||
return {
|
||||
'pos_inds': self.pos_inds,
|
||||
'neg_inds': self.neg_inds,
|
||||
'pos_bboxes': self.pos_bboxes,
|
||||
'neg_bboxes': self.neg_bboxes,
|
||||
'pos_is_gt': self.pos_is_gt,
|
||||
'num_gts': self.num_gts,
|
||||
'pos_assigned_gt_inds': self.pos_assigned_gt_inds,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def random(cls, rng=None, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
rng (None | int | numpy.random.RandomState): seed or state.
|
||||
kwargs (keyword arguments):
|
||||
- num_preds: number of predicted boxes
|
||||
- num_gts: number of true boxes
|
||||
- p_ignore (float): probability of a predicted box assigned to \
|
||||
an ignored truth.
|
||||
- p_assigned (float): probability of a predicted box not being \
|
||||
assigned.
|
||||
- p_use_label (float | bool): with labels or not.
|
||||
|
||||
Returns:
|
||||
:obj:`SamplingResult`: Randomly generated sampling result.
|
||||
|
||||
Example:
|
||||
>>> from mmdet.core.bbox.samplers.sampling_result import * # NOQA
|
||||
>>> self = SamplingResult.random()
|
||||
>>> print(self.__dict__)
|
||||
"""
|
||||
from mmdet.core.bbox import demodata
|
||||
from mmdet.core.bbox.assigners.assign_result import AssignResult
|
||||
from mmdet.core.bbox.samplers.random_sampler import RandomSampler
|
||||
rng = demodata.ensure_rng(rng)
|
||||
|
||||
# make probabalistic?
|
||||
num = 32
|
||||
pos_fraction = 0.5
|
||||
neg_pos_ub = -1
|
||||
|
||||
assign_result = AssignResult.random(rng=rng, **kwargs)
|
||||
|
||||
# Note we could just compute an assignment
|
||||
bboxes = demodata.random_boxes(assign_result.num_preds, rng=rng)
|
||||
gt_bboxes = demodata.random_boxes(assign_result.num_gts, rng=rng)
|
||||
|
||||
if rng.rand() > 0.2:
|
||||
# sometimes algorithms squeeze their data, be robust to that
|
||||
gt_bboxes = gt_bboxes.squeeze()
|
||||
bboxes = bboxes.squeeze()
|
||||
|
||||
if assign_result.labels is None:
|
||||
gt_labels = None
|
||||
else:
|
||||
gt_labels = None # todo
|
||||
|
||||
if gt_labels is None:
|
||||
add_gt_as_proposals = False
|
||||
else:
|
||||
add_gt_as_proposals = True # make probabalistic?
|
||||
|
||||
sampler = RandomSampler(num,
|
||||
pos_fraction,
|
||||
neg_pos_ub=neg_pos_ub,
|
||||
add_gt_as_proposals=add_gt_as_proposals,
|
||||
rng=rng)
|
||||
self = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels)
|
||||
return self
|
||||
2
segmentation/mmseg_custom/core/evaluation/__init__.py
Normal file
2
segmentation/mmseg_custom/core/evaluation/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .panoptic_utils import INSTANCE_OFFSET # noqa: F401,F403
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
# A custom value to distinguish instance ID and category ID; need to
|
||||
# be greater than the number of categories.
|
||||
# For a pixel in the panoptic result map:
|
||||
# pan_id = ins_id * INSTANCE_OFFSET + cat_id
|
||||
INSTANCE_OFFSET = 1000
|
||||
2
segmentation/mmseg_custom/core/mask/__init__.py
Normal file
2
segmentation/mmseg_custom/core/mask/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .utils import mask2bbox # noqa: F401,F403
|
||||
89
segmentation/mmseg_custom/core/mask/utils.py
Normal file
89
segmentation/mmseg_custom/core/mask/utils.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import pycocotools.mask as mask_util
|
||||
import torch
|
||||
|
||||
|
||||
def split_combined_polys(polys, poly_lens, polys_per_mask):
|
||||
"""Split the combined 1-D polys into masks.
|
||||
|
||||
A mask is represented as a list of polys, and a poly is represented as
|
||||
a 1-D array. In dataset, all masks are concatenated into a single 1-D
|
||||
tensor. Here we need to split the tensor into original representations.
|
||||
|
||||
Args:
|
||||
polys (list): a list (length = image num) of 1-D tensors
|
||||
poly_lens (list): a list (length = image num) of poly length
|
||||
polys_per_mask (list): a list (length = image num) of poly number
|
||||
of each mask
|
||||
|
||||
Returns:
|
||||
list: a list (length = image num) of list (length = mask num) of \
|
||||
list (length = poly num) of numpy array.
|
||||
"""
|
||||
mask_polys_list = []
|
||||
for img_id in range(len(polys)):
|
||||
polys_single = polys[img_id]
|
||||
polys_lens_single = poly_lens[img_id].tolist()
|
||||
polys_per_mask_single = polys_per_mask[img_id].tolist()
|
||||
|
||||
split_polys = mmcv.slice_list(polys_single, polys_lens_single)
|
||||
mask_polys = mmcv.slice_list(split_polys, polys_per_mask_single)
|
||||
mask_polys_list.append(mask_polys)
|
||||
return mask_polys_list
|
||||
|
||||
|
||||
# TODO: move this function to more proper place
|
||||
def encode_mask_results(mask_results):
|
||||
"""Encode bitmap mask to RLE code.
|
||||
|
||||
Args:
|
||||
mask_results (list | tuple[list]): bitmap mask results.
|
||||
In mask scoring rcnn, mask_results is a tuple of (segm_results,
|
||||
segm_cls_score).
|
||||
|
||||
Returns:
|
||||
list | tuple: RLE encoded mask.
|
||||
"""
|
||||
if isinstance(mask_results, tuple): # mask scoring
|
||||
cls_segms, cls_mask_scores = mask_results
|
||||
else:
|
||||
cls_segms = mask_results
|
||||
num_classes = len(cls_segms)
|
||||
encoded_mask_results = [[] for _ in range(num_classes)]
|
||||
for i in range(len(cls_segms)):
|
||||
for cls_segm in cls_segms[i]:
|
||||
encoded_mask_results[i].append(
|
||||
mask_util.encode(
|
||||
np.array(
|
||||
cls_segm[:, :, np.newaxis], order='F',
|
||||
dtype='uint8'))[0]) # encoded with RLE
|
||||
if isinstance(mask_results, tuple):
|
||||
return encoded_mask_results, cls_mask_scores
|
||||
else:
|
||||
return encoded_mask_results
|
||||
|
||||
|
||||
def mask2bbox(masks):
|
||||
"""Obtain tight bounding boxes of binary masks.
|
||||
|
||||
Args:
|
||||
masks (Tensor): Binary mask of shape (n, h, w).
|
||||
|
||||
Returns:
|
||||
Tensor: Bboxe with shape (n, 4) of \
|
||||
positive region in binary mask.
|
||||
"""
|
||||
N = masks.shape[0]
|
||||
bboxes = masks.new_zeros((N, 4), dtype=torch.float32)
|
||||
x_any = torch.any(masks, dim=1)
|
||||
y_any = torch.any(masks, dim=2)
|
||||
for i in range(N):
|
||||
x = torch.where(x_any[i, :])[0]
|
||||
y = torch.where(y_any[i, :])[0]
|
||||
if len(x) > 0 and len(y) > 0:
|
||||
bboxes[i, :] = bboxes.new_tensor(
|
||||
[x[0], y[0], x[-1] + 1, y[-1] + 1])
|
||||
|
||||
return bboxes
|
||||
9
segmentation/mmseg_custom/core/utils/__init__.py
Normal file
9
segmentation/mmseg_custom/core/utils/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .dist_utils import (DistOptimizerHook, all_reduce_dict, allreduce_grads,
|
||||
reduce_mean)
|
||||
from .misc import add_prefix, multi_apply
|
||||
|
||||
__all__ = [
|
||||
'add_prefix', 'multi_apply', 'DistOptimizerHook', 'allreduce_grads',
|
||||
'all_reduce_dict', 'reduce_mean'
|
||||
]
|
||||
148
segmentation/mmseg_custom/core/utils/dist_utils.py
Normal file
148
segmentation/mmseg_custom/core/utils/dist_utils.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import functools
|
||||
import pickle
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from mmcv.runner import OptimizerHook, get_dist_info
|
||||
from torch._utils import (_flatten_dense_tensors, _take_tensors,
|
||||
_unflatten_dense_tensors)
|
||||
|
||||
|
||||
def _allreduce_coalesced(tensors, world_size, bucket_size_mb=-1):
|
||||
if bucket_size_mb > 0:
|
||||
bucket_size_bytes = bucket_size_mb * 1024 * 1024
|
||||
buckets = _take_tensors(tensors, bucket_size_bytes)
|
||||
else:
|
||||
buckets = OrderedDict()
|
||||
for tensor in tensors:
|
||||
tp = tensor.type()
|
||||
if tp not in buckets:
|
||||
buckets[tp] = []
|
||||
buckets[tp].append(tensor)
|
||||
buckets = buckets.values()
|
||||
|
||||
for bucket in buckets:
|
||||
flat_tensors = _flatten_dense_tensors(bucket)
|
||||
dist.all_reduce(flat_tensors)
|
||||
flat_tensors.div_(world_size)
|
||||
for tensor, synced in zip(
|
||||
bucket, _unflatten_dense_tensors(flat_tensors, bucket)):
|
||||
tensor.copy_(synced)
|
||||
|
||||
|
||||
def allreduce_grads(params, coalesce=True, bucket_size_mb=-1):
|
||||
"""Allreduce gradients.
|
||||
|
||||
Args:
|
||||
params (list[torch.Parameters]): List of parameters of a model
|
||||
coalesce (bool, optional): Whether allreduce parameters as a whole.
|
||||
Defaults to True.
|
||||
bucket_size_mb (int, optional): Size of bucket, the unit is MB.
|
||||
Defaults to -1.
|
||||
"""
|
||||
grads = [
|
||||
param.grad.data for param in params
|
||||
if param.requires_grad and param.grad is not None
|
||||
]
|
||||
world_size = dist.get_world_size()
|
||||
if coalesce:
|
||||
_allreduce_coalesced(grads, world_size, bucket_size_mb)
|
||||
else:
|
||||
for tensor in grads:
|
||||
dist.all_reduce(tensor.div_(world_size))
|
||||
|
||||
|
||||
class DistOptimizerHook(OptimizerHook):
|
||||
"""Deprecated optimizer hook for distributed training."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn('"DistOptimizerHook" is deprecated, please switch to'
|
||||
'"mmcv.runner.OptimizerHook".')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def reduce_mean(tensor):
|
||||
""""Obtain the mean of tensor on different GPUs."""
|
||||
if not (dist.is_available() and dist.is_initialized()):
|
||||
return tensor
|
||||
tensor = tensor.clone()
|
||||
dist.all_reduce(tensor.div_(dist.get_world_size()), op=dist.ReduceOp.SUM)
|
||||
return tensor
|
||||
|
||||
|
||||
def obj2tensor(pyobj, device='cuda'):
|
||||
"""Serialize picklable python object to tensor."""
|
||||
storage = torch.ByteStorage.from_buffer(pickle.dumps(pyobj))
|
||||
return torch.ByteTensor(storage).to(device=device)
|
||||
|
||||
|
||||
def tensor2obj(tensor):
|
||||
"""Deserialize tensor to picklable python object."""
|
||||
return pickle.loads(tensor.cpu().numpy().tobytes())
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def _get_global_gloo_group():
|
||||
"""Return a process group based on gloo backend, containing all the ranks
|
||||
The result is cached."""
|
||||
if dist.get_backend() == 'nccl':
|
||||
return dist.new_group(backend='gloo')
|
||||
else:
|
||||
return dist.group.WORLD
|
||||
|
||||
|
||||
def all_reduce_dict(py_dict, op='sum', group=None, to_float=True):
|
||||
"""Apply all reduce function for python dict object.
|
||||
|
||||
The code is modified from https://github.com/Megvii-
|
||||
BaseDetection/YOLOX/blob/main/yolox/utils/allreduce_norm.py.
|
||||
|
||||
NOTE: make sure that py_dict in different ranks has the same keys and
|
||||
the values should be in the same shape.
|
||||
|
||||
Args:
|
||||
py_dict (dict): Dict to be applied all reduce op.
|
||||
op (str): Operator, could be 'sum' or 'mean'. Default: 'sum'
|
||||
group (:obj:`torch.distributed.group`, optional): Distributed group,
|
||||
Default: None.
|
||||
to_float (bool): Whether to convert all values of dict to float.
|
||||
Default: True.
|
||||
|
||||
Returns:
|
||||
OrderedDict: reduced python dict object.
|
||||
"""
|
||||
_, world_size = get_dist_info()
|
||||
if world_size == 1:
|
||||
return py_dict
|
||||
if group is None:
|
||||
# TODO: May try not to use gloo in the future
|
||||
group = _get_global_gloo_group()
|
||||
if dist.get_world_size(group) == 1:
|
||||
return py_dict
|
||||
|
||||
# all reduce logic across different devices.
|
||||
py_key = list(py_dict.keys())
|
||||
py_key_tensor = obj2tensor(py_key)
|
||||
dist.broadcast(py_key_tensor, src=0)
|
||||
py_key = tensor2obj(py_key_tensor)
|
||||
|
||||
tensor_shapes = [py_dict[k].shape for k in py_key]
|
||||
tensor_numels = [py_dict[k].numel() for k in py_key]
|
||||
|
||||
if to_float:
|
||||
flatten_tensor = torch.cat(
|
||||
[py_dict[k].flatten().float() for k in py_key])
|
||||
else:
|
||||
flatten_tensor = torch.cat([py_dict[k].flatten() for k in py_key])
|
||||
|
||||
dist.all_reduce(flatten_tensor, op=dist.ReduceOp.SUM)
|
||||
if op == 'mean':
|
||||
flatten_tensor /= world_size
|
||||
|
||||
split_tensors = [
|
||||
x.reshape(shape) for x, shape in zip(
|
||||
torch.split(flatten_tensor, tensor_numels), tensor_shapes)
|
||||
]
|
||||
return OrderedDict({k: v for k, v in zip(py_key, split_tensors)})
|
||||
40
segmentation/mmseg_custom/core/utils/misc.py
Normal file
40
segmentation/mmseg_custom/core/utils/misc.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
def multi_apply(func, *args, **kwargs):
|
||||
"""Apply function to a list of arguments.
|
||||
|
||||
Note:
|
||||
This function applies the ``func`` to multiple inputs and
|
||||
map the multiple outputs of the ``func`` into different
|
||||
list. Each list contains the same type of outputs corresponding
|
||||
to different inputs.
|
||||
|
||||
Args:
|
||||
func (Function): A function that will be applied to a list of
|
||||
arguments
|
||||
|
||||
Returns:
|
||||
tuple(list): A tuple containing multiple list, each list contains \
|
||||
a kind of returned results by the function
|
||||
"""
|
||||
pfunc = partial(func, **kwargs) if kwargs else func
|
||||
map_results = map(pfunc, *args)
|
||||
return tuple(map(list, zip(*map_results)))
|
||||
|
||||
|
||||
def add_prefix(inputs, prefix):
|
||||
"""Add prefix for dict.
|
||||
|
||||
Args:
|
||||
inputs (dict): The input dict with str keys.
|
||||
prefix (str): The prefix to add.
|
||||
|
||||
Returns:
|
||||
|
||||
dict: The dict with keys updated with ``prefix``.
|
||||
"""
|
||||
|
||||
outputs = dict()
|
||||
for name, value in inputs.items():
|
||||
outputs[f'{prefix}.{name}'] = value
|
||||
|
||||
return outputs
|
||||
Reference in New Issue
Block a user