fps.py 3.97 KB
Newer Older
limm's avatar
limm committed
1
from typing import List, Optional, Union
quyuanhao123's avatar
quyuanhao123 committed
2
3
4
5

import torch
from torch import Tensor

limm's avatar
limm committed
6
7
import torch_cluster.typing

quyuanhao123's avatar
quyuanhao123 committed
8
9

@torch.jit._overload  # noqa
limm's avatar
limm committed
10
11
def fps(src, batch, ratio, random_start, batch_size, ptr):  # noqa
    # type: (Tensor, Optional[Tensor], Optional[float], bool, Optional[int], Optional[Tensor]) -> Tensor  # noqa
quyuanhao123's avatar
quyuanhao123 committed
12
13
14
15
    pass  # pragma: no cover


@torch.jit._overload  # noqa
limm's avatar
limm committed
16
17
def fps(src, batch, ratio, random_start, batch_size, ptr):  # noqa
    # type: (Tensor, Optional[Tensor], Optional[Tensor], bool, Optional[int], Optional[Tensor]) -> Tensor  # noqa
quyuanhao123's avatar
quyuanhao123 committed
18
19
20
    pass  # pragma: no cover


limm's avatar
limm committed
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@torch.jit._overload  # noqa
def fps(src, batch, ratio, random_start, batch_size, ptr):  # noqa
    # type: (Tensor, Optional[Tensor], Optional[float], bool, Optional[int], Optional[List[int]]) -> Tensor  # noqa
    pass  # pragma: no cover


@torch.jit._overload  # noqa
def fps(src, batch, ratio, random_start, batch_size, ptr):  # noqa
    # type: (Tensor, Optional[Tensor], Optional[Tensor], bool, Optional[int], Optional[List[int]]) -> Tensor  # noqa
    pass  # pragma: no cover


def fps(  # noqa
    src: torch.Tensor,
    batch: Optional[Tensor] = None,
    ratio: Optional[Union[Tensor, float]] = None,
    random_start: bool = True,
    batch_size: Optional[int] = None,
    ptr: Optional[Union[Tensor, List[int]]] = None,
):
quyuanhao123's avatar
quyuanhao123 committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    r""""A sampling algorithm from the `"PointNet++: Deep Hierarchical Feature
    Learning on Point Sets in a Metric Space"
    <https://arxiv.org/abs/1706.02413>`_ paper, which iteratively samples the
    most distant point with regard to the rest points.

    Args:
        src (Tensor): Point feature matrix
            :math:`\mathbf{X} \in \mathbb{R}^{N \times F}`.
        batch (LongTensor, optional): Batch vector
            :math:`\mathbf{b} \in {\{ 0, \ldots, B-1\}}^N`, which assigns each
            node to a specific example. (default: :obj:`None`)
        ratio (float or Tensor, optional): Sampling ratio.
            (default: :obj:`0.5`)
        random_start (bool, optional): If set to :obj:`False`, use the first
            node in :math:`\mathbf{X}` as starting node. (default: obj:`True`)
limm's avatar
limm committed
56
57
58
59
60
61
        batch_size (int, optional): The number of examples :math:`B`.
            Automatically calculated if not given. (default: :obj:`None`)
        ptr (torch.Tensor or [int], optional): If given, batch assignment will
            be determined based on boundaries in CSR representation, *e.g.*,
            :obj:`batch=[0,0,1,1,1,2]` translates to :obj:`ptr=[0,2,5,6]`.
            (default: :obj:`None`)
quyuanhao123's avatar
quyuanhao123 committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

    :rtype: :class:`LongTensor`

    .. code-block:: python

        import torch
        from torch_cluster import fps

        src = torch.Tensor([[-1, -1], [-1, 1], [1, -1], [1, 1]])
        batch = torch.tensor([0, 0, 0, 0])
        index = fps(src, batch, ratio=0.5)
    """
    r: Optional[Tensor] = None
    if ratio is None:
        r = torch.tensor(0.5, dtype=src.dtype, device=src.device)
    elif isinstance(ratio, float):
        r = torch.tensor(ratio, dtype=src.dtype, device=src.device)
    else:
        r = ratio
    assert r is not None

limm's avatar
limm committed
83
84
85
86
87
88
89
90
91
92
93
    if ptr is not None:
        if isinstance(ptr, list) and torch_cluster.typing.WITH_PTR_LIST:
            return torch.ops.torch_cluster.fps_ptr_list(
                src, ptr, r, random_start)

        if isinstance(ptr, list):
            return torch.ops.torch_cluster.fps(
                src, torch.tensor(ptr, device=src.device), r, random_start)
        else:
            return torch.ops.torch_cluster.fps(src, ptr, r, random_start)

quyuanhao123's avatar
quyuanhao123 committed
94
95
    if batch is not None:
        assert src.size(0) == batch.numel()
limm's avatar
limm committed
96
97
        if batch_size is None:
            batch_size = int(batch.max()) + 1
quyuanhao123's avatar
quyuanhao123 committed
98
99
100
101

        deg = src.new_zeros(batch_size, dtype=torch.long)
        deg.scatter_add_(0, batch, torch.ones_like(batch))

limm's avatar
limm committed
102
103
        ptr_vec = deg.new_zeros(batch_size + 1)
        torch.cumsum(deg, 0, out=ptr_vec[1:])
quyuanhao123's avatar
quyuanhao123 committed
104
    else:
limm's avatar
limm committed
105
        ptr_vec = torch.tensor([0, src.size(0)], device=src.device)
quyuanhao123's avatar
quyuanhao123 committed
106

limm's avatar
limm committed
107
    return torch.ops.torch_cluster.fps(src, ptr_vec, r, random_start)