primitive_head.py 45.4 KB
Newer Older
dingchang's avatar
dingchang committed
1
# Copyright (c) OpenMMLab. All rights reserved.
2
from typing import Dict, List, Optional, Tuple
jshilong's avatar
jshilong committed
3

encore-zhou's avatar
encore-zhou committed
4
5
import torch
from mmcv.cnn import ConvModule
6
from mmcv.ops import furthest_point_sample
7
from mmdet.models.utils import multi_apply
8
from mmengine.model import BaseModule
9
from mmengine.structures import InstanceData
encore-zhou's avatar
encore-zhou committed
10
11
12
from torch import nn as nn
from torch.nn import functional as F

zhangshilong's avatar
zhangshilong committed
13
from mmdet3d.models.layers import VoteModule, build_sa_module
14
from mmdet3d.registry import MODELS
zhangshilong's avatar
zhangshilong committed
15
from mmdet3d.structures import Det3DDataSample
16
from mmdet3d.structures.bbox_3d import BaseInstance3DBoxes
encore-zhou's avatar
encore-zhou committed
17
18


19
@MODELS.register_module()
20
class PrimitiveHead(BaseModule):
encore-zhou's avatar
encore-zhou committed
21
22
23
24
25
26
    r"""Primitive head of `H3DNet <https://arxiv.org/abs/2006.05682>`_.

    Args:
        num_dims (int): The dimension of primitive semantic information.
        num_classes (int): The number of class.
        primitive_mode (str): The mode of primitive module,
27
            available mode ['z', 'xy', 'line'].
encore-zhou's avatar
encore-zhou committed
28
29
        bbox_coder (:obj:`BaseBBoxCoder`): Bbox coder for encoding and
            decoding boxes.
30
31
32
33
34
35
        train_cfg (dict, optional): Config for training.
        test_cfg (dict, optional): Config for testing.
        vote_module_cfg (dict, optional): Config of VoteModule for point-wise
            votes.
        vote_aggregation_cfg (dict, optional): Config of vote aggregation
            layer.
encore-zhou's avatar
encore-zhou committed
36
37
38
        feat_channels (tuple[int]): Convolution channels of
            prediction layer.
        upper_thresh (float): Threshold for line matching.
39
        surface_thresh (float): Threshold for surface matching.
40
41
42
43
44
45
        conv_cfg (dict, optional): Config of convolution in prediction layer.
        norm_cfg (dict, optional): Config of BN in prediction layer.
        objectness_loss (dict, optional): Config of objectness loss.
        center_loss (dict, optional): Config of center loss.
        semantic_loss (dict, optional): Config of point-wise semantic
            segmentation loss.
encore-zhou's avatar
encore-zhou committed
46
47
48
    """

    def __init__(self,
jshilong's avatar
jshilong committed
49
50
51
                 num_dims: int,
                 num_classes: int,
                 primitive_mode: str,
52
53
54
55
                 train_cfg: Optional[dict] = None,
                 test_cfg: Optional[dict] = None,
                 vote_module_cfg: Optional[dict] = None,
                 vote_aggregation_cfg: Optional[dict] = None,
jshilong's avatar
jshilong committed
56
57
58
59
60
                 feat_channels: tuple = (128, 128),
                 upper_thresh: float = 100.0,
                 surface_thresh: float = 0.5,
                 conv_cfg: dict = dict(type='Conv1d'),
                 norm_cfg: dict = dict(type='BN1d'),
61
62
63
64
65
                 objectness_loss: Optional[dict] = None,
                 center_loss: Optional[dict] = None,
                 semantic_reg_loss: Optional[dict] = None,
                 semantic_cls_loss: Optional[dict] = None,
                 init_cfg: Optional[dict] = None):
66
        super(PrimitiveHead, self).__init__(init_cfg=init_cfg)
jshilong's avatar
jshilong committed
67
        # bounding boxes centers,  face centers and edge centers
encore-zhou's avatar
encore-zhou committed
68
69
70
71
72
73
74
        assert primitive_mode in ['z', 'xy', 'line']
        # The dimension of primitive semantic information.
        self.num_dims = num_dims
        self.num_classes = num_classes
        self.primitive_mode = primitive_mode
        self.train_cfg = train_cfg
        self.test_cfg = test_cfg
75
        self.gt_per_seed = vote_module_cfg['gt_per_seed']
encore-zhou's avatar
encore-zhou committed
76
77
78
79
        self.num_proposal = vote_aggregation_cfg['num_point']
        self.upper_thresh = upper_thresh
        self.surface_thresh = surface_thresh

jshilong's avatar
jshilong committed
80
81
82
83
        self.loss_objectness = MODELS.build(objectness_loss)
        self.loss_center = MODELS.build(center_loss)
        self.loss_semantic_reg = MODELS.build(semantic_reg_loss)
        self.loss_semantic_cls = MODELS.build(semantic_cls_loss)
encore-zhou's avatar
encore-zhou committed
84

85
        assert vote_aggregation_cfg['mlp_channels'][0] == vote_module_cfg[
encore-zhou's avatar
encore-zhou committed
86
87
88
89
            'in_channels']

        # Primitive existence flag prediction
        self.flag_conv = ConvModule(
90
91
            vote_module_cfg['conv_channels'][-1],
            vote_module_cfg['conv_channels'][-1] // 2,
encore-zhou's avatar
encore-zhou committed
92
93
94
95
96
97
98
            1,
            padding=0,
            conv_cfg=conv_cfg,
            norm_cfg=norm_cfg,
            bias=True,
            inplace=True)
        self.flag_pred = torch.nn.Conv1d(
99
            vote_module_cfg['conv_channels'][-1] // 2, 2, 1)
encore-zhou's avatar
encore-zhou committed
100

101
        self.vote_module = VoteModule(**vote_module_cfg)
102
        self.vote_aggregation = build_sa_module(vote_aggregation_cfg)
encore-zhou's avatar
encore-zhou committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

        prev_channel = vote_aggregation_cfg['mlp_channels'][-1]
        conv_pred_list = list()
        for k in range(len(feat_channels)):
            conv_pred_list.append(
                ConvModule(
                    prev_channel,
                    feat_channels[k],
                    1,
                    padding=0,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    bias=True,
                    inplace=True))
            prev_channel = feat_channels[k]
        self.conv_pred = nn.Sequential(*conv_pred_list)

        conv_out_channel = 3 + num_dims + num_classes
        self.conv_pred.add_module('conv_out',
                                  nn.Conv1d(prev_channel, conv_out_channel, 1))

jshilong's avatar
jshilong committed
124
125
126
127
128
129
130
131
132
    @property
    def sample_mode(self):
        if self.training:
            sample_mode = self.train_cfg.sample_mode
        else:
            sample_mode = self.test_cfg.sample_mode
        assert sample_mode in ['vote', 'seed', 'random']
        return sample_mode

133
    def forward(self, feats_dict: dict) -> dict:
encore-zhou's avatar
encore-zhou committed
134
135
136
137
        """Forward pass.

        Args:
            feats_dict (dict): Feature dict from backbone.
jshilong's avatar
jshilong committed
138

encore-zhou's avatar
encore-zhou committed
139
140
141
142

        Returns:
            dict: Predictions of primitive head.
        """
jshilong's avatar
jshilong committed
143
        sample_mode = self.sample_mode
encore-zhou's avatar
encore-zhou committed
144
145
146
147
148
149
150
151
152
153
154

        seed_points = feats_dict['fp_xyz_net0'][-1]
        seed_features = feats_dict['hd_feature']
        results = {}

        primitive_flag = self.flag_conv(seed_features)
        primitive_flag = self.flag_pred(primitive_flag)

        results['pred_flag_' + self.primitive_mode] = primitive_flag

        # 1. generate vote_points from seed_points
155
156
        vote_points, vote_features, _ = self.vote_module(
            seed_points, seed_features)
encore-zhou's avatar
encore-zhou committed
157
158
159
160
        results['vote_' + self.primitive_mode] = vote_points
        results['vote_features_' + self.primitive_mode] = vote_features

        # 2. aggregate vote_points
jshilong's avatar
jshilong committed
161
        if sample_mode == 'vote':
encore-zhou's avatar
encore-zhou committed
162
163
            # use fps in vote_aggregation
            sample_indices = None
jshilong's avatar
jshilong committed
164
        elif sample_mode == 'seed':
encore-zhou's avatar
encore-zhou committed
165
166
167
            # FPS on seed and choose the votes corresponding to the seeds
            sample_indices = furthest_point_sample(seed_points,
                                                   self.num_proposal)
jshilong's avatar
jshilong committed
168
        elif sample_mode == 'random':
encore-zhou's avatar
encore-zhou committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
            # Random sampling from the votes
            batch_size, num_seed = seed_points.shape[:2]
            sample_indices = torch.randint(
                0,
                num_seed, (batch_size, self.num_proposal),
                dtype=torch.int32,
                device=seed_points.device)
        else:
            raise NotImplementedError('Unsupported sample mod!')

        vote_aggregation_ret = self.vote_aggregation(vote_points,
                                                     vote_features,
                                                     sample_indices)
        aggregated_points, features, aggregated_indices = vote_aggregation_ret
        results['aggregated_points_' + self.primitive_mode] = aggregated_points
        results['aggregated_features_' + self.primitive_mode] = features
        results['aggregated_indices_' +
                self.primitive_mode] = aggregated_indices

        # 3. predict primitive offsets and semantic information
        predictions = self.conv_pred(features)

        # 4. decode predictions
        decode_ret = self.primitive_decode_scores(predictions,
                                                  aggregated_points)
        results.update(decode_ret)

        center, pred_ind = self.get_primitive_center(
            primitive_flag, decode_ret['center_' + self.primitive_mode])

        results['pred_' + self.primitive_mode + '_ind'] = pred_ind
        results['pred_' + self.primitive_mode + '_center'] = center
        return results

jshilong's avatar
jshilong committed
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    def loss(self, points: List[torch.Tensor], feats_dict: Dict[str,
                                                                torch.Tensor],
             batch_data_samples: List[Det3DDataSample], **kwargs) -> dict:
        """
        Args:
            points (list[tensor]): Points cloud of multiple samples.
            feats_dict (dict): Predictions from backbone or FPN.
            batch_data_samples (list[:obj:`Det3DDataSample`]): Each item
                contains the meta information of each sample and
                corresponding annotations.

        Returns:
            dict:  A dictionary of loss components.
        """
        preds = self(feats_dict)
        feats_dict.update(preds)

        batch_gt_instance_3d = []
        batch_gt_instances_ignore = []
        batch_input_metas = []
        batch_pts_semantic_mask = []
        batch_pts_instance_mask = []
        for data_sample in batch_data_samples:
            batch_input_metas.append(data_sample.metainfo)
            batch_gt_instance_3d.append(data_sample.gt_instances_3d)
            batch_gt_instances_ignore.append(
                data_sample.get('ignored_instances', None))
            batch_pts_semantic_mask.append(
                data_sample.gt_pts_seg.get('pts_semantic_mask', None))
            batch_pts_instance_mask.append(
                data_sample.gt_pts_seg.get('pts_instance_mask', None))

        loss_inputs = (points, feats_dict, batch_gt_instance_3d)
        losses = self.loss_by_feat(
            *loss_inputs,
            batch_pts_semantic_mask=batch_pts_semantic_mask,
            batch_pts_instance_mask=batch_pts_instance_mask,
            batch_gt_instances_ignore=batch_gt_instances_ignore,
        )
        return losses

    def loss_by_feat(
            self,
            points: List[torch.Tensor],
            feats_dict: dict,
            batch_gt_instances_3d: List[InstanceData],
            batch_pts_semantic_mask: Optional[List[torch.Tensor]] = None,
            batch_pts_instance_mask: Optional[List[torch.Tensor]] = None,
            **kwargs):
encore-zhou's avatar
encore-zhou committed
252
253
254
255
        """Compute loss.

        Args:
            points (list[torch.Tensor]): Input points.
jshilong's avatar
jshilong committed
256
257
258
259
260
261
            feats_dict (dict): Predictions of previous modules.
            batch_gt_instances_3d (list[:obj:`InstanceData`]): Batch of
                gt_instances. It usually includes ``bboxes`` and ``labels``
                attributes.
            batch_pts_semantic_mask (list[tensor]): Semantic mask
                of points cloud. Defaults to None.
262
            batch_pts_instance_mask (list[tensor]): Instance mask
jshilong's avatar
jshilong committed
263
                of points cloud. Defaults to None.
encore-zhou's avatar
encore-zhou committed
264
265
266
267

        Returns:
            dict: Losses of Primitive Head.
        """
jshilong's avatar
jshilong committed
268
269
270
271

        targets = self.get_targets(points, feats_dict, batch_gt_instances_3d,
                                   batch_pts_semantic_mask,
                                   batch_pts_instance_mask)
encore-zhou's avatar
encore-zhou committed
272
273
274
275
276
277

        (point_mask, point_offset, gt_primitive_center, gt_primitive_semantic,
         gt_sem_cls_label, gt_primitive_mask) = targets

        losses = {}
        # Compute the loss of primitive existence flag
jshilong's avatar
jshilong committed
278
279
        pred_flag = feats_dict['pred_flag_' + self.primitive_mode]
        flag_loss = self.loss_objectness(pred_flag, gt_primitive_mask.long())
encore-zhou's avatar
encore-zhou committed
280
281
282
283
        losses['flag_loss_' + self.primitive_mode] = flag_loss

        # calculate vote loss
        vote_loss = self.vote_module.get_loss(
jshilong's avatar
jshilong committed
284
285
286
            feats_dict['seed_points'],
            feats_dict['vote_' + self.primitive_mode],
            feats_dict['seed_indices'], point_mask, point_offset)
encore-zhou's avatar
encore-zhou committed
287
288
        losses['vote_loss_' + self.primitive_mode] = vote_loss

jshilong's avatar
jshilong committed
289
        num_proposal = feats_dict['aggregated_points_' +
encore-zhou's avatar
encore-zhou committed
290
                                  self.primitive_mode].shape[1]
jshilong's avatar
jshilong committed
291
        primitive_center = feats_dict['center_' + self.primitive_mode]
encore-zhou's avatar
encore-zhou committed
292
        if self.primitive_mode != 'line':
jshilong's avatar
jshilong committed
293
            primitive_semantic = feats_dict['size_residuals_' +
encore-zhou's avatar
encore-zhou committed
294
295
296
                                            self.primitive_mode].contiguous()
        else:
            primitive_semantic = None
jshilong's avatar
jshilong committed
297
        semancitc_scores = feats_dict['sem_cls_scores_' +
encore-zhou's avatar
encore-zhou committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
                                      self.primitive_mode].transpose(2, 1)

        gt_primitive_mask = gt_primitive_mask / \
            (gt_primitive_mask.sum() + 1e-6)
        center_loss, size_loss, sem_cls_loss = self.compute_primitive_loss(
            primitive_center, primitive_semantic, semancitc_scores,
            num_proposal, gt_primitive_center, gt_primitive_semantic,
            gt_sem_cls_label, gt_primitive_mask)
        losses['center_loss_' + self.primitive_mode] = center_loss
        losses['size_loss_' + self.primitive_mode] = size_loss
        losses['sem_loss_' + self.primitive_mode] = sem_cls_loss

        return losses

jshilong's avatar
jshilong committed
312
313
314
315
316
317
318
319
    def get_targets(
        self,
        points,
        bbox_preds: Optional[dict] = None,
        batch_gt_instances_3d: List[InstanceData] = None,
        batch_pts_semantic_mask: List[torch.Tensor] = None,
        batch_pts_instance_mask: List[torch.Tensor] = None,
    ):
encore-zhou's avatar
encore-zhou committed
320
321
322
323
        """Generate targets of primitive head.

        Args:
            points (list[torch.Tensor]): Points of each batch.
jshilong's avatar
jshilong committed
324
325
326
327
328
329
330
331
332
            bbox_preds (torch.Tensor): Bounding box predictions of
                primitive head.
            batch_gt_instances_3d (list[:obj:`InstanceData`]): Batch of
                gt_instances. It usually includes ``bboxes_3d`` and
                ``labels_3d`` attributes.
            batch_pts_semantic_mask (list[tensor]): Semantic gt mask for
                multiple images.
            batch_pts_instance_mask (list[tensor]): Instance gt mask for
                multiple images.
encore-zhou's avatar
encore-zhou committed
333
334
335
336

        Returns:
            tuple[torch.Tensor]: Targets of primitive head.
        """
jshilong's avatar
jshilong committed
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
        batch_gt_labels_3d = [
            gt_instances_3d.labels_3d
            for gt_instances_3d in batch_gt_instances_3d
        ]
        batch_gt_bboxes_3d = [
            gt_instances_3d.bboxes_3d
            for gt_instances_3d in batch_gt_instances_3d
        ]
        for index in range(len(batch_gt_labels_3d)):
            if len(batch_gt_labels_3d[index]) == 0:
                fake_box = batch_gt_bboxes_3d[index].tensor.new_zeros(
                    1, batch_gt_bboxes_3d[index].tensor.shape[-1])
                batch_gt_bboxes_3d[index] = batch_gt_bboxes_3d[index].new_box(
                    fake_box)
                batch_gt_labels_3d[index] = batch_gt_labels_3d[
                    index].new_zeros(1)

        if batch_pts_semantic_mask is None:
            batch_pts_semantic_mask = [
                None for _ in range(len(batch_gt_labels_3d))
            ]
            batch_pts_instance_mask = [
                None for _ in range(len(batch_gt_labels_3d))
            ]
encore-zhou's avatar
encore-zhou committed
361
362
363

        (point_mask, point_sem,
         point_offset) = multi_apply(self.get_targets_single, points,
jshilong's avatar
jshilong committed
364
365
366
                                     batch_gt_bboxes_3d, batch_gt_labels_3d,
                                     batch_pts_semantic_mask,
                                     batch_pts_instance_mask)
encore-zhou's avatar
encore-zhou committed
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396

        point_mask = torch.stack(point_mask)
        point_sem = torch.stack(point_sem)
        point_offset = torch.stack(point_offset)

        batch_size = point_mask.shape[0]
        num_proposal = bbox_preds['aggregated_points_' +
                                  self.primitive_mode].shape[1]
        num_seed = bbox_preds['seed_points'].shape[1]
        seed_inds = bbox_preds['seed_indices'].long()
        seed_inds_expand = seed_inds.view(batch_size, num_seed,
                                          1).repeat(1, 1, 3)
        seed_gt_votes = torch.gather(point_offset, 1, seed_inds_expand)
        seed_gt_votes += bbox_preds['seed_points']
        gt_primitive_center = seed_gt_votes.view(batch_size * num_proposal, 1,
                                                 3)

        seed_inds_expand_sem = seed_inds.view(batch_size, num_seed, 1).repeat(
            1, 1, 4 + self.num_dims)
        seed_gt_sem = torch.gather(point_sem, 1, seed_inds_expand_sem)
        gt_primitive_semantic = seed_gt_sem[:, :, 3:3 + self.num_dims].view(
            batch_size * num_proposal, 1, self.num_dims).contiguous()

        gt_sem_cls_label = seed_gt_sem[:, :, -1].long()

        gt_votes_mask = torch.gather(point_mask, 1, seed_inds)

        return (point_mask, point_offset, gt_primitive_center,
                gt_primitive_semantic, gt_sem_cls_label, gt_votes_mask)

397
398
399
400
401
402
403
    def get_targets_single(
            self,
            points: torch.Tensor,
            gt_bboxes_3d: BaseInstance3DBoxes,
            gt_labels_3d: torch.Tensor,
            pts_semantic_mask: torch.Tensor = None,
            pts_instance_mask: torch.Tensor = None) -> Tuple[torch.Tensor]:
encore-zhou's avatar
encore-zhou committed
404
405
406
407
        """Generate targets of primitive head for single batch.

        Args:
            points (torch.Tensor): Points of each batch.
408
            gt_bboxes_3d (:obj:`BaseInstance3DBoxes`): Ground truth
encore-zhou's avatar
encore-zhou committed
409
410
                boxes of each batch.
            gt_labels_3d (torch.Tensor): Labels of each batch.
411
            pts_semantic_mask (torch.Tensor): Point-wise semantic
encore-zhou's avatar
encore-zhou committed
412
                label of each batch.
413
            pts_instance_mask (torch.Tensor): Point-wise instance
encore-zhou's avatar
encore-zhou committed
414
415
416
417
418
419
420
421
422
423
424
425
426
427
                label of each batch.

        Returns:
            tuple[torch.Tensor]: Targets of primitive head.
        """
        gt_bboxes_3d = gt_bboxes_3d.to(points.device)
        num_points = points.shape[0]

        point_mask = points.new_zeros(num_points)
        # Offset to the primitive center
        point_offset = points.new_zeros([num_points, 3])
        # Semantic information of primitive center
        point_sem = points.new_zeros([num_points, 3 + self.num_dims + 1])

428
429
        # Generate pts_semantic_mask and pts_instance_mask when they are None
        if pts_semantic_mask is None or pts_instance_mask is None:
430
            points2box_mask = gt_bboxes_3d.points_in_boxes_all(points)
431
432
433
434
435
436
437
438
439
440
441
            assignment = points2box_mask.argmax(1)
            background_mask = points2box_mask.max(1)[0] == 0

            if pts_semantic_mask is None:
                pts_semantic_mask = gt_labels_3d[assignment]
                pts_semantic_mask[background_mask] = self.num_classes

            if pts_instance_mask is None:
                pts_instance_mask = assignment
                pts_instance_mask[background_mask] = gt_labels_3d.shape[0]

encore-zhou's avatar
encore-zhou committed
442
        instance_flag = torch.nonzero(
Wenhao Wu's avatar
Wenhao Wu committed
443
            pts_semantic_mask != self.num_classes, as_tuple=False).squeeze(1)
encore-zhou's avatar
encore-zhou committed
444
445
        instance_labels = pts_instance_mask[instance_flag].unique()

446
        with_yaw = gt_bboxes_3d.with_yaw
encore-zhou's avatar
encore-zhou committed
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        for i, i_instance in enumerate(instance_labels):
            indices = instance_flag[pts_instance_mask[instance_flag] ==
                                    i_instance]
            coords = points[indices, :3]
            cur_cls_label = pts_semantic_mask[indices][0]

            # Bbox Corners
            cur_corners = gt_bboxes_3d.corners[i]

            plane_lower_temp = points.new_tensor(
                [0, 0, 1, -cur_corners[7, -1]])
            upper_points = cur_corners[[1, 2, 5, 6]]
            refined_distance = (upper_points * plane_lower_temp[:3]).sum(dim=1)

            if self.check_horizon(upper_points) and \
                    plane_lower_temp[0] + plane_lower_temp[1] < \
                    self.train_cfg['lower_thresh']:
                plane_lower = points.new_tensor(
                    [0, 0, 1, plane_lower_temp[-1]])
                plane_upper = points.new_tensor(
                    [0, 0, 1, -torch.mean(refined_distance)])
            else:
                raise NotImplementedError('Only horizontal plane is support!')

            if self.check_dist(plane_upper, upper_points) is False:
                raise NotImplementedError(
                    'Mean distance to plane should be lower than thresh!')

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_lower, coords)

479
            # Get bottom four lines
encore-zhou's avatar
encore-zhou committed
480
481
            if self.primitive_mode == 'line':
                point2line_matching = self.match_point2line(
482
                    coords[selected], cur_corners, with_yaw, mode='bottom')
encore-zhou's avatar
encore-zhou committed
483
484
485
486
487
488
489
490
491
492

                point_mask, point_offset, point_sem = \
                    self._assign_primitive_line_targets(point_mask,
                                                        point_offset,
                                                        point_sem,
                                                        coords[selected],
                                                        indices[selected],
                                                        cur_cls_label,
                                                        point2line_matching,
                                                        cur_corners,
493
494
495
                                                        [1, 1, 0, 0],
                                                        with_yaw,
                                                        mode='bottom')
encore-zhou's avatar
encore-zhou committed
496
497
498
499
500
501
502
503
504
505
506
507
508
509

            # Set the surface labels here
            if self.primitive_mode == 'z' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    point2plane_dist[selected].var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
                    self._assign_primitive_surface_targets(point_mask,
                                                           point_offset,
                                                           point_sem,
                                                           coords[selected],
                                                           indices[selected],
                                                           cur_cls_label,
510
511
512
                                                           cur_corners,
                                                           with_yaw,
                                                           mode='bottom')
encore-zhou's avatar
encore-zhou committed
513
514
515
516
517

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_upper, coords)

518
            # Get top four lines
encore-zhou's avatar
encore-zhou committed
519
520
            if self.primitive_mode == 'line':
                point2line_matching = self.match_point2line(
521
                    coords[selected], cur_corners, with_yaw, mode='top')
encore-zhou's avatar
encore-zhou committed
522
523
524
525
526
527
528
529
530
531

                point_mask, point_offset, point_sem = \
                    self._assign_primitive_line_targets(point_mask,
                                                        point_offset,
                                                        point_sem,
                                                        coords[selected],
                                                        indices[selected],
                                                        cur_cls_label,
                                                        point2line_matching,
                                                        cur_corners,
532
533
534
                                                        [1, 1, 0, 0],
                                                        with_yaw,
                                                        mode='top')
encore-zhou's avatar
encore-zhou committed
535
536
537
538
539
540
541
542
543
544
545
546
547

            if self.primitive_mode == 'z' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    point2plane_dist[selected].var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
                    self._assign_primitive_surface_targets(point_mask,
                                                           point_offset,
                                                           point_sem,
                                                           coords[selected],
                                                           indices[selected],
                                                           cur_cls_label,
548
549
550
                                                           cur_corners,
                                                           with_yaw,
                                                           mode='top')
encore-zhou's avatar
encore-zhou committed
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574

            # Get left two lines
            plane_left_temp = self._get_plane_fomulation(
                cur_corners[2] - cur_corners[3],
                cur_corners[3] - cur_corners[0], cur_corners[0])

            right_points = cur_corners[[4, 5, 7, 6]]
            plane_left_temp /= torch.norm(plane_left_temp[:3])
            refined_distance = (right_points * plane_left_temp[:3]).sum(dim=1)

            if plane_left_temp[2] < self.train_cfg['lower_thresh']:
                plane_left = plane_left_temp
                plane_right = points.new_tensor([
                    plane_left_temp[0], plane_left_temp[1], plane_left_temp[2],
                    -refined_distance.mean()
                ])
            else:
                raise NotImplementedError(
                    'Normal vector of the plane should be horizontal!')

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_left, coords)

575
            # Get left four lines
encore-zhou's avatar
encore-zhou committed
576
            if self.primitive_mode == 'line':
577
578
                point2line_matching = self.match_point2line(
                    coords[selected], cur_corners, with_yaw, mode='left')
encore-zhou's avatar
encore-zhou committed
579
                point_mask, point_offset, point_sem = \
580
581
582
583
584
                    self._assign_primitive_line_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        point2line_matching[2:], cur_corners, [2, 2],
                        with_yaw, mode='left')
encore-zhou's avatar
encore-zhou committed
585
586
587
588
589
590
591

            if self.primitive_mode == 'xy' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    point2plane_dist[selected].var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
592
593
594
595
                    self._assign_primitive_surface_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        cur_corners, with_yaw, mode='left')
encore-zhou's avatar
encore-zhou committed
596
597
598
599
600

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_right, coords)

601
            # Get right four lines
encore-zhou's avatar
encore-zhou committed
602
            if self.primitive_mode == 'line':
603
604
                point2line_matching = self.match_point2line(
                    coords[selected], cur_corners, with_yaw, mode='right')
encore-zhou's avatar
encore-zhou committed
605
606

                point_mask, point_offset, point_sem = \
607
608
609
610
611
                    self._assign_primitive_line_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        point2line_matching[2:], cur_corners, [2, 2],
                        with_yaw, mode='right')
encore-zhou's avatar
encore-zhou committed
612
613
614
615
616
617
618

            if self.primitive_mode == 'xy' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    point2plane_dist[selected].var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
619
620
621
622
                    self._assign_primitive_surface_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        cur_corners, with_yaw, mode='right')
encore-zhou's avatar
encore-zhou committed
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651

            plane_front_temp = self._get_plane_fomulation(
                cur_corners[0] - cur_corners[4],
                cur_corners[4] - cur_corners[5], cur_corners[5])

            back_points = cur_corners[[3, 2, 7, 6]]
            plane_front_temp /= torch.norm(plane_front_temp[:3])
            refined_distance = (back_points * plane_front_temp[:3]).sum(dim=1)

            if plane_front_temp[2] < self.train_cfg['lower_thresh']:
                plane_front = plane_front_temp
                plane_back = points.new_tensor([
                    plane_front_temp[0], plane_front_temp[1],
                    plane_front_temp[2], -torch.mean(refined_distance)
                ])
            else:
                raise NotImplementedError(
                    'Normal vector of the plane should be horizontal!')

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_front, coords)

            if self.primitive_mode == 'xy' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    (point2plane_dist[selected]).var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
652
653
654
655
                    self._assign_primitive_surface_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        cur_corners, with_yaw, mode='front')
encore-zhou's avatar
encore-zhou committed
656
657
658
659
660
661
662
663
664
665
666

            # Get the boundary points here
            point2plane_dist, selected = self.match_point2plane(
                plane_back, coords)

            if self.primitive_mode == 'xy' and \
                    selected.sum() > self.train_cfg['num_point'] and \
                    point2plane_dist[selected].var() < \
                    self.train_cfg['var_thresh']:

                point_mask, point_offset, point_sem = \
667
668
669
670
                    self._assign_primitive_surface_targets(
                        point_mask, point_offset, point_sem,
                        coords[selected], indices[selected], cur_cls_label,
                        cur_corners, with_yaw, mode='back')
encore-zhou's avatar
encore-zhou committed
671
672
673

        return (point_mask, point_sem, point_offset)

674
675
    def primitive_decode_scores(self, predictions: torch.Tensor,
                                aggregated_points: torch.Tensor) -> dict:
encore-zhou's avatar
encore-zhou committed
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
        """Decode predicted parts to primitive head.

        Args:
            predictions (torch.Tensor): primitive pridictions of each batch.
            aggregated_points (torch.Tensor): The aggregated points
                of vote stage.

        Returns:
            Dict: Predictions of primitive head, including center,
                semantic size and semantic scores.
        """

        ret_dict = {}
        pred_transposed = predictions.transpose(2, 1)

        center = aggregated_points + pred_transposed[:, :, 0:3]
        ret_dict['center_' + self.primitive_mode] = center

        if self.primitive_mode in ['z', 'xy']:
            ret_dict['size_residuals_' + self.primitive_mode] = \
                pred_transposed[:, :, 3:3 + self.num_dims]

        ret_dict['sem_cls_scores_' + self.primitive_mode] = \
            pred_transposed[:, :, 3 + self.num_dims:]

        return ret_dict

703
    def check_horizon(self, points: torch.Tensor) -> bool:
encore-zhou's avatar
encore-zhou committed
704
705
706
707
708
709
710
711
712
713
714
715
        """Check whether is a horizontal plane.

        Args:
            points (torch.Tensor): Points of input.

        Returns:
            Bool: Flag of result.
        """
        return (points[0][-1] == points[1][-1]) and \
               (points[1][-1] == points[2][-1]) and \
               (points[2][-1] == points[3][-1])

716
717
    def check_dist(self, plane_equ: torch.Tensor,
                   points: torch.Tensor) -> tuple:
encore-zhou's avatar
encore-zhou committed
718
719
720
721
722
723
724
725
726
727
728
729
        """Whether the mean of points to plane distance is lower than thresh.

        Args:
            plane_equ (torch.Tensor): Plane to be checked.
            points (torch.Tensor): Points to be checked.

        Returns:
            Tuple: Flag of result.
        """
        return (points[:, 2] +
                plane_equ[-1]).sum() / 4.0 < self.train_cfg['lower_thresh']

730
731
    def point2line_dist(self, points: torch.Tensor, pts_a: torch.Tensor,
                        pts_b: torch.Tensor) -> torch.Tensor:
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
        """Calculate the distance from point to line.

        Args:
            points (torch.Tensor): Points of input.
            pts_a (torch.Tensor): Point on the specific line.
            pts_b (torch.Tensor): Point on the specific line.

        Returns:
            torch.Tensor: Distance between each point to line.
        """
        line_a2b = pts_b - pts_a
        line_a2pts = points - pts_a
        length = (line_a2pts * line_a2b.view(1, 3)).sum(1) / \
            line_a2b.norm()
        dist = (line_a2pts.norm(dim=1)**2 - length**2).sqrt()

        return dist

750
751
752
753
754
    def match_point2line(self,
                         points: torch.Tensor,
                         corners: torch.Tensor,
                         with_yaw: bool,
                         mode: str = 'bottom') -> tuple:
encore-zhou's avatar
encore-zhou committed
755
756
757
758
        """Match points to corresponding line.

        Args:
            points (torch.Tensor): Points of input.
759
760
761
762
763
            corners (torch.Tensor): Eight corners of a bounding box.
            with_yaw (Bool): Whether the boundind box is with rotation.
            mode (str, optional): Specify which line should be matched,
                available mode are ('bottom', 'top', 'left', 'right').
                Defaults to 'bottom'.
encore-zhou's avatar
encore-zhou committed
764
765
766
767

        Returns:
            Tuple: Flag of matching correspondence.
        """
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
        if with_yaw:
            corners_pair = {
                'bottom': [[0, 3], [4, 7], [0, 4], [3, 7]],
                'top': [[1, 2], [5, 6], [1, 5], [2, 6]],
                'left': [[0, 1], [3, 2], [0, 1], [3, 2]],
                'right': [[4, 5], [7, 6], [4, 5], [7, 6]]
            }
            selected_list = []
            for pair_index in corners_pair[mode]:
                selected = self.point2line_dist(
                    points, corners[pair_index[0]], corners[pair_index[1]]) \
                    < self.train_cfg['line_thresh']
                selected_list.append(selected)
        else:
            xmin, ymin, _ = corners.min(0)[0]
            xmax, ymax, _ = corners.max(0)[0]
            sel1 = torch.abs(points[:, 0] -
                             xmin) < self.train_cfg['line_thresh']
            sel2 = torch.abs(points[:, 0] -
                             xmax) < self.train_cfg['line_thresh']
            sel3 = torch.abs(points[:, 1] -
                             ymin) < self.train_cfg['line_thresh']
            sel4 = torch.abs(points[:, 1] -
                             ymax) < self.train_cfg['line_thresh']
            selected_list = [sel1, sel2, sel3, sel4]
        return selected_list
encore-zhou's avatar
encore-zhou committed
794

795
796
    def match_point2plane(self, plane: torch.Tensor,
                          points: torch.Tensor) -> tuple:
encore-zhou's avatar
encore-zhou committed
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
        """Match points to plane.

        Args:
            plane (torch.Tensor): Equation of the plane.
            points (torch.Tensor): Points of input.

        Returns:
            Tuple: Distance of each point to the plane and
                flag of matching correspondence.
        """
        point2plane_dist = torch.abs((points * plane[:3]).sum(dim=1) +
                                     plane[-1])
        min_dist = point2plane_dist.min()
        selected = torch.abs(point2plane_dist -
                             min_dist) < self.train_cfg['dist_thresh']
        return point2plane_dist, selected

814
815
816
817
818
819
820
821
    def compute_primitive_loss(self, primitive_center: torch.Tensor,
                               primitive_semantic: torch.Tensor,
                               semantic_scores: torch.Tensor,
                               num_proposal: torch.Tensor,
                               gt_primitive_center: torch.Tensor,
                               gt_primitive_semantic: torch.Tensor,
                               gt_sem_cls_label: torch.Tensor,
                               gt_primitive_mask: torch.Tensor) -> Tuple:
encore-zhou's avatar
encore-zhou committed
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
        """Compute loss of primitive module.

        Args:
            primitive_center (torch.Tensor): Pridictions of primitive center.
            primitive_semantic (torch.Tensor): Pridictions of primitive
                semantic.
            semantic_scores (torch.Tensor): Pridictions of primitive
                semantic scores.
            num_proposal (int): The number of primitive proposal.
            gt_primitive_center (torch.Tensor): Ground truth of
                primitive center.
            gt_votes_sem (torch.Tensor): Ground truth of primitive semantic.
            gt_sem_cls_label (torch.Tensor): Ground truth of primitive
                semantic class.
            gt_primitive_mask (torch.Tensor): Ground truth of primitive mask.

        Returns:
            Tuple: Loss of primitive module.
        """
        batch_size = primitive_center.shape[0]
        vote_xyz_reshape = primitive_center.view(batch_size * num_proposal, -1,
                                                 3)

jshilong's avatar
jshilong committed
845
        center_loss = self.loss_center(
encore-zhou's avatar
encore-zhou committed
846
847
848
849
850
851
852
            vote_xyz_reshape,
            gt_primitive_center,
            dst_weight=gt_primitive_mask.view(batch_size * num_proposal, 1))[1]

        if self.primitive_mode != 'line':
            size_xyz_reshape = primitive_semantic.view(
                batch_size * num_proposal, -1, self.num_dims).contiguous()
jshilong's avatar
jshilong committed
853
            size_loss = self.loss_semantic_reg(
encore-zhou's avatar
encore-zhou committed
854
855
856
857
858
859
860
861
                size_xyz_reshape,
                gt_primitive_semantic,
                dst_weight=gt_primitive_mask.view(batch_size * num_proposal,
                                                  1))[1]
        else:
            size_loss = center_loss.new_tensor(0.0)

        # Semantic cls loss
jshilong's avatar
jshilong committed
862
        sem_cls_loss = self.loss_semantic_cls(
encore-zhou's avatar
encore-zhou committed
863
864
865
866
            semantic_scores, gt_sem_cls_label, weight=gt_primitive_mask)

        return center_loss, size_loss, sem_cls_loss

867
868
    def get_primitive_center(self, pred_flag: torch.Tensor,
                             center: torch.Tensor) -> Tuple:
encore-zhou's avatar
encore-zhou committed
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
        """Generate primitive center from predictions.

        Args:
            pred_flag (torch.Tensor): Scores of primitive center.
            center (torch.Tensor): Pridictions of primitive center.

        Returns:
            Tuple: Primitive center and the prediction indices.
        """
        ind_normal = F.softmax(pred_flag, dim=1)
        pred_indices = (ind_normal[:, 1, :] >
                        self.surface_thresh).detach().float()
        selected = (ind_normal[:, 1, :] <=
                    self.surface_thresh).detach().float()
        offset = torch.ones_like(center) * self.upper_thresh
        center = center + offset * selected.unsqueeze(-1)
        return center, pred_indices

887
    def _assign_primitive_line_targets(self,
888
889
890
891
892
893
894
895
896
897
898
                                       point_mask: torch.Tensor,
                                       point_offset: torch.Tensor,
                                       point_sem: torch.Tensor,
                                       coords: torch.Tensor,
                                       indices: torch.Tensor,
                                       cls_label: int,
                                       point2line_matching: torch.Tensor,
                                       corners: torch.Tensor,
                                       center_axises: torch.Tensor,
                                       with_yaw: bool,
                                       mode: str = 'bottom') -> Tuple:
encore-zhou's avatar
encore-zhou committed
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
        """Generate targets of line primitive.

        Args:
            point_mask (torch.Tensor): Tensor to store the ground
                truth of mask.
            point_offset (torch.Tensor): Tensor to store the ground
                truth of offset.
            point_sem (torch.Tensor): Tensor to store the ground
                truth of semantic.
            coords (torch.Tensor): The selected points.
            indices (torch.Tensor): Indices of the selected points.
            cls_label (int): Class label of the ground truth bounding box.
            point2line_matching (torch.Tensor): Flag indicate that
                matching line of each point.
            corners (torch.Tensor): Corners of the ground truth bounding box.
            center_axises (list[int]): Indicate in which axis the line center
                should be refined.
916
917
918
919
            with_yaw (Bool): Whether the boundind box is with rotation.
            mode (str, optional): Specify which line should be matched,
                available mode are ('bottom', 'top', 'left', 'right').
                Defaults to 'bottom'.
encore-zhou's avatar
encore-zhou committed
920
921
922
923

        Returns:
            Tuple: Targets of the line primitive.
        """
924
925
926
927
928
929
930
931
932
933
934
        corners_pair = {
            'bottom': [[0, 3], [4, 7], [0, 4], [3, 7]],
            'top': [[1, 2], [5, 6], [1, 5], [2, 6]],
            'left': [[0, 1], [3, 2]],
            'right': [[4, 5], [7, 6]]
        }
        corners_pair = corners_pair[mode]
        assert len(corners_pair) == len(point2line_matching) == len(
            center_axises)
        for line_select, center_axis, pair_index in zip(
                point2line_matching, center_axises, corners_pair):
encore-zhou's avatar
encore-zhou committed
935
936
            if line_select.sum() > self.train_cfg['num_point_line']:
                point_mask[indices[line_select]] = 1.0
937
938
939
940
941
942
943
944

                if with_yaw:
                    line_center = (corners[pair_index[0]] +
                                   corners[pair_index[1]]) / 2
                else:
                    line_center = coords[line_select].mean(dim=0)
                    line_center[center_axis] = corners[:, center_axis].mean()

encore-zhou's avatar
encore-zhou committed
945
946
947
948
949
950
951
                point_offset[indices[line_select]] = \
                    line_center - coords[line_select]
                point_sem[indices[line_select]] = \
                    point_sem.new_tensor([line_center[0], line_center[1],
                                          line_center[2], cls_label])
        return point_mask, point_offset, point_sem

952
    def _assign_primitive_surface_targets(self,
953
954
955
956
957
958
959
960
961
                                          point_mask: torch.Tensor,
                                          point_offset: torch.Tensor,
                                          point_sem: torch.Tensor,
                                          coords: torch.Tensor,
                                          indices: torch.Tensor,
                                          cls_label: int,
                                          corners: torch.Tensor,
                                          with_yaw: bool,
                                          mode: str = 'bottom') -> Tuple:
encore-zhou's avatar
encore-zhou committed
962
963
964
965
966
967
968
969
970
971
972
973
974
        """Generate targets for primitive z and primitive xy.

        Args:
            point_mask (torch.Tensor): Tensor to store the ground
                truth of mask.
            point_offset (torch.Tensor): Tensor to store the ground
                truth of offset.
            point_sem (torch.Tensor): Tensor to store the ground
                truth of semantic.
            coords (torch.Tensor): The selected points.
            indices (torch.Tensor): Indices of the selected points.
            cls_label (int): Class label of the ground truth bounding box.
            corners (torch.Tensor): Corners of the ground truth bounding box.
975
976
977
978
979
            with_yaw (Bool): Whether the boundind box is with rotation.
            mode (str, optional): Specify which line should be matched,
                available mode are ('bottom', 'top', 'left', 'right',
                'front', 'back').
                Defaults to 'bottom'.
encore-zhou's avatar
encore-zhou committed
980
981
982
983
984

        Returns:
            Tuple: Targets of the center primitive.
        """
        point_mask[indices] = 1.0
985
986
987
988
989
990
991
992
993
        corners_pair = {
            'bottom': [0, 7],
            'top': [1, 6],
            'left': [0, 1],
            'right': [4, 5],
            'front': [0, 1],
            'back': [3, 2]
        }
        pair_index = corners_pair[mode]
encore-zhou's avatar
encore-zhou committed
994
        if self.primitive_mode == 'z':
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
            if with_yaw:
                center = (corners[pair_index[0]] +
                          corners[pair_index[1]]) / 2.0
                center[2] = coords[:, 2].mean()
                point_sem[indices] = point_sem.new_tensor([
                    center[0], center[1],
                    center[2], (corners[4] - corners[0]).norm(),
                    (corners[3] - corners[0]).norm(), cls_label
                ])
            else:
                center = point_mask.new_tensor([
                    corners[:, 0].mean(), corners[:, 1].mean(),
                    coords[:, 2].mean()
                ])
                point_sem[indices] = point_sem.new_tensor([
                    center[0], center[1], center[2],
                    corners[:, 0].max() - corners[:, 0].min(),
                    corners[:, 1].max() - corners[:, 1].min(), cls_label
                ])
encore-zhou's avatar
encore-zhou committed
1014
        elif self.primitive_mode == 'xy':
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
            if with_yaw:
                center = coords.mean(0)
                center[2] = (corners[pair_index[0], 2] +
                             corners[pair_index[1], 2]) / 2.0
                point_sem[indices] = point_sem.new_tensor([
                    center[0], center[1], center[2],
                    corners[pair_index[1], 2] - corners[pair_index[0], 2],
                    cls_label
                ])
            else:
                center = point_mask.new_tensor([
                    coords[:, 0].mean(), coords[:, 1].mean(),
                    corners[:, 2].mean()
                ])
                point_sem[indices] = point_sem.new_tensor([
                    center[0], center[1], center[2],
                    corners[:, 2].max() - corners[:, 2].min(), cls_label
                ])
encore-zhou's avatar
encore-zhou committed
1033
1034
1035
        point_offset[indices] = center - coords
        return point_mask, point_offset, point_sem

1036
1037
1038
    def _get_plane_fomulation(self, vector1: torch.Tensor,
                              vector2: torch.Tensor,
                              point: torch.Tensor) -> torch.Tensor:
encore-zhou's avatar
encore-zhou committed
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
        """Compute the equation of the plane.

        Args:
            vector1 (torch.Tensor): Parallel vector of the plane.
            vector2 (torch.Tensor): Parallel vector of the plane.
            point (torch.Tensor): Point on the plane.

        Returns:
            torch.Tensor: Equation of the plane.
        """
        surface_norm = torch.cross(vector1, vector2)
        surface_dis = -torch.dot(surface_norm, point)
        plane = point.new_tensor(
            [surface_norm[0], surface_norm[1], surface_norm[2], surface_dis])
        return plane