Skip to content

Commit d9324a3

Browse files
Use cluster labels for truth point lookup
1 parent 4506666 commit d9324a3

4 files changed

Lines changed: 93 additions & 7 deletions

File tree

src/spine/model/full_chain.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,8 @@ def run_part_aggregation(self, data, clust_label=None, coord_label=None):
783783
data,
784784
fragments,
785785
fragment_shapes,
786-
coord_label,
786+
clust_label=clust_label,
787+
coord_label=coord_label,
787788
aggregate_shapes=True,
788789
shape_use_primary=use_primary[name],
789790
retain_primaries=use_primary[name],
@@ -895,7 +896,8 @@ def run_inter_aggregation(self, data, clust_label=None, coord_label=None):
895896
particles,
896897
particle_shapes,
897898
particle_primaries,
898-
coord_label,
899+
clust_label=clust_label,
900+
coord_label=coord_label,
899901
point_use_primary=True,
900902
)
901903

@@ -1002,6 +1004,7 @@ def run_grappa(
10021004
clusts,
10031005
clust_shapes,
10041006
clust_primaries=None,
1007+
clust_label=None,
10051008
coord_label=None,
10061009
aggregate_shapes=False,
10071010
shape_use_primary=False,
@@ -1024,6 +1027,9 @@ def run_grappa(
10241027
Semantic type of each of the clusters
10251028
clust_primaries : IndexBatch
10261029
List of primary fragments associated with each input cluster
1030+
clust_label : TensorBatch, optional
1031+
Tensor used to fetch truth labels when building points from
1032+
`coord_label`. Defaults to `data`.
10271033
coord_label : TensorBatch, optional
10281034
(N, 1 + D + 6) Array of label particle end points
10291035
aggregate_shapes : bool, default False
@@ -1058,6 +1064,7 @@ def run_grappa(
10581064
clusts,
10591065
clust_shapes,
10601066
clust_primaries,
1067+
clust_label,
10611068
coord_label,
10621069
point_use_primary,
10631070
)
@@ -1209,6 +1216,7 @@ def prepare_grappa_input(
12091216
clusts,
12101217
clust_shapes,
12111218
clust_primaries=None,
1219+
clust_label=None,
12121220
coord_label=None,
12131221
point_use_primaries=False,
12141222
):
@@ -1231,6 +1239,9 @@ def prepare_grappa_input(
12311239
Semantic type of each of the clusters
12321240
clust_primaries : IndexBatch, optional
12331241
List of primary fragment within each cluster to aggregate
1242+
clust_label : TensorBatch, optional
1243+
Tensor used to fetch truth labels when building points from
1244+
`coord_label`. Defaults to `data`.
12341245
coord_label : TensorBatch, optional
12351246
(N, 1 + D + 6) Array of label particle end points
12361247
point_use_primaries:
@@ -1274,9 +1285,13 @@ def prepare_grappa_input(
12741285
"Must provide either `ppn_points` or `coord_label` to add "
12751286
"points to the GrapPA input."
12761287
)
1288+
assert clust_label is not None, (
1289+
"Must provide `clust_label` with `coord_label` to add "
1290+
"label points to the GrapPA input."
1291+
)
12771292
point_clusts = clusts if point_use_primaries else ref_clusts
12781293
points = get_cluster_points_label_batch(
1279-
data,
1294+
clust_label,
12801295
coord_label,
12811296
point_clusts,
12821297
use_group=point_use_primaries,

src/spine/utils/gnn/cluster.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,12 +1033,23 @@ def _get_cluster_points_label(
10331033
if use_group:
10341034
# Use the true aggregate particle identity directly.
10351035
label_ids = np.unique(data[c, GROUP_COL]).astype(np.int64)
1036-
label = coord_label[label_ids[0]]
1036+
label_id = label_ids[0]
10371037
else:
10381038
# Use the first constituent particle in time.
10391039
part_ids = np.unique(data[c, PART_COL]).astype(np.int64)
1040-
min_id = part_ids[np.argmin(coord_label[part_ids, COORD_TIME_COL])]
1041-
label = coord_label[min_id]
1040+
label_id = -1
1041+
min_time = np.inf
1042+
for part_id in part_ids:
1043+
if part_id < 0 or part_id >= len(coord_label):
1044+
raise IndexError("Invalid label index for coord_label.")
1045+
time = coord_label[part_id, COORD_TIME_COL]
1046+
if time < min_time:
1047+
min_time = time
1048+
label_id = part_id
1049+
1050+
if label_id < 0 or label_id >= len(coord_label):
1051+
raise IndexError("Invalid label index for coord_label.")
1052+
label = coord_label[label_id]
10421053

10431054
start = label[COORD_START_COLS_LO:COORD_START_COLS_HI]
10441055
end = label[COORD_END_COLS_LO:COORD_END_COLS_HI]

test/test_model/test_full_chain.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from types import SimpleNamespace
22

33
import numpy as np
4+
import pytest
45
import torch
56

67
import spine.model.full_chain as full_chain_mod
@@ -183,6 +184,9 @@ def test_prepare_grappa_input_uses_label_points_without_ppn(monkeypatch):
183184

184185
model = SimpleNamespace(node_encoder=SimpleNamespace(add_points=True))
185186
data = TensorBatch(np.zeros((3, 4), dtype=np.float32), counts=np.array([3]))
187+
clust_label = TensorBatch(
188+
np.zeros((3, GROUP_COL + 1), dtype=np.float32), counts=np.array([3])
189+
)
186190
clusts = IndexBatch(
187191
[np.array([0], dtype=np.int64), np.array([1, 2], dtype=np.int64)],
188192
spans=np.array([3]),
@@ -214,12 +218,38 @@ def fake_label_points(data_arg, coord_label_arg, clusts_arg, **kwargs):
214218
clusts,
215219
clust_shapes,
216220
clust_primaries=primaries,
221+
clust_label=clust_label,
217222
coord_label=coord_label,
218223
point_use_primaries=True,
219224
)
220225

221226
assert grappa_input["points"] is label_points
222227
assert "coord_label" not in grappa_input
223228
assert calls == [
224-
(data, coord_label, clusts, {"use_group": True}),
229+
(clust_label, coord_label, clusts, {"use_group": True}),
225230
]
231+
232+
233+
def test_prepare_grappa_input_requires_clust_label_for_label_points():
234+
full_chain = object.__new__(FullChain)
235+
full_chain.result = {}
236+
237+
model = SimpleNamespace(node_encoder=SimpleNamespace(add_points=True))
238+
data = TensorBatch(np.zeros((1, 4), dtype=np.float32), counts=np.array([1]))
239+
clusts = IndexBatch(
240+
[np.array([0], dtype=np.int64)],
241+
spans=np.array([1]),
242+
counts=np.array([1]),
243+
single_counts=np.array([1]),
244+
)
245+
clust_shapes = TensorBatch(np.array([SHOWR_SHP]), counts=np.array([1]))
246+
coord_label = TensorBatch(np.zeros((1, 9), dtype=np.float32), counts=np.array([1]))
247+
248+
with pytest.raises(AssertionError, match="clust_label"):
249+
full_chain.prepare_grappa_input(
250+
model,
251+
data,
252+
clusts,
253+
clust_shapes,
254+
coord_label=coord_label,
255+
)

test/test_utils/test_gnn_cluster.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Regression tests for GNN cluster utilities."""
22

33
import numpy as np
4+
import pytest
45

56
from spine.constants import GROUP_COL, PART_COL
67
from spine.utils.gnn.cluster import (
@@ -78,3 +79,32 @@ def test_cluster_points_label_can_use_group_identity():
7879

7980
np.testing.assert_allclose(points[0, :3], [20.0, 0.0, 0.0])
8081
np.testing.assert_allclose(points[0, 3:], [20.0, 0.0, 0.0])
82+
83+
84+
def test_cluster_points_label_rejects_invalid_particle_id():
85+
data = np.zeros((1, PART_COL + 1), dtype=np.float32)
86+
data[:, PART_COL] = 1
87+
coord_label = np.zeros((1, 9), dtype=np.float32)
88+
89+
with pytest.raises(IndexError, match="Invalid label index"):
90+
get_cluster_points_label(
91+
data,
92+
coord_label,
93+
[np.array([0], dtype=np.int64)],
94+
random_order=False,
95+
)
96+
97+
98+
def test_cluster_points_label_rejects_invalid_group_id():
99+
data = np.zeros((1, GROUP_COL + 1), dtype=np.float32)
100+
data[:, GROUP_COL] = 1
101+
coord_label = np.zeros((1, 9), dtype=np.float32)
102+
103+
with pytest.raises(IndexError, match="Invalid label index"):
104+
get_cluster_points_label(
105+
data,
106+
coord_label,
107+
[np.array([0], dtype=np.int64)],
108+
random_order=False,
109+
use_group=True,
110+
)

0 commit comments

Comments
 (0)