Skip to content

Commit 6299a1f

Browse files
[Feature]Add Minkowski block (#2528)
* add minkowski block * fix minkowski residual block * rename ut * fix test case * add doc string * fix __init__
1 parent b4f5724 commit 6299a1f

File tree

3 files changed

+238
-1
lines changed

3 files changed

+238
-1
lines changed

mmdet3d/models/layers/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from .edge_fusion_module import EdgeFusionModule
66
from .fusion_layers import (PointFusion, VoteFusion, apply_3d_transformation,
77
bbox_2d_transform, coord_2d_transform)
8+
from .minkowski_engine_block import (MinkowskiBasicBlock, MinkowskiBottleneck,
9+
MinkowskiConvModule)
810
from .mlp import MLP
911
from .norm import NaiveSyncBatchNorm1d, NaiveSyncBatchNorm2d
1012
from .paconv import PAConv, PAConvCUDA
@@ -29,5 +31,6 @@
2931
'nms_normal_bev', 'build_sa_module', 'PointSAModuleMSG', 'PointSAModule',
3032
'PointFPModule', 'PAConvSAModule', 'PAConvSAModuleMSG',
3133
'PAConvCUDASAModule', 'PAConvCUDASAModuleMSG', 'TorchSparseConvModule',
32-
'TorchSparseBasicBlock', 'TorchSparseBottleneck'
34+
'TorchSparseBasicBlock', 'TorchSparseBottleneck', 'MinkowskiBasicBlock',
35+
'MinkowskiBottleneck', 'MinkowskiConvModule'
3336
]
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Copyright (c) OpenMMLab. All rights reserved.
2+
from typing import Optional, Tuple, Union
3+
4+
from mmcv.cnn import build_activation_layer, build_conv_layer, build_norm_layer
5+
from mmengine.model import BaseModule
6+
from mmengine.registry import MODELS
7+
from torch import nn
8+
9+
from mmdet3d.utils import ConfigType, OptConfigType, OptMultiConfig
10+
11+
try:
12+
from MinkowskiEngine import (MinkowskiBatchNorm, MinkowskiConvolution,
13+
MinkowskiConvolutionTranspose, MinkowskiReLU,
14+
MinkowskiSyncBatchNorm, SparseTensor)
15+
from MinkowskiEngine.modules.resnet_block import BasicBlock, Bottleneck
16+
except ImportError:
17+
SparseTensor = None
18+
from mmcv.cnn.resnet import BasicBlock, Bottleneck
19+
IS_MINKOWSKI_ENGINE_AVAILABLE = False
20+
else:
21+
MODELS._register_module(MinkowskiConvolution, 'MinkowskiConvNd')
22+
MODELS._register_module(MinkowskiConvolutionTranspose,
23+
'MinkowskiConvNdTranspose')
24+
MODELS._register_module(MinkowskiBatchNorm, 'MinkowskiBN')
25+
MODELS._register_module(MinkowskiSyncBatchNorm, 'MinkowskiSyncBN')
26+
MODELS._register_module(MinkowskiReLU, 'MinkowskiReLU')
27+
IS_MINKOWSKI_ENGINE_AVAILABLE = True
28+
29+
30+
class MinkowskiConvModule(BaseModule):
31+
"""A minkowski engine conv block that bundles conv/norm/activation layers.
32+
33+
Args:
34+
in_channels (int): In channels of block.
35+
out_channels (int): Out channels of block.
36+
kernel_size (int or Tuple[int]): Kernel_size of block.
37+
stride (int or Tuple[int]): Stride of the first block. Defaults to 1.
38+
dilation (int): Dilation of block. Defaults to 1.
39+
bias (bool): Whether to use bias in conv. Defaults to False.
40+
conv_cfg (:obj:`ConfigDict` or dict, optional): Config of conv layer.
41+
Defaults to None.
42+
norm_cfg (:obj:`ConfigDict` or dict): The config of normalization.
43+
Defaults to dict(type='MinkowskiBN').
44+
act_cfg (:obj:`ConfigDict` or dict): The config of activation.
45+
Defaults to dict(type='MinkowskiReLU', inplace=True).
46+
init_cfg (:obj:`ConfigDict` or dict, optional): Initialization config.
47+
Defaults to None.
48+
"""
49+
50+
def __init__(self,
51+
in_channels: int,
52+
out_channels: int,
53+
kernel_size: Union[int, Tuple[int, int, int]],
54+
stride: Union[int, Tuple[int, int, int]] = 1,
55+
dilation: int = 1,
56+
bias: bool = False,
57+
conv_cfg: OptConfigType = None,
58+
norm_cfg: ConfigType = dict(type='MinkowskiBN'),
59+
act_cfg: ConfigType = dict(
60+
type='MinkowskiReLU', inplace=True),
61+
init_cfg: OptMultiConfig = None,
62+
**kwargs) -> None:
63+
super().__init__(init_cfg)
64+
layers = []
65+
if conv_cfg is None:
66+
conv_cfg = dict(type='MinkowskiConvNd')
67+
conv = build_conv_layer(
68+
conv_cfg,
69+
in_channels,
70+
out_channels,
71+
kernel_size,
72+
stride,
73+
dilation,
74+
bias,
75+
dimension=3)
76+
layers.append(conv)
77+
78+
if norm_cfg is not None:
79+
_, norm = build_norm_layer(norm_cfg, out_channels)
80+
layers.append(norm)
81+
if act_cfg is not None:
82+
activation = build_activation_layer(act_cfg)
83+
layers.append(activation)
84+
self.net = nn.Sequential(*layers)
85+
86+
def forward(self, x: SparseTensor) -> SparseTensor:
87+
out = self.net(x)
88+
return out
89+
90+
91+
class MinkowskiBasicBlock(BasicBlock, BaseModule):
92+
"""A wrapper of minkowski engine basic block. It inherits from mmengine's
93+
`BaseModule` and allows additional keyword arguments.
94+
95+
Args:
96+
inplanes (int): In channels of block.
97+
planes (int): Out channels of block.
98+
stride (int or Tuple[int]): Stride of the first conv. Defaults to 1.
99+
dilation (int): Dilation of block. Defaults to 1.
100+
downsample (nn.Module, optional): Residual branch conv module if
101+
necessary. Defaults to None.
102+
bn_momentum (float): Momentum of batch norm layer. Defaults to 0.1.
103+
dimension (int): Dimension of minkowski convolution. Defaults to 3.
104+
init_cfg (:obj:`ConfigDict` or dict, optional): Initialization config.
105+
Defaults to None.
106+
"""
107+
108+
def __init__(self,
109+
inplanes: int,
110+
planes: int,
111+
stride: int = 1,
112+
dilation: int = 1,
113+
downsample: Optional[nn.Module] = None,
114+
bn_momentum: float = 0.1,
115+
dimension: int = 3,
116+
init_cfg: OptConfigType = None,
117+
**kwargs):
118+
BaseModule.__init__(self, init_cfg)
119+
BasicBlock.__init__(
120+
self,
121+
inplanes,
122+
planes,
123+
stride=stride,
124+
dilation=dilation,
125+
downsample=downsample,
126+
bn_momentum=bn_momentum,
127+
dimension=dimension)
128+
129+
130+
class MinkowskiBottleneck(Bottleneck, BaseModule):
131+
"""A wrapper of minkowski engine bottleneck block. It inherits from
132+
mmengine's `BaseModule` and allows additional keyword arguments.
133+
134+
Args:
135+
inplanes (int): In channels of block.
136+
planes (int): Out channels of block.
137+
stride (int or Tuple[int]): Stride of the second conv. Defaults to 1.
138+
dilation (int): Dilation of block. Defaults to 1.
139+
downsample (nn.Module, optional): Residual branch conv module if
140+
necessary. Defaults to None.
141+
bn_momentum (float): Momentum of batch norm layer. Defaults to 0.1.
142+
dimension (int): Dimension of minkowski convolution. Defaults to 3.
143+
init_cfg (:obj:`ConfigDict` or dict, optional): Initialization config.
144+
Defaults to None.
145+
"""
146+
147+
def __init__(self,
148+
inplanes: int,
149+
planes: int,
150+
stride: int = 1,
151+
dilation: int = 1,
152+
downsample: Optional[nn.Module] = None,
153+
bn_momentum: float = 0.1,
154+
dimension: int = 3,
155+
init_cfg: OptConfigType = None,
156+
**kwargs):
157+
BaseModule.__init__(self, init_cfg)
158+
Bottleneck.__init__(
159+
self,
160+
inplanes,
161+
planes,
162+
stride=stride,
163+
dilation=dilation,
164+
downsample=downsample,
165+
bn_momentum=bn_momentum,
166+
dimension=dimension)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright (c) OpenMMLab. All rights reserved.
2+
import pytest
3+
import torch
4+
5+
from mmdet3d.models.layers.minkowski_engine_block import \
6+
IS_MINKOWSKI_ENGINE_AVAILABLE
7+
8+
if IS_MINKOWSKI_ENGINE_AVAILABLE:
9+
from MinkowskiEngine import SparseTensor
10+
11+
from mmdet3d.models.layers.minkowski_engine_block import (
12+
MinkowskiBasicBlock, MinkowskiBottleneck, MinkowskiConvModule)
13+
else:
14+
pytest.skip('test requires Minkowski Engine.', allow_module_level=True)
15+
16+
17+
def test_MinkowskiConvModule():
18+
if not torch.cuda.is_available():
19+
pytest.skip('test requires GPU and torch+cuda')
20+
voxel_features = torch.tensor(
21+
[[6.56126, 0.9648336, -1.7339306, 0.315],
22+
[6.8162713, -2.480431, -1.3616394, 0.36],
23+
[11.643568, -4.744306, -1.3580885, 0.16],
24+
[23.482342, 6.5036807, 0.5806964, 0.35]],
25+
dtype=torch.float32).cuda() # n, point_features
26+
coordinates = torch.tensor(
27+
[[0, 12, 819, 131], [0, 16, 750, 136], [1, 16, 705, 232],
28+
[1, 35, 930, 469]],
29+
dtype=torch.int32).cuda() # n, 4(batch, ind_x, ind_y, ind_z)
30+
31+
# test
32+
input_sp_tensor = SparseTensor(voxel_features, coordinates)
33+
34+
self = MinkowskiConvModule(4, 4, kernel_size=2, stride=2).cuda()
35+
36+
out_features = self(input_sp_tensor)
37+
assert out_features.F.shape == torch.Size([4, 4])
38+
39+
40+
def test_MinkowskiResidualBlock():
41+
if not torch.cuda.is_available():
42+
pytest.skip('test requires GPU and torch+cuda')
43+
voxel_features = torch.tensor(
44+
[[6.56126, 0.9648336, -1.7339306, 0.315],
45+
[6.8162713, -2.480431, -1.3616394, 0.36],
46+
[11.643568, -4.744306, -1.3580885, 0.16],
47+
[23.482342, 6.5036807, 0.5806964, 0.35]],
48+
dtype=torch.float32).cuda() # n, point_features
49+
coordinates = torch.tensor(
50+
[[0, 12, 819, 131], [0, 16, 750, 136], [1, 16, 705, 232],
51+
[1, 35, 930, 469]],
52+
dtype=torch.int32).cuda() # n, 4(batch, ind_x, ind_y, ind_z)
53+
54+
# test
55+
input_sp_tensor = SparseTensor(voxel_features, coordinates)
56+
57+
sparse_block0 = MinkowskiBasicBlock(4, 4, kernel_size=3).cuda()
58+
sparse_block1 = MinkowskiBottleneck(
59+
4,
60+
4,
61+
downsample=MinkowskiConvModule(4, 16, kernel_size=1, act_cfg=None),
62+
kernel_size=3).cuda()
63+
64+
# test forward
65+
out_features0 = sparse_block0(input_sp_tensor)
66+
out_features1 = sparse_block1(input_sp_tensor)
67+
assert out_features0.F.shape == torch.Size([4, 4])
68+
assert out_features1.F.shape == torch.Size([4, 16])

0 commit comments

Comments
 (0)