traveller59/spconv

points vanished during using spconv.SparseConv3d

yejr0229 opened this issue · 4 comments

Here is the detailed error:
ValueError: Your points vanished here, this usually because you provide
conv params that may ignore some input points. Example:
spatial_shape=[8, 200, 200]
ksize=3
stride=2
padding=[0, 1, 1]
dilation=1
Coordinates=[[0, 7, 153, 142]]
these params will cause ALL points in z == 7 dropped because of padding_z=0.
enlarge your spatial shape or change your conv param to make sure
every input point has a corresponding output point.
Your Conv Params:
spatial_shape=[96, 320, 384]
ksize=[3, 3, 3]
stride=[2, 2, 2]
padding=[1, 1, 1]
dilation=[1, 1, 1]
How should I do?

I have resolved this issue recently. The example bug shown in your console is confusing because the actual vanished point is in x==7 rather than z==7. Image that there is a 3-D space with x ranging from [0,7] (right closed), and a 3-D convolution with ksize=3, stride=2 operates on this plane. As a result, the sampled areas are [0,2], [2,4], [4,6], and the plane x==7 is not sampled. Therefore, to make it valid, you can choose to set the padding to all 1 and ensure the dimension of spatial_shape are odd numbers.

Furthermore, check if all the input coordinates are non-negative values. If not, add a constant bias to them.

Use the following code if necessary:

grid_coord, feat, batch_size = data
amax = torch.amax(grid_coord[:,1:], dim=0)
amin = torch.amin(grid_coord[:,1:],dim=0)
drange = amax - amin
grid_coord[:, 1:] -= amin[None, :]
drange_bias = torch.zeros_like(drange)
drange_bias[drange % 2 ==0] = 1  # ensure spatial_shape are odd numbers
drange += drange_bias
x = spconv.SparseConvTensor(
    features=feat,
    indices=grid_coord.contiguous(),
    spatial_shape=drange,
    batch_size=batch_size,
)

# convolution parameters ksize=3, stride=2, padding=1 are abosolutely valid in this case

@gitouni May i ask, how can i change the padding ? In the config or in source code of backbone ?
Thank you

@BNTzHax The padding is the convolutional parameter and set in your model. I copied a snippet of code from pointcept and show how to modify it below.

class SpUNetNoSkipBase(nn.Module):
    def __init__(
        self,
        in_channels,
        out_channels,
        base_channels=32,
        channels=(32, 64, 128, 128, 96, 96),
        layers=(2, 3, 4, 4, 2, 2),
    ):
        super().__init__()
        assert len(layers) % 2 == 0
        assert len(layers) == len(channels)
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.base_channels = base_channels
        self.channels = channels
        self.layers = layers
        self.num_stages = len(layers) // 2

        norm_fn = partial(nn.BatchNorm1d, eps=1e-3, momentum=0.01)
        block = BasicBlock

        self.conv_input = spconv.SparseSequential(
            spconv.SubMConv3d(
                in_channels,
                base_channels,
                kernel_size=5,
                padding=1,
                bias=False,
                indice_key="stem",
            ),
            norm_fn(base_channels),
            nn.ReLU(),
        )

        enc_channels = base_channels
        dec_channels = channels[-1]
        self.down = nn.ModuleList()
        self.up = nn.ModuleList()
        self.enc = nn.ModuleList()
        self.dec = nn.ModuleList()

        for s in range(self.num_stages):
            # encode num_stages
            self.down.append(
                spconv.SparseSequential(
                    spconv.SparseConv3d(
                        enc_channels,
                        channels[s],
                        kernel_size=3,
                        padding=1,
                        stride=2,
                        bias=False,
                        indice_key=f"spconv{s + 1}",
                    ),
                    norm_fn(channels[s]),
                    nn.ReLU(),
                )
            )
            self.enc.append(
                spconv.SparseSequential(
                    OrderedDict(
                        [
                            # (f"block{i}", block(enc_channels, channels[s], norm_fn=norm_fn, indice_key=f"subm{s + 1}"))
                            # if i == 0 else
                            (
                                f"block{i}",
                                block(
                                    channels[s],
                                    channels[s],
                                    norm_fn=norm_fn,
                                    indice_key=f"subm{s + 1}",
                                ),
                            )
                            for i in range(layers[s])
                        ]
                    )
                )
            )

            # decode num_stages
            self.up.append(
                spconv.SparseSequential(
                    spconv.SparseInverseConv3d(
                        channels[len(channels) - s - 2],
                        dec_channels,
                        kernel_size=3,
                        bias=False,
                        indice_key=f"spconv{s + 1}",
                    ),
                    norm_fn(dec_channels),
                    nn.ReLU(),
                )
            )
            self.dec.append(
                spconv.SparseSequential(
                    OrderedDict(
                        [
                            (
                                (
                                    f"block{i}",
                                    block(
                                        dec_channels,
                                        dec_channels,
                                        norm_fn=norm_fn,
                                        indice_key=f"subm{s}",
                                    ),
                                )
                                if i == 0
                                else (
                                    f"block{i}",
                                    block(
                                        dec_channels,
                                        dec_channels,
                                        norm_fn=norm_fn,
                                        indice_key=f"subm{s}",
                                    ),
                                )
                            )
                            for i in range(layers[len(channels) - s - 1])
                        ]
                    )
                )
            )
            enc_channels = channels[s]
            dec_channels = channels[len(channels) - s - 2]

        self.final = (
            spconv.SubMConv3d(
                channels[-1], out_channels, kernel_size=1, padding=1, bias=True
            )
            if out_channels > 0
            else spconv.Identity()
        )
        self.apply(self._init_weights)  # apply initiation through all children

    @staticmethod
    def _init_weights(m):
        if isinstance(m, nn.Linear):
            trunc_normal_(m.weight, std=0.02)
            if m.bias is not None:
                nn.init.constant_(m.bias, 0)
        elif isinstance(m, spconv.SubMConv3d):
            trunc_normal_(m.weight, std=0.02)
            if m.bias is not None:
                nn.init.constant_(m.bias, 0)
        elif isinstance(m, nn.BatchNorm1d):
            nn.init.constant_(m.bias, 0)
            nn.init.constant_(m.weight, 1.0)

    def forward(self, data):
        grid_coord, feat, batch_size = data
        amax = torch.amax(grid_coord[:,1:], dim=0)
        amin = torch.amin(grid_coord[:,1:],dim=0)
        drange = amax - amin
        grid_coord[:, 1:] -= amin[None, :]
        drange_bias = torch.zeros_like(drange)
        drange_bias[drange % 2 ==0] = 1
        drange += drange_bias
        x = spconv.SparseConvTensor(
            features=feat,
            indices=grid_coord.contiguous(),
            spatial_shape=drange,
            batch_size=batch_size,
        )
        x = self.conv_input(x)
        skips = [x]
        # enc forward
        for s in range(self.num_stages):
            x = self.down[s](x)
            x = self.enc[s](x)
            skips.append(x)
            
        x = skips.pop(-1)
        # dec forward
        for s in reversed(range(self.num_stages)):
            x = self.up[s](x)
            # skip = skips.pop(-1)
            # x = x.replace_feature(torch.cat((x.features, skip.features), dim=1))
            x = self.dec[s](x)

        x = self.final(x)
        return x.features

The convolutional parameters are set in

self.down.append(
                spconv.SparseSequential(
                    spconv.SparseConv3d(
                        enc_channels,
                        channels[s],
                        kernel_size=3,
                        padding=1,
                        stride=2,
                        bias=False,
                        indice_key=f"spconv{s + 1}",
                    ),
                    norm_fn(channels[s]),
                    nn.ReLU(),
                )
            )

, illustrating that he padding size=1, kernel_size=3, stride=2

@gitouni Thank you, i will try that