# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved import torch from torch import nn from torch.autograd import Function from torch.nn.modules.utils import _pair from .voxel_layer import dynamic_voxelize, hard_voxelize class _Voxelization(Function): @staticmethod def forward(ctx, points, voxel_size, coors_range, max_points=35, max_voxels=20000, deterministic=True): """convert kitti points(N, >=3) to voxels. Args: points: [N, ndim] float tensor. points[:, :3] contain xyz points and points[:, 3:] contain other information like reflectivity voxel_size: [3] list/tuple or array, float. xyz, indicate voxel size coors_range: [6] list/tuple or array, float. indicate voxel range. format: xyzxyz, minmax max_points: int. indicate maximum points contained in a voxel. if max_points=-1, it means using dynamic_voxelize max_voxels: int. indicate maximum voxels this function create. for second, 20000 is a good choice. Users should shuffle points before call this function because max_voxels may drop points. deterministic: bool. whether to invoke the non-deterministic version of hard-voxelization implementations. non-deterministic version is considerablly fast but is not deterministic. only affects hard voxelization. default True. for more information of this argument and the implementation insights, please refer to the following links: https://github.com/open-mmlab/mmdetection3d/issues/894 https://github.com/open-mmlab/mmdetection3d/pull/904 it is an experimental feature and we will appreciate it if you could share with us the failing cases. Returns: voxels: [M, max_points, ndim] float tensor. only contain points and returned when max_points != -1. coordinates: [M, 3] int32 tensor, always returned. num_points_per_voxel: [M] int32 tensor. Only returned when max_points != -1. """ if max_points == -1 or max_voxels == -1: coors = points.new_zeros(size=(points.size(0), 3), dtype=torch.int) dynamic_voxelize(points, coors, voxel_size, coors_range, 3) return coors else: voxels = points.new_zeros( size=(max_voxels, max_points, points.size(1))) coors = points.new_zeros(size=(max_voxels, 3), dtype=torch.int) num_points_per_voxel = points.new_zeros( size=(max_voxels, ), dtype=torch.int) voxel_num = hard_voxelize( points, voxels, coors, num_points_per_voxel, voxel_size, coors_range, max_points, max_voxels, 3, deterministic, ) # select the valid voxels voxels_out = voxels[:voxel_num] coors_out = coors[:voxel_num] num_points_per_voxel_out = num_points_per_voxel[:voxel_num] return voxels_out, coors_out, num_points_per_voxel_out voxelization = _Voxelization.apply class Voxelization(nn.Module): def __init__(self, voxel_size, point_cloud_range, max_num_points, max_voxels=20000, deterministic=True): super(Voxelization, self).__init__() """ Args: voxel_size (list): list [x, y, z] size of three dimension point_cloud_range (list): [x_min, y_min, z_min, x_max, y_max, z_max] max_num_points (int): max number of points per voxel max_voxels (tuple or int): max number of voxels in (training, testing) time deterministic: bool. whether to invoke the non-deterministic version of hard-voxelization implementations. non-deterministic version is considerablly fast but is not deterministic. only affects hard voxelization. default True. for more information of this argument and the implementation insights, please refer to the following links: https://github.com/open-mmlab/mmdetection3d/issues/894 https://github.com/open-mmlab/mmdetection3d/pull/904 it is an experimental feature and we will appreciate it if you could share with us the failing cases. """ self.voxel_size = voxel_size self.point_cloud_range = point_cloud_range self.max_num_points = max_num_points if isinstance(max_voxels, tuple): self.max_voxels = max_voxels else: self.max_voxels = _pair(max_voxels) self.deterministic = deterministic point_cloud_range = torch.tensor( point_cloud_range, dtype=torch.float32) # [0, -40, -3, 70.4, 40, 1] voxel_size = torch.tensor(voxel_size, dtype=torch.float32) grid_size = (point_cloud_range[3:] - point_cloud_range[:3]) / voxel_size grid_size = torch.round(grid_size).long() input_feat_shape = grid_size[:2] self.grid_size = grid_size # the origin shape is as [x-len, y-len, z-len] # [w, h, d] -> [d, h, w] removed self.pcd_shape = [*input_feat_shape, 1] # [::-1] def forward(self, input): """ Args: input: NC points """ if self.training: max_voxels = self.max_voxels[0] else: max_voxels = self.max_voxels[1] return voxelization( input, self.voxel_size, self.point_cloud_range, self.max_num_points, max_voxels, self.deterministic, ) def __repr__(self): tmpstr = self.__class__.__name__ + '(' tmpstr += 'voxel_size=' + str(self.voxel_size) tmpstr += ', point_cloud_range=' + str(self.point_cloud_range) tmpstr += ', max_num_points=' + str(self.max_num_points) tmpstr += ', max_voxels=' + str(self.max_voxels) tmpstr += ', deterministic=' + str(self.deterministic) tmpstr += ')' return tmpstr