Skip to content

Commit a4c9048

Browse files
CPU Instance iou
1 parent 81f0f7f commit a4c9048

File tree

7 files changed

+141
-15
lines changed

7 files changed

+141
-15
lines changed

cuda/include/metrics.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
#include <torch/extension.h>
3+
4+
at::Tensor instance_iou_cuda(at::Tensor instance_idx, at::Tensor instance_offsets,
5+
at::Tensor instance_gt);

cuda/src/metrics.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "metrics.h"
2+
#include "compat.h"
3+
#include "utils.h"
4+
5+
void instance_iou_kernel_wrapper(int b, int n, int m, const float* dataset, float* temp, int* idxs);
6+
7+
at::Tensor instance_iou_cuda(at::Tensor instance_idx, at::Tensor instance_offsets,
8+
at::Tensor instance_gt)
9+
{
10+
CHECK_CONTIGUOUS(instance_idx);
11+
CHECK_CONTIGUOUS(instance_offsets);
12+
CHECK_CONTIGUOUS(instance_gt);
13+
CHECK_CUDA(instance_idx)
14+
CHECK_CUDA(instance_offsets)
15+
CHECK_CUDA(instance_gt)
16+
17+
auto num_gt_instances = instance_gt.max(0);
18+
auto num_proposed_instances = instance_offsets.size(0);
19+
at::Tensor output =
20+
torch::zeros({num_proposed_instances, num_gt_instances},
21+
at::device(num_gt_instances.device()).dtype(at::ScalarType::Float));
22+
23+
instance_iou_kernel_wrapper(points.size(0), points.size(1), nsamples, points.DATA_PTR<float>(),
24+
tmp.DATA_PTR<float>(), output.DATA_PTR<float>());
25+
26+
return output;
27+
}

cuda/src/metrics.cu

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <math.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
5+
#include "cuda_utils.h"
6+
7+
void instance_iou_kernel_wrapper(int b, int n, int m, const float* dataset, float* temp,
8+
int* idxs);

test/test_cluster.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,16 @@ def test_simple(self):
3434
self.assertEqual(clusters, [[0, 1, 2], [4, 5, 6]])
3535

3636
def test_region_grow(self):
37-
clusters = region_grow(
37+
cluster_idx = region_grow(
3838
self.pos, self.labels, self.batch, radius=2, min_cluster_size=1
3939
)
40-
self.assertEqual(len(clusters[0]), 2)
41-
self.assertEqual(len(clusters[1]), 3)
42-
self.assertEqual(len(clusters[10]), 1)
43-
torch.testing.assert_allclose(clusters[0][0], torch.tensor([0, 1]))
44-
torch.testing.assert_allclose(clusters[0][1], torch.tensor([4]))
45-
torch.testing.assert_allclose(clusters[1][0], torch.tensor([2]))
46-
torch.testing.assert_allclose(clusters[1][1], torch.tensor([3]))
47-
torch.testing.assert_allclose(clusters[1][2], torch.tensor([5, 6]))
48-
torch.testing.assert_allclose(clusters[10][0], torch.tensor([7]))
40+
self.assertEqual(len(cluster_idx), 6)
41+
torch.testing.assert_allclose(cluster_idx[0], torch.tensor([0, 1]))
42+
torch.testing.assert_allclose(cluster_idx[1], torch.tensor([4]))
43+
torch.testing.assert_allclose(cluster_idx[2], torch.tensor([2]))
44+
torch.testing.assert_allclose(cluster_idx[3], torch.tensor([3]))
45+
torch.testing.assert_allclose(cluster_idx[4], torch.tensor([5, 6]))
46+
torch.testing.assert_allclose(cluster_idx[5], torch.tensor([7]))
4947

5048

5149
if __name__ == "__main__":

test/test_metrics.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import unittest
2+
import torch
3+
import os
4+
import sys
5+
import numpy as np
6+
7+
ROOT = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
8+
sys.path.insert(0, ROOT)
9+
10+
from torch_points_kernels.metrics import instance_iou
11+
12+
13+
class TestInstanceIou(unittest.TestCase):
14+
def test_simple(self):
15+
gt_instances = torch.tensor([1, 2, 1, 2, 2, 3, 0])
16+
proposed_instances = [
17+
torch.tensor([0, 2]), # 100% instance 1
18+
torch.tensor([1, 4]), # 2/3 of instance 2
19+
torch.tensor([3, 5]), # 1/3 of instance 2 and 1/1 of instance 3
20+
]
21+
22+
ious = instance_iou(proposed_instances, gt_instances)
23+
torch.testing.assert_allclose(
24+
ious, torch.tensor([[1, 0, 0], [0, 2 / 3.0, 0], [0, 1.0 / 4.0, 1.0 / 2.0]])
25+
)
26+
27+
28+
if __name__ == "__main__":
29+
unittest.main()

torch_points_kernels/cluster.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .torchpoints import ball_query_partial_dense
33
import numpy as np
44
import numba
5+
from typing import List
56

67

78
@numba.jit(nopython=True)
@@ -50,7 +51,7 @@ def grow_proximity(pos, batch, nsample=16, radius=0.02, min_cluster_size=32):
5051

5152
def region_grow(
5253
pos, labels, batch, ignore_labels=[], nsample=16, radius=0.02, min_cluster_size=32
53-
):
54+
) -> List[torch.Tensor]:
5455
""" Region growing clustering algorithm proposed in
5556
PointGroup: Dual-Set Point Grouping for 3D Instance Segmentation
5657
https://arxiv.org/pdf/2004.01658.pdf
@@ -76,7 +77,7 @@ def region_grow(
7677
assert pos.shape[0] == labels.shape[0]
7778

7879
unique_labels = torch.unique(labels)
79-
clusters = {}
80+
clusters = []
8081
ind = torch.arange(0, pos.shape[0])
8182
for l in unique_labels:
8283
if l in ignore_labels:
@@ -95,10 +96,8 @@ def region_grow(
9596

9697
# Remap indices to original coordinates
9798
if len(label_clusters):
98-
remaped_clusters = []
9999
for cluster in label_clusters:
100100
cluster = torch.tensor(cluster).to(pos.device)
101-
remaped_clusters.append(local_ind[cluster])
102-
clusters[l.item()] = remaped_clusters
101+
clusters.append(local_ind[cluster])
103102

104103
return clusters

torch_points_kernels/metrics.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import torch
2+
from typing import List
3+
import numpy as np
4+
import numba
5+
6+
7+
@numba.jit(nopython=True, parallel=True)
8+
def _instance_iou_cpu(instance_idx, instance_offsets, instance_gt):
9+
num_instances = np.max(instance_gt)
10+
gt_instance_sizes = []
11+
for instance_id in numba.prange(1, num_instances + 1):
12+
gt_instance_sizes.append(np.sum(instance_gt == instance_id))
13+
iou = np.zeros((len(instance_offsets), num_instances))
14+
old_offset = 0
15+
for proposed_instance, offset in enumerate(instance_offsets):
16+
instance = instance_idx[old_offset:offset]
17+
for instance_id in numba.prange(1, num_instances + 1):
18+
intersection = 0
19+
for idx in instance:
20+
if instance_gt[idx] == instance_id:
21+
intersection += 1
22+
iou[proposed_instance, instance_id - 1] = intersection / float(
23+
len(instance) + gt_instance_sizes[instance_id - 1] - intersection
24+
)
25+
old_offset = offset
26+
return iou
27+
28+
29+
def instance_iou(instance_idx: List[torch.Tensor], gt_instances: torch.Tensor):
30+
""" Computes the IoU between each proposed instance in instance_idx and ground truth instances. Returns a
31+
tensor of shape [instance_idx.shape[0], num_instances] that contains the iou between the proposed instances and all gt instances
32+
Instance label 0 is reserved for non instance points
33+
34+
Parameters
35+
----------
36+
instance_idx : List[torch.Tensor]
37+
List of instances. Each tensor in this list is a proposed and contains the index of the points
38+
that belong to that particular instance
39+
gt_instances : torch.Tensor
40+
Ground truth instances, contains the index of the instance for each point
41+
num_instances : int
42+
Number of instances in ground truth
43+
44+
Returns
45+
-------
46+
ious:
47+
"""
48+
instance_offsets = []
49+
cum_offset = 0
50+
for instance in instance_idx:
51+
cum_offset += instance.shape[0]
52+
instance_offsets.append(cum_offset)
53+
instance_idx = torch.cat(instance_idx)
54+
if gt_instances.is_cuda:
55+
pass
56+
else:
57+
res = _instance_iou_cpu(
58+
instance_idx.numpy(), np.asarray(instance_offsets), gt_instances.numpy()
59+
)
60+
return torch.tensor(res).float()

0 commit comments

Comments
 (0)