birth
This commit is contained in:
13
segmentation/mmseg_custom/models/utils/__init__.py
Normal file
13
segmentation/mmseg_custom/models/utils/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .assigner import MaskHungarianAssigner
|
||||
from .point_sample import get_uncertain_point_coords_with_randomness
|
||||
from .positional_encoding import (LearnedPositionalEncoding,
|
||||
SinePositionalEncoding)
|
||||
from .transformer import (DetrTransformerDecoder, DetrTransformerDecoderLayer,
|
||||
DynamicConv, Transformer)
|
||||
|
||||
__all__ = [
|
||||
'DetrTransformerDecoderLayer', 'DetrTransformerDecoder', 'DynamicConv',
|
||||
'Transformer', 'LearnedPositionalEncoding', 'SinePositionalEncoding',
|
||||
'MaskHungarianAssigner', 'get_uncertain_point_coords_with_randomness'
|
||||
]
|
||||
165
segmentation/mmseg_custom/models/utils/assigner.py
Normal file
165
segmentation/mmseg_custom/models/utils/assigner.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import MASK_ASSIGNERS, build_match_cost
|
||||
|
||||
try:
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
except ImportError:
|
||||
linear_sum_assignment = None
|
||||
|
||||
|
||||
class AssignResult(metaclass=ABCMeta):
|
||||
"""Collection of assign results."""
|
||||
def __init__(self, num_gts, gt_inds, labels):
|
||||
self.num_gts = num_gts
|
||||
self.gt_inds = gt_inds
|
||||
self.labels = labels
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
info = {
|
||||
'num_gts': self.num_gts,
|
||||
'gt_inds': self.gt_inds,
|
||||
'labels': self.labels,
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
class BaseAssigner(metaclass=ABCMeta):
|
||||
"""Base assigner that assigns boxes to ground truth boxes."""
|
||||
@abstractmethod
|
||||
def assign(self, masks, gt_masks, gt_masks_ignore=None, gt_labels=None):
|
||||
"""Assign boxes to either a ground truth boxes or a negative boxes."""
|
||||
pass
|
||||
|
||||
|
||||
@MASK_ASSIGNERS.register_module()
|
||||
class MaskHungarianAssigner(BaseAssigner):
|
||||
"""Computes one-to-one matching between predictions and ground truth for
|
||||
mask.
|
||||
|
||||
This class computes an assignment between the targets and the predictions
|
||||
based on the costs. The costs are weighted sum of three components:
|
||||
classification cost, regression L1 cost and regression iou cost. The
|
||||
targets don't include the no_object, so generally there are more
|
||||
predictions than targets. After the one-to-one matching, the un-matched
|
||||
are treated as backgrounds. Thus each query prediction will be assigned
|
||||
with `0` or a positive integer indicating the ground truth index:
|
||||
|
||||
- 0: negative sample, no assigned gt
|
||||
- positive integer: positive sample, index (1-based) of assigned gt
|
||||
|
||||
Args:
|
||||
cls_cost (obj:`mmcv.ConfigDict`|dict): Classification cost config.
|
||||
mask_cost (obj:`mmcv.ConfigDict`|dict): Mask cost config.
|
||||
dice_cost (obj:`mmcv.ConfigDict`|dict): Dice cost config.
|
||||
"""
|
||||
def __init__(self,
|
||||
cls_cost=dict(type='ClassificationCost', weight=1.0),
|
||||
dice_cost=dict(type='DiceCost', weight=1.0),
|
||||
mask_cost=dict(type='MaskFocalCost', weight=1.0)):
|
||||
self.cls_cost = build_match_cost(cls_cost)
|
||||
self.dice_cost = build_match_cost(dice_cost)
|
||||
self.mask_cost = build_match_cost(mask_cost)
|
||||
|
||||
def assign(self,
|
||||
cls_pred,
|
||||
mask_pred,
|
||||
gt_labels,
|
||||
gt_masks,
|
||||
img_meta,
|
||||
gt_masks_ignore=None,
|
||||
eps=1e-7):
|
||||
"""Computes one-to-one matching based on the weighted costs.
|
||||
|
||||
This method assign each query prediction to a ground truth or
|
||||
background. The `assigned_gt_inds` with -1 means don't care,
|
||||
0 means negative sample, and positive number is the index (1-based)
|
||||
of assigned gt.
|
||||
The assignment is done in the following steps, the order matters.
|
||||
|
||||
1. assign every prediction to -1
|
||||
2. compute the weighted costs
|
||||
3. do Hungarian matching on CPU based on the costs
|
||||
4. assign all to 0 (background) first, then for each matched pair
|
||||
between predictions and gts, treat this prediction as foreground
|
||||
and assign the corresponding gt index (plus 1) to it.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): Predicted mask, shape [num_query, h, w]
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_masks (Tensor): Ground truth mask, shape [num_gt, h, w].
|
||||
gt_labels (Tensor): Label of `gt_masks`, shape (num_gt,).
|
||||
img_meta (dict): Meta information for current image.
|
||||
gt_masks_ignore (Tensor, optional): Ground truth masks that are
|
||||
labelled as `ignored`. Default None.
|
||||
eps (int | float, optional): A value added to the denominator for
|
||||
numerical stability. Default 1e-7.
|
||||
|
||||
Returns:
|
||||
:obj:`AssignResult`: The assigned result.
|
||||
"""
|
||||
assert gt_masks_ignore is None, \
|
||||
'Only case when gt_masks_ignore is None is supported.'
|
||||
num_gts, num_queries = gt_labels.shape[0], cls_pred.shape[0]
|
||||
|
||||
# 1. assign -1 by default
|
||||
assigned_gt_inds = cls_pred.new_full((num_queries, ),
|
||||
-1,
|
||||
dtype=torch.long)
|
||||
assigned_labels = cls_pred.new_full((num_queries, ),
|
||||
-1,
|
||||
dtype=torch.long)
|
||||
if num_gts == 0 or num_queries == 0:
|
||||
# No ground truth or boxes, return empty assignment
|
||||
if num_gts == 0:
|
||||
# No ground truth, assign all to background
|
||||
assigned_gt_inds[:] = 0
|
||||
return AssignResult(
|
||||
num_gts, assigned_gt_inds, labels=assigned_labels)
|
||||
|
||||
# 2. compute the weighted costs
|
||||
# classification and maskcost.
|
||||
if self.cls_cost.weight != 0 and cls_pred is not None:
|
||||
cls_cost = self.cls_cost(cls_pred, gt_labels)
|
||||
else:
|
||||
cls_cost = 0
|
||||
|
||||
if self.mask_cost.weight != 0:
|
||||
# mask_pred shape = [nq, h, w]
|
||||
# gt_mask shape = [ng, h, w]
|
||||
# mask_cost shape = [nq, ng]
|
||||
mask_cost = self.mask_cost(mask_pred, gt_masks)
|
||||
else:
|
||||
mask_cost = 0
|
||||
|
||||
if self.dice_cost.weight != 0:
|
||||
dice_cost = self.dice_cost(mask_pred, gt_masks)
|
||||
else:
|
||||
dice_cost = 0
|
||||
cost = cls_cost + mask_cost + dice_cost
|
||||
|
||||
# 3. do Hungarian matching on CPU using linear_sum_assignment
|
||||
cost = cost.detach().cpu()
|
||||
if linear_sum_assignment is None:
|
||||
raise ImportError('Please run "pip install scipy" '
|
||||
'to install scipy first.')
|
||||
|
||||
matched_row_inds, matched_col_inds = linear_sum_assignment(cost)
|
||||
matched_row_inds = torch.from_numpy(matched_row_inds).to(
|
||||
cls_pred.device)
|
||||
matched_col_inds = torch.from_numpy(matched_col_inds).to(
|
||||
cls_pred.device)
|
||||
|
||||
# 4. assign backgrounds and foregrounds
|
||||
# assign all indices to backgrounds first
|
||||
assigned_gt_inds[:] = 0
|
||||
# assign foregrounds based on matching results
|
||||
assigned_gt_inds[matched_row_inds] = matched_col_inds + 1
|
||||
assigned_labels[matched_row_inds] = gt_labels[matched_col_inds]
|
||||
return AssignResult(num_gts, assigned_gt_inds, labels=assigned_labels)
|
||||
87
segmentation/mmseg_custom/models/utils/point_sample.py
Normal file
87
segmentation/mmseg_custom/models/utils/point_sample.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from mmcv.ops import point_sample
|
||||
|
||||
|
||||
def get_uncertainty(mask_pred, labels):
|
||||
"""Estimate uncertainty based on pred logits.
|
||||
|
||||
We estimate uncertainty as L1 distance between 0.0 and the logits
|
||||
prediction in 'mask_pred' for the foreground class in `classes`.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): mask predication logits, shape (num_rois,
|
||||
num_classes, mask_height, mask_width).
|
||||
|
||||
labels (list[Tensor]): Either predicted or ground truth label for
|
||||
each predicted mask, of length num_rois.
|
||||
|
||||
Returns:
|
||||
scores (Tensor): Uncertainty scores with the most uncertain
|
||||
locations having the highest uncertainty score,
|
||||
shape (num_rois, 1, mask_height, mask_width)
|
||||
"""
|
||||
if mask_pred.shape[1] == 1:
|
||||
gt_class_logits = mask_pred.clone()
|
||||
else:
|
||||
inds = torch.arange(mask_pred.shape[0], device=mask_pred.device)
|
||||
gt_class_logits = mask_pred[inds, labels].unsqueeze(1)
|
||||
return -torch.abs(gt_class_logits)
|
||||
|
||||
|
||||
def get_uncertain_point_coords_with_randomness(mask_pred, labels, num_points,
|
||||
oversample_ratio,
|
||||
importance_sample_ratio):
|
||||
"""Get ``num_points`` most uncertain points with random points during
|
||||
train.
|
||||
|
||||
Sample points in [0, 1] x [0, 1] coordinate space based on their
|
||||
uncertainty. The uncertainties are calculated for each point using
|
||||
'get_uncertainty()' function that takes point's logit prediction as
|
||||
input.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): A tensor of shape (num_rois, num_classes,
|
||||
mask_height, mask_width) for class-specific or class-agnostic
|
||||
prediction.
|
||||
labels (list): The ground truth class for each instance.
|
||||
num_points (int): The number of points to sample.
|
||||
oversample_ratio (int): Oversampling parameter.
|
||||
importance_sample_ratio (float): Ratio of points that are sampled
|
||||
via importnace sampling.
|
||||
|
||||
Returns:
|
||||
point_coords (Tensor): A tensor of shape (num_rois, num_points, 2)
|
||||
that contains the coordinates sampled points.
|
||||
"""
|
||||
assert oversample_ratio >= 1
|
||||
assert 0 <= importance_sample_ratio <= 1
|
||||
batch_size = mask_pred.shape[0]
|
||||
num_sampled = int(num_points * oversample_ratio)
|
||||
point_coords = torch.rand(
|
||||
batch_size, num_sampled, 2, device=mask_pred.device)
|
||||
point_logits = point_sample(mask_pred, point_coords)
|
||||
# It is crucial to calculate uncertainty based on the sampled
|
||||
# prediction value for the points. Calculating uncertainties of the
|
||||
# coarse predictions first and sampling them for points leads to
|
||||
# incorrect results. To illustrate this: assume uncertainty func(
|
||||
# logits)=-abs(logits), a sampled point between two coarse
|
||||
# predictions with -1 and 1 logits has 0 logits, and therefore 0
|
||||
# uncertainty value. However, if we calculate uncertainties for the
|
||||
# coarse predictions first, both will have -1 uncertainty,
|
||||
# and sampled point will get -1 uncertainty.
|
||||
point_uncertainties = get_uncertainty(point_logits, labels)
|
||||
num_uncertain_points = int(importance_sample_ratio * num_points)
|
||||
num_random_points = num_points - num_uncertain_points
|
||||
idx = torch.topk(
|
||||
point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1]
|
||||
shift = num_sampled * torch.arange(
|
||||
batch_size, dtype=torch.long, device=mask_pred.device)
|
||||
idx += shift[:, None]
|
||||
point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view(
|
||||
batch_size, num_uncertain_points, 2)
|
||||
if num_random_points > 0:
|
||||
rand_roi_coords = torch.rand(
|
||||
batch_size, num_random_points, 2, device=mask_pred.device)
|
||||
point_coords = torch.cat((point_coords, rand_roi_coords), dim=1)
|
||||
return point_coords
|
||||
161
segmentation/mmseg_custom/models/utils/positional_encoding.py
Normal file
161
segmentation/mmseg_custom/models/utils/positional_encoding.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn.bricks.transformer import POSITIONAL_ENCODING
|
||||
from mmcv.runner import BaseModule
|
||||
|
||||
|
||||
@POSITIONAL_ENCODING.register_module()
|
||||
class SinePositionalEncoding(BaseModule):
|
||||
"""Position encoding with sine and cosine functions.
|
||||
|
||||
See `End-to-End Object Detection with Transformers
|
||||
<https://arxiv.org/pdf/2005.12872>`_ for details.
|
||||
|
||||
Args:
|
||||
num_feats (int): The feature dimension for each position
|
||||
along x-axis or y-axis. Note the final returned dimension
|
||||
for each position is 2 times of this value.
|
||||
temperature (int, optional): The temperature used for scaling
|
||||
the position embedding. Defaults to 10000.
|
||||
normalize (bool, optional): Whether to normalize the position
|
||||
embedding. Defaults to False.
|
||||
scale (float, optional): A scale factor that scales the position
|
||||
embedding. The scale will be used only when `normalize` is True.
|
||||
Defaults to 2*pi.
|
||||
eps (float, optional): A value added to the denominator for
|
||||
numerical stability. Defaults to 1e-6.
|
||||
offset (float): offset add to embed when do the normalization.
|
||||
Defaults to 0.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
Default: None
|
||||
"""
|
||||
def __init__(self,
|
||||
num_feats,
|
||||
temperature=10000,
|
||||
normalize=False,
|
||||
scale=2 * math.pi,
|
||||
eps=1e-6,
|
||||
offset=0.,
|
||||
init_cfg=None):
|
||||
super(SinePositionalEncoding, self).__init__(init_cfg)
|
||||
if normalize:
|
||||
assert isinstance(scale, (float, int)), 'when normalize is set,' \
|
||||
'scale should be provided and in float or int type, ' \
|
||||
f'found {type(scale)}'
|
||||
self.num_feats = num_feats
|
||||
self.temperature = temperature
|
||||
self.normalize = normalize
|
||||
self.scale = scale
|
||||
self.eps = eps
|
||||
self.offset = offset
|
||||
|
||||
def forward(self, mask):
|
||||
"""Forward function for `SinePositionalEncoding`.
|
||||
|
||||
Args:
|
||||
mask (Tensor): ByteTensor mask. Non-zero values representing
|
||||
ignored positions, while zero values means valid positions
|
||||
for this image. Shape [bs, h, w].
|
||||
|
||||
Returns:
|
||||
pos (Tensor): Returned position embedding with shape
|
||||
[bs, num_feats*2, h, w].
|
||||
"""
|
||||
# For convenience of exporting to ONNX, it's required to convert
|
||||
# `masks` from bool to int.
|
||||
mask = mask.to(torch.int)
|
||||
not_mask = 1 - mask # logical_not
|
||||
y_embed = not_mask.cumsum(1, dtype=torch.float32)
|
||||
x_embed = not_mask.cumsum(2, dtype=torch.float32)
|
||||
if self.normalize:
|
||||
y_embed = (y_embed + self.offset) / \
|
||||
(y_embed[:, -1:, :] + self.eps) * self.scale
|
||||
x_embed = (x_embed + self.offset) / \
|
||||
(x_embed[:, :, -1:] + self.eps) * self.scale
|
||||
dim_t = torch.arange(
|
||||
self.num_feats, dtype=torch.float32, device=mask.device)
|
||||
dim_t = self.temperature**(2 * (dim_t // 2) / self.num_feats)
|
||||
pos_x = x_embed[:, :, :, None] / dim_t
|
||||
pos_y = y_embed[:, :, :, None] / dim_t
|
||||
# use `view` instead of `flatten` for dynamically exporting to ONNX
|
||||
B, H, W = mask.size()
|
||||
pos_x = torch.stack(
|
||||
(pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()),
|
||||
dim=4).view(B, H, W, -1)
|
||||
pos_y = torch.stack(
|
||||
(pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()),
|
||||
dim=4).view(B, H, W, -1)
|
||||
pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
|
||||
return pos
|
||||
|
||||
def __repr__(self):
|
||||
"""str: a string that describes the module"""
|
||||
repr_str = self.__class__.__name__
|
||||
repr_str += f'(num_feats={self.num_feats}, '
|
||||
repr_str += f'temperature={self.temperature}, '
|
||||
repr_str += f'normalize={self.normalize}, '
|
||||
repr_str += f'scale={self.scale}, '
|
||||
repr_str += f'eps={self.eps})'
|
||||
return repr_str
|
||||
|
||||
|
||||
@POSITIONAL_ENCODING.register_module()
|
||||
class LearnedPositionalEncoding(BaseModule):
|
||||
"""Position embedding with learnable embedding weights.
|
||||
|
||||
Args:
|
||||
num_feats (int): The feature dimension for each position
|
||||
along x-axis or y-axis. The final returned dimension for
|
||||
each position is 2 times of this value.
|
||||
row_num_embed (int, optional): The dictionary size of row embeddings.
|
||||
Default 50.
|
||||
col_num_embed (int, optional): The dictionary size of col embeddings.
|
||||
Default 50.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
"""
|
||||
def __init__(self,
|
||||
num_feats,
|
||||
row_num_embed=50,
|
||||
col_num_embed=50,
|
||||
init_cfg=dict(type='Uniform', layer='Embedding')):
|
||||
super(LearnedPositionalEncoding, self).__init__(init_cfg)
|
||||
self.row_embed = nn.Embedding(row_num_embed, num_feats)
|
||||
self.col_embed = nn.Embedding(col_num_embed, num_feats)
|
||||
self.num_feats = num_feats
|
||||
self.row_num_embed = row_num_embed
|
||||
self.col_num_embed = col_num_embed
|
||||
|
||||
def forward(self, mask):
|
||||
"""Forward function for `LearnedPositionalEncoding`.
|
||||
|
||||
Args:
|
||||
mask (Tensor): ByteTensor mask. Non-zero values representing
|
||||
ignored positions, while zero values means valid positions
|
||||
for this image. Shape [bs, h, w].
|
||||
|
||||
Returns:
|
||||
pos (Tensor): Returned position embedding with shape
|
||||
[bs, num_feats*2, h, w].
|
||||
"""
|
||||
h, w = mask.shape[-2:]
|
||||
x = torch.arange(w, device=mask.device)
|
||||
y = torch.arange(h, device=mask.device)
|
||||
x_embed = self.col_embed(x)
|
||||
y_embed = self.row_embed(y)
|
||||
pos = torch.cat(
|
||||
(x_embed.unsqueeze(0).repeat(h, 1, 1), y_embed.unsqueeze(1).repeat(
|
||||
1, w, 1)),
|
||||
dim=-1).permute(2, 0,
|
||||
1).unsqueeze(0).repeat(mask.shape[0], 1, 1, 1)
|
||||
return pos
|
||||
|
||||
def __repr__(self):
|
||||
"""str: a string that describes the module"""
|
||||
repr_str = self.__class__.__name__
|
||||
repr_str += f'(num_feats={self.num_feats}, '
|
||||
repr_str += f'row_num_embed={self.row_num_embed}, '
|
||||
repr_str += f'col_num_embed={self.col_num_embed})'
|
||||
return repr_str
|
||||
1083
segmentation/mmseg_custom/models/utils/transformer.py
Normal file
1083
segmentation/mmseg_custom/models/utils/transformer.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user