Skip to content

Commit aba0db2

Browse files
committed
Added to_sparse that removes zeros. Prev to_sparse -> to_sparse_all
1 parent 258433e commit aba0db2

File tree

7 files changed

+85
-8
lines changed

7 files changed

+85
-8
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
- origin field
1414
- origin field map
1515
- MinkowskiGlobalMaxPool CPU/GPU updates for a field input
16+
- SparseTensor.dense() raises a value error when a coordinate is negative rather than subtracting the minimum coordinate from a sparse tensor.
17+
- Added `to_sparse()` that removes zeros.
18+
- Previous `to_sparse()` was renamed to `to_sparse_all()`
19+
- `MinkowskiToSparseTensor` takes an optional `remove_zeros` boolean argument.
20+
1621

1722
## [0.5.1]
1823

MinkowskiEngine/MinkowskiOps.py

+56-5
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,49 @@ def dense_coordinates(shape: Union[list, torch.Size]):
276276
return coordinates
277277

278278

279-
def to_sparse(dense_tensor: torch.Tensor, coordinates: torch.Tensor = None):
280-
r"""Converts a (differentiable) dense tensor to a sparse tensor.
279+
def to_sparse(x: torch.Tensor, format: str = None, coordinates=None, device=None):
280+
r"""Convert a batched tensor (dimension 0 is the batch dimension) to a SparseTensor
281+
282+
:attr:`x` (:attr:`torch.Tensor`): a batched tensor. The first dimension is the batch dimension.
283+
284+
:attr:`format` (:attr:`str`): Format of the tensor. It must include 'B' and 'C' indicating the batch and channel dimension respectively. The rest of the dimensions must be 'X'. .e.g. format="BCXX" if image data with BCHW format is used. If a 3D data with the channel at the last dimension, use format="BXXXC" indicating Batch X Height X Width X Depth X Channel. If not provided, the format will be "BCX...X".
285+
286+
:attr:`device`: Device the sparse tensor will be generated on. If not provided, the device of the input tensor will be used.
287+
288+
"""
289+
assert x.ndim > 2, "Input has 0 spatial dimension."
290+
assert isinstance(x, torch.Tensor)
291+
if format is None:
292+
format = [
293+
"X",
294+
] * x.ndim
295+
format[0] = "B"
296+
format[1] = "C"
297+
format = "".join(format)
298+
assert x.ndim == len(format), f"Invalid format: {format}. len(format) != x.ndim"
299+
assert (
300+
"B" in format and "B" == format[0] and format.count("B") == 1
301+
), "The input must have the batch axis and the format must include 'B' indicating the batch axis."
302+
assert (
303+
"C" in format and format.count("C") == 1
304+
), "The format must indicate the channel axis"
305+
if device is None:
306+
device = x.device
307+
ch_dim = format.find("C")
308+
reduced_x = torch.abs(x).sum(ch_dim)
309+
bcoords = torch.where(reduced_x != 0)
310+
stacked_bcoords = torch.stack(bcoords, dim=1).int()
311+
indexing = [f"bcoords[{i}]" for i in range(len(bcoords))]
312+
indexing.insert(ch_dim, ":")
313+
features = torch.zeros(
314+
(len(stacked_bcoords), x.size(ch_dim)), dtype=x.dtype, device=x.device
315+
)
316+
exec("features[:] = x[" + ", ".join(indexing) + "]")
317+
return SparseTensor(features=features, coordinates=stacked_bcoords, device=device)
318+
319+
320+
def to_sparse_all(dense_tensor: torch.Tensor, coordinates: torch.Tensor = None):
321+
r"""Converts a (differentiable) dense tensor to a sparse tensor with all coordinates.
281322
282323
Assume the input to have BxCxD1xD2x....xDN format.
283324
@@ -312,6 +353,9 @@ class MinkowskiToSparseTensor(MinkowskiModuleBase):
312353
313354
For dense tensor, the input must have the BxCxD1xD2x....xDN format.
314355
356+
:attr:`remove_zeros` (bool): if True, removes zero valued coordinates. If
357+
False, use all coordinates to populate a sparse tensor. True by default.
358+
315359
If the shape of the tensor do not change, use `dense_coordinates` to cache the coordinates.
316360
Please refer to tests/python/dense.py for usage.
317361
@@ -327,7 +371,7 @@ class MinkowskiToSparseTensor(MinkowskiModuleBase):
327371
>>> network = nn.Sequential(
328372
>>> # Add layers that can be applied on a regular pytorch tensor
329373
>>> nn.ReLU(),
330-
>>> MinkowskiToSparseTensor(coordinates=coordinates),
374+
>>> MinkowskiToSparseTensor(remove_zeros=False, coordinates=coordinates),
331375
>>> MinkowskiConvolution(4, 5, kernel_size=3, dimension=4),
332376
>>> MinkowskiBatchNorm(5),
333377
>>> MinkowskiReLU(),
@@ -341,16 +385,23 @@ class MinkowskiToSparseTensor(MinkowskiModuleBase):
341385
342386
"""
343387

344-
def __init__(self, coordinates: torch.Tensor = None):
388+
def __init__(self, remove_zeros=True, coordinates: torch.Tensor = None):
345389
MinkowskiModuleBase.__init__(self)
390+
assert (
391+
remove_zeros and coordinates is None
392+
), "The coordinates argument cannot be used with remove_zeros=True. If you want to use the coordinates argument, provide remove_zeros=False."
393+
self.remove_zeros = remove_zeros
346394
self.coordinates = coordinates
347395

348396
def forward(self, input: Union[TensorField, torch.Tensor]):
349397
if isinstance(input, TensorField):
350398
return input.sparse()
351399
elif isinstance(input, torch.Tensor):
352400
# dense tensor to sparse tensor conversion
353-
return to_sparse(input, self.coordinates)
401+
if self.remove_zeros:
402+
return to_sparse(input)
403+
else:
404+
return to_sparse_all(input, self.coordinates)
354405
else:
355406
raise ValueError(
356407
"Unsupported type. Only TensorField and torch.Tensor are supported"

MinkowskiEngine/MinkowskiSparseTensor.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,11 @@ def dense(self, shape=None, min_coordinate=None, contract_stride=True):
486486
if min_coordinate is None:
487487
min_coordinate, _ = self.C.min(0, keepdim=True)
488488
min_coordinate = min_coordinate[:, 1:]
489-
coords = self.C[:, 1:] - min_coordinate
489+
if not torch.all(min_coordinate >= 0):
490+
raise ValueError(
491+
f"Coordinate has a negative value: {min_coordinate}. Please provide min_coordinate argument"
492+
)
493+
coords = self.C[:, 1:]
490494
elif isinstance(min_coordinate, int) and min_coordinate == 0:
491495
coords = self.C[:, 1:]
492496
else:

MinkowskiEngine/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
mean,
198198
var,
199199
to_sparse,
200+
to_sparse_all,
200201
dense_coordinates,
201202
)
202203

MinkowskiEngine/utils/coords.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# Please cite "4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural
2222
# Networks", CVPR'19 (https://arxiv.org/abs/1904.08755) if you use any part
2323
# of the code.
24+
import torch
25+
2426
from MinkowskiSparseTensor import SparseTensor
2527

2628

@@ -55,5 +57,7 @@ def get_coords_map(x, y):
5557
"""
5658
assert isinstance(x, SparseTensor)
5759
assert isinstance(y, SparseTensor)
58-
assert x.coords_man == y.coords_man, "X and Y are using different CoordinateManagers. Y must be derived from X through strided conv/pool/etc."
60+
assert (
61+
x.coords_man == y.coords_man
62+
), "X and Y are using different CoordinateManagers. Y must be derived from X through strided conv/pool/etc."
5963
return x.coords_man.get_coords_map(x.coords_key, y.coords_key)

docs/utils.rst

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ to_sparse
3838
.. autofunction:: MinkowskiEngine.to_sparse
3939

4040

41+
to_sparse_all
42+
-------------
43+
44+
.. autofunction:: MinkowskiEngine.to_sparse_all
45+
46+
4147
SparseCollation
4248
---------------
4349

tests/python/dense.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ def test(self):
102102
self.assertEqual(len(sparse_tensor), 3 * 5 * 6)
103103
self.assertEqual(sparse_tensor.F.size(1), 4)
104104

105+
def test_format(self):
106+
dense_tensor = torch.rand(3, 4, 5, 6)
107+
sparse_tensor = to_sparse(dense_tensor, format="BXXC")
108+
self.assertEqual(len(sparse_tensor), 3 * 4 * 5)
109+
self.assertEqual(sparse_tensor.F.size(1), 6)
110+
105111
def test_network(self):
106112
dense_tensor = torch.rand(3, 4, 11, 11, 11, 11) # BxCxD1xD2x....xDN
107113
dense_tensor.requires_grad = True
@@ -112,7 +118,7 @@ def test_network(self):
112118
network = nn.Sequential(
113119
# Add layers that can be applied on a regular pytorch tensor
114120
nn.ReLU(),
115-
MinkowskiToSparseTensor(coordinates=coordinates),
121+
MinkowskiToSparseTensor(remove_zeros=False, coordinates=coordinates),
116122
MinkowskiConvolution(4, 5, stride=2, kernel_size=3, dimension=4),
117123
MinkowskiBatchNorm(5),
118124
MinkowskiReLU(),

0 commit comments

Comments
 (0)