group_points.py 7.97 KB
Newer Older
1
# Copyright (c) OpenMMLab. All rights reserved.
2
3
from typing import Tuple

wuyuefeng's avatar
wuyuefeng committed
4
import torch
5
from mmcv.runner import force_fp32
zhangwenwei's avatar
zhangwenwei committed
6
from torch import nn as nn
wuyuefeng's avatar
wuyuefeng committed
7
8
9
from torch.autograd import Function

from ..ball_query import ball_query
10
from ..knn import knn
wuyuefeng's avatar
wuyuefeng committed
11
12
13
14
15
16
17
18
19
from . import group_points_ext


class QueryAndGroup(nn.Module):
    """Query and Group.

    Groups with a ball query of radius

    Args:
20
        max_radius (float): The maximum radius of the balls.
21
            If None is given, we will use kNN sampling instead of ball query.
wuyuefeng's avatar
wuyuefeng committed
22
        sample_num (int): Maximum number of features to gather in the ball.
23
24
25
        min_radius (float, optional): The minimum radius of the balls.
            Default: 0.
        use_xyz (bool, optional): Whether to use xyz.
wuyuefeng's avatar
wuyuefeng committed
26
            Default: True.
27
        return_grouped_xyz (bool, optional): Whether to return grouped xyz.
wuyuefeng's avatar
wuyuefeng committed
28
            Default: False.
29
        normalize_xyz (bool, optional): Whether to normalize xyz.
wuyuefeng's avatar
wuyuefeng committed
30
            Default: False.
31
        uniform_sample (bool, optional): Whether to sample uniformly.
wuyuefeng's avatar
wuyuefeng committed
32
            Default: False
33
34
35
        return_unique_cnt (bool, optional): Whether to return the count of
            unique samples. Default: False.
        return_grouped_idx (bool, optional): Whether to return grouped idx.
36
            Default: False.
wuyuefeng's avatar
wuyuefeng committed
37
38
39
    """

    def __init__(self,
40
                 max_radius,
wuyuefeng's avatar
wuyuefeng committed
41
                 sample_num,
42
                 min_radius=0,
wuyuefeng's avatar
wuyuefeng committed
43
44
45
46
                 use_xyz=True,
                 return_grouped_xyz=False,
                 normalize_xyz=False,
                 uniform_sample=False,
47
48
                 return_unique_cnt=False,
                 return_grouped_idx=False):
wuyuefeng's avatar
wuyuefeng committed
49
        super(QueryAndGroup, self).__init__()
50
51
        self.max_radius = max_radius
        self.min_radius = min_radius
wuyuefeng's avatar
wuyuefeng committed
52
53
54
55
56
57
        self.sample_num = sample_num
        self.use_xyz = use_xyz
        self.return_grouped_xyz = return_grouped_xyz
        self.normalize_xyz = normalize_xyz
        self.uniform_sample = uniform_sample
        self.return_unique_cnt = return_unique_cnt
58
        self.return_grouped_idx = return_grouped_idx
wuyuefeng's avatar
wuyuefeng committed
59
        if self.return_unique_cnt:
60
61
62
63
64
65
            assert self.uniform_sample, \
                'uniform_sample should be True when ' \
                'returning the count of unique samples'
        if self.max_radius is None:
            assert not self.normalize_xyz, \
                'can not normalize grouped xyz when max_radius is None'
66
        self.fp16_enabled = False
wuyuefeng's avatar
wuyuefeng committed
67

68
    @force_fp32()
wuyuefeng's avatar
wuyuefeng committed
69
    def forward(self, points_xyz, center_xyz, features=None):
zhangwenwei's avatar
zhangwenwei committed
70
        """forward.
wuyuefeng's avatar
wuyuefeng committed
71
72
73
74
75
76
77
78
79

        Args:
            points_xyz (Tensor): (B, N, 3) xyz coordinates of the features.
            center_xyz (Tensor): (B, npoint, 3) Centriods.
            features (Tensor): (B, C, N) Descriptors of the features.

        Return:
            Tensor: (B, 3 + C, npoint, sample_num) Grouped feature.
        """
80
81
82
83
84
85
86
87
        # if self.max_radius is None, we will perform kNN instead of ball query
        # idx is of shape [B, npoint, sample_num]
        if self.max_radius is None:
            idx = knn(self.sample_num, points_xyz, center_xyz, False)
            idx = idx.transpose(1, 2).contiguous()
        else:
            idx = ball_query(self.min_radius, self.max_radius, self.sample_num,
                             points_xyz, center_xyz)
wuyuefeng's avatar
wuyuefeng committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

        if self.uniform_sample:
            unique_cnt = torch.zeros((idx.shape[0], idx.shape[1]))
            for i_batch in range(idx.shape[0]):
                for i_region in range(idx.shape[1]):
                    unique_ind = torch.unique(idx[i_batch, i_region, :])
                    num_unique = unique_ind.shape[0]
                    unique_cnt[i_batch, i_region] = num_unique
                    sample_ind = torch.randint(
                        0,
                        num_unique, (self.sample_num - num_unique, ),
                        dtype=torch.long)
                    all_ind = torch.cat((unique_ind, unique_ind[sample_ind]))
                    idx[i_batch, i_region, :] = all_ind

        xyz_trans = points_xyz.transpose(1, 2).contiguous()
        # (B, 3, npoint, sample_num)
        grouped_xyz = grouping_operation(xyz_trans, idx)
106
107
        grouped_xyz_diff = grouped_xyz - \
            center_xyz.transpose(1, 2).unsqueeze(-1)  # relative offsets
wuyuefeng's avatar
wuyuefeng committed
108
        if self.normalize_xyz:
109
            grouped_xyz_diff /= self.max_radius
wuyuefeng's avatar
wuyuefeng committed
110
111
112
113
114

        if features is not None:
            grouped_features = grouping_operation(features, idx)
            if self.use_xyz:
                # (B, C + 3, npoint, sample_num)
115
                new_features = torch.cat([grouped_xyz_diff, grouped_features],
wuyuefeng's avatar
wuyuefeng committed
116
117
118
119
120
121
                                         dim=1)
            else:
                new_features = grouped_features
        else:
            assert (self.use_xyz
                    ), 'Cannot have not features and not use xyz as a feature!'
122
            new_features = grouped_xyz_diff
wuyuefeng's avatar
wuyuefeng committed
123
124
125
126
127
128

        ret = [new_features]
        if self.return_grouped_xyz:
            ret.append(grouped_xyz)
        if self.return_unique_cnt:
            ret.append(unique_cnt)
129
130
        if self.return_grouped_idx:
            ret.append(idx)
wuyuefeng's avatar
wuyuefeng committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
        if len(ret) == 1:
            return ret[0]
        else:
            return tuple(ret)


class GroupAll(nn.Module):
    """Group All.

    Group xyz with feature.

    Args:
        use_xyz (bool): Whether to use xyz.
    """

    def __init__(self, use_xyz: bool = True):
        super().__init__()
        self.use_xyz = use_xyz
149
        self.fp16_enabled = False
wuyuefeng's avatar
wuyuefeng committed
150

151
    @force_fp32()
wuyuefeng's avatar
wuyuefeng committed
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    def forward(self,
                xyz: torch.Tensor,
                new_xyz: torch.Tensor,
                features: torch.Tensor = None):
        """forward.

        Args:
            xyz (Tensor): (B, N, 3) xyz coordinates of the features.
            new_xyz (Tensor): Ignored.
            features (Tensor): (B, C, N) features to group.

        Return:
            Tensor: (B, C + 3, 1, N) Grouped feature.
        """
        grouped_xyz = xyz.transpose(1, 2).unsqueeze(2)
        if features is not None:
            grouped_features = features.unsqueeze(2)
            if self.use_xyz:
                new_features = torch.cat([grouped_xyz, grouped_features],
                                         dim=1)  # (B, 3 + C, 1, N)
            else:
                new_features = grouped_features
        else:
            new_features = grouped_xyz

        return new_features


class GroupingOperation(Function):
    """Grouping Operation.

    Group feature with given index.
    """

    @staticmethod
    def forward(ctx, features: torch.Tensor,
                indices: torch.Tensor) -> torch.Tensor:
        """forward.

        Args:
            features (Tensor): (B, C, N) tensor of features to group.
193
            indices (Tensor): (B, npoint, nsample) the indices of
wuyuefeng's avatar
wuyuefeng committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
                features to group with.

        Returns:
            Tensor: (B, C, npoint, nsample) Grouped features.
        """
        assert features.is_contiguous()
        assert indices.is_contiguous()

        B, nfeatures, nsample = indices.size()
        _, C, N = features.size()
        output = torch.cuda.FloatTensor(B, C, nfeatures, nsample)

        group_points_ext.forward(B, C, N, nfeatures, nsample, features,
                                 indices, output)

        ctx.for_backwards = (indices, N)
        return output

    @staticmethod
    def backward(ctx,
                 grad_out: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        """backward.

        Args:
            grad_out (Tensor): (B, C, npoint, nsample) tensor of the gradients
                of the output from forward.

        Returns:
            Tensor: (B, C, N) gradient of the features.
        """
        idx, N = ctx.for_backwards

        B, C, npoint, nsample = grad_out.size()
        grad_features = torch.cuda.FloatTensor(B, C, N).zero_()

        grad_out_data = grad_out.data.contiguous()
        group_points_ext.backward(B, C, N, npoint, nsample, grad_out_data, idx,
                                  grad_features.data)
        return grad_features, None


grouping_operation = GroupingOperation.apply