Skip to content

Commit b02f465

Browse files
Merge pull request #84 from francois-drielsma/develop
Various speed-ups in the full chain routines
2 parents 90ddb4c + f2cf4e3 commit b02f465

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2185
-1176
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Build Status](https://app.travis-ci.com/francois-drielsma/lartpc_mlreco3d.svg?token=WB4oxAv87vEXhuxUGH7e&branch=develop&status=passed)](https://app.travis-ci.com/github/francois-drielsma/lartpc_mlreco3d/logscans?serverType=git)
66
[![Documentation Status](https://readthedocs.org/projects/lartpc-mlreco3d/badge/?version=latest)](https://lartpc-mlreco3d.readthedocs.io/en/latest/?badge=latest)
77

8-
The Scalable Particle Imaging with Neural Embeddings (SPINE) package leverages state-of-the-art Machine Learning (ML) algorithms -- in particular Deep Neural Networks (DNNs) -- to reconstruct particle imagaging detector data. This package was primarily developed for Liquid Argon Time-Projection Chamber (LArTPC) data and relies on Convolutional Neural Networks (CNNs) for pixel-level feature extraction and Graph Neural Networks (GNNs) for superstructure formation. The schematic below breaks down the full end-to-end reconstruction flow.
8+
The Scalable Particle Imaging with Neural Embeddings (SPINE) package leverages state-of-the-art Machine Learning (ML) algorithms -- in particular Deep Neural Networks (DNNs) -- to reconstruct particle imaging detector data. This package was primarily developed for Liquid Argon Time-Projection Chamber (LArTPC) data and relies on Convolutional Neural Networks (CNNs) for pixel-level feature extraction and Graph Neural Networks (GNNs) for superstructure formation. The schematic below breaks down the full end-to-end reconstruction flow.
99

1010
![Full chain](https://github.com/DeepLearnPhysics/spine/blob/develop/docs/source/_static/img/spine-chain-alpha.png)
1111

spine/ana/diag/track.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"""Module to evaluate diagnostic metrics on tracks."""
22

33
import numpy as np
4-
from scipy.spatial.distance import cdist
54

6-
from spine.ana.base import AnaBase
5+
from spine.math.distance import cdist
76

87
from spine.utils.globals import TRACK_SHP
9-
from spine.utils.numba_local import principal_components
108

9+
from spine.ana.base import AnaBase
1110

1211
__all__ = ['TrackCompletenessAna']
1312

@@ -142,10 +141,11 @@ def cluster_track_chunks(points, start_point, end_point, pixel_size):
142141
"""
143142
# Project and cluster on the projected axis
144143
direction = (end_point-start_point)/np.linalg.norm(end_point-start_point)
144+
scale = pixel_size*np.max(direction)
145145
projs = np.dot(points - start_point, direction)
146146
perm = np.argsort(projs)
147147
seps = projs[perm][1:] - projs[perm][:-1]
148-
breaks = np.where(seps > pixel_size*1.1)[0] + 1
148+
breaks = np.where(seps > scale*1.1)[0] + 1
149149
cluster_labels = np.empty(len(projs), dtype=int)
150150
for i, index in enumerate(np.split(np.arange(len(projs)), breaks)):
151151
cluster_labels[perm[index]] = i

spine/ana/metric/segment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def process(self, data):
111111
if self.ghost:
112112
# If there are ghost, must combine the predictions
113113
full_seg_pred = np.full_like(seg_label, GHOST_SHP, dtype=np.int32)
114-
deghost_mask = data['ghost'][:, 0] > data['ghost'][:, 1]
114+
deghost_mask = np.argmax(data['ghost'], axis=1) == 0
115115
full_seg_pred[deghost_mask] = seg_pred
116116
seg_pred = full_seg_pred
117117

spine/build/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ class BuilderBase(ABC):
3838

3939
# Necessary/optional data products to load a reconstructed object
4040
_load_reco_keys = (
41-
('points', True), ('depositions', True), ('sources', False)
41+
('points', False), ('depositions', False), ('sources', False)
4242
)
4343

4444
# Necessary/optional data products to load a truth object
4545
_load_truth_keys = (
46-
('points_label', True), ('points', False), ('points_g4', False),
47-
('depositions_label', True), ('depositions', False),
46+
('points_label', False), ('points', False), ('points_g4', False),
47+
('depositions_label', False), ('depositions', False),
4848
('depositions_q_label', False), ('depositions_g4', False),
4949
('sources_label', False), ('sources', False)
5050
)

spine/build/fragment.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def build_truth(self, data):
152152
"""
153153
return self._build_truth(**data)
154154

155-
def _build_truth(self, label_tensor, points_label, depositions_label,
155+
def _build_truth(self, label_tensor, points_label, depositions_label,
156156
depositions_q_label=None, label_adapt_tensor=None,
157157
points=None, depositions=None, label_g4_tensor=None,
158158
points_g4=None, depositions_g4=None, sources_label=None,
@@ -287,16 +287,17 @@ def load_reco(self, data):
287287
"""
288288
return self._load_reco(**data)
289289

290-
def _load_reco(self, reco_fragments, points, depositions, sources=None):
290+
def _load_reco(self, reco_fragments, points=None, depositions=None,
291+
sources=None):
291292
"""Load :class:`RecoFragment` objects from their stored versions.
292293
293294
Parameters
294295
----------
295296
reco_fragments : List[RecoFragment]
296297
(F) List of partial reconstructed fragments
297-
points : np.ndarray
298+
points : np.ndarray, optional
298299
(N, 3) Set of deposition coordinates in the image
299-
depositions : np.ndarray
300+
depositions : np.ndarray, optional
300301
(N) Set of deposition values
301302
sources : np.ndarray, optional
302303
(N, 2) Tensor which contains the module/tpc information
@@ -313,10 +314,11 @@ def _load_reco(self, reco_fragments, points, depositions, sources=None):
313314
"The ordering of the stored fragments is wrong.")
314315

315316
# Update the fragment with its long-form attributes
316-
fragment.points = points[fragment.index]
317-
fragment.depositions = depositions[fragment.index]
318-
if sources is not None:
319-
fragment.sources = sources[fragment.index]
317+
if points is not None:
318+
fragment.points = points[fragment.index]
319+
fragment.depositions = depositions[fragment.index]
320+
if sources is not None:
321+
fragment.sources = sources[fragment.index]
320322

321323
return reco_fragments
322324

@@ -335,20 +337,20 @@ def load_truth(self, data):
335337
"""
336338
return self._load_truth(**data)
337339

338-
def _load_truth(self, truth_fragments, points_label, depositions_label,
339-
depositions_q_label=None, points=None, depositions=None,
340-
points_g4=None, depositions_g4=None, sources_label=None,
341-
sources=None):
340+
def _load_truth(self, truth_fragments, points_label=None,
341+
depositions_label=None, depositions_q_label=None,
342+
points=None, depositions=None, points_g4=None,
343+
depositions_g4=None, sources_label=None, sources=None):
342344
"""Load :class:`TruthFragment` objects from their stored versions.
343345
344346
Parameters
345347
----------
346348
truth_fragments : List[TruthFragment]
347349
(F) List of partial truth fragments
348-
points_label : np.ndarray
350+
points_label : np.ndarray, optional
349351
(N', 3) Set of deposition coordinates in the label image (identical
350352
for pixel TPCs, different if deghosting is involved)
351-
depositions_label : np.ndarray
353+
depositions_label : np.ndarray, optional
352354
(N') Set of true deposition values in MeV
353355
depositions_q_label : np.ndarray, optional
354356
(N') Set of true deposition values in ADC, if relevant
@@ -377,12 +379,13 @@ def _load_truth(self, truth_fragments, points_label, depositions_label,
377379
"The ordering of the stored fragments is wrong.")
378380

379381
# Update the fragment with its long-form attributes
380-
fragment.points = points_label[fragment.index]
381-
fragment.depositions = depositions_label[fragment.index]
382-
if depositions_q_label is not None:
383-
fragment.depositions_q = depositions_q_label[fragment.index]
384-
if sources_label is not None:
385-
fragment.sources = sources_label[fragment.index]
382+
if points_label is not None:
383+
fragment.points = points_label[fragment.index]
384+
fragment.depositions = depositions_label[fragment.index]
385+
if depositions_q_label is not None:
386+
fragment.depositions_q = depositions_q_label[fragment.index]
387+
if sources_label is not None:
388+
fragment.sources = sources_label[fragment.index]
386389

387390
if points is not None:
388391
fragment.points_adapt = points[fragment.index_adapt]

spine/build/manager.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class BuildManager:
4444
)
4545

4646
def __init__(self, fragments, particles, interactions,
47-
mode='both', units='cm', sources=None):
47+
mode='both', units='cm', sources=None, lite=False):
4848
"""Initializes the build manager.
4949
5050
Parameters
@@ -60,6 +60,9 @@ def __init__(self, fragments, particles, interactions,
6060
sources : Dict[str, str], optional
6161
Dictionary which maps the necessary data products onto a name
6262
in the input/output dictionary of the reconstruction chain.
63+
lite : bool, default False
64+
If `True`, the objects being loaded are lite and do not map
65+
to long-form attributes. Simply load the matches.
6366
"""
6467
# Check on the mode, store it
6568
assert mode in self._run_modes, (
@@ -100,6 +103,9 @@ def __init__(self, fragments, particles, interactions,
100103
assert len(self.builders), (
101104
"Do not call the builder unless it does anything.")
102105

106+
# Store whether to load the long-form attributes or not
107+
self.lite = lite
108+
103109
def __call__(self, data):
104110
"""Build the representations for one entry.
105111
@@ -115,7 +121,7 @@ def __call__(self, data):
115121
# If this is the first time the builders are called, build
116122
# the objects shared between fragments/particles/interactions
117123
load = True
118-
if 'points' not in data and 'points_label' not in data:
124+
if not self.lite and 'points' not in data and 'points_label' not in data:
119125
load = False
120126
if np.isscalar(data['index']):
121127
sources = self.build_sources(data)

spine/build/particle.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -311,16 +311,17 @@ def load_reco(self, data):
311311
"""
312312
return self._load_reco(**data)
313313

314-
def _load_reco(self, reco_particles, points, depositions, sources=None):
314+
def _load_reco(self, reco_particles, points=None, depositions=None,
315+
sources=None):
315316
"""Construct :class:`RecoParticle` objects from their stored versions.
316317
317318
Parameters
318319
----------
319320
reco_particles : List[RecoParticle]
320321
(P) List of partial reconstructed particles
321-
points : np.ndarray
322+
points : np.ndarray, optional
322323
(N, 3) Set of deposition coordinates in the image
323-
depositions : np.ndarray
324+
depositions : np.ndarray, optional
324325
(N) Set of deposition values
325326
sources : np.ndarray, optional
326327
(N, 2) Tensor which contains the module/tpc information
@@ -337,10 +338,11 @@ def _load_reco(self, reco_particles, points, depositions, sources=None):
337338
"The ordering of the stored particles is wrong.")
338339

339340
# Update the particle with its long-form attributes
340-
particle.points = points[particle.index]
341-
particle.depositions = depositions[particle.index]
342-
if sources is not None:
343-
particle.sources = sources[particle.index]
341+
if points is not None:
342+
particle.points = points[particle.index]
343+
particle.depositions = depositions[particle.index]
344+
if sources is not None:
345+
particle.sources = sources[particle.index]
344346

345347
return reco_particles
346348

@@ -354,20 +356,20 @@ def load_truth(self, data):
354356
"""
355357
return self._load_truth(**data)
356358

357-
def _load_truth(self, truth_particles, points_label, depositions_label,
358-
depositions_q_label=None, points=None, depositions=None,
359-
points_g4=None, depositions_g4=None, sources_label=None,
360-
sources=None):
359+
def _load_truth(self, truth_particles, points_label=None,
360+
depositions_label=None, depositions_q_label=None,
361+
points=None, depositions=None, points_g4=None,
362+
depositions_g4=None, sources_label=None, sources=None):
361363
"""Construct :class:`TruthParticle` objects from their stored versions.
362364
363365
Parameters
364366
----------
365367
truth_particles : List[TruthParticle]
366368
(P) List of partial truth particles
367-
points_label : np.ndarray
369+
points_label : np.ndarray, optional
368370
(N', 3) Set of deposition coordinates in the label image (identical
369371
for pixel TPCs, different if deghosting is involved)
370-
depositions_label : np.ndarray
372+
depositions_label : np.ndarray, optional
371373
(N') Set of true deposition values in MeV
372374
depositions_q_label : np.ndarray, optional
373375
(N') Set of true deposition values in ADC, if relevant
@@ -396,12 +398,13 @@ def _load_truth(self, truth_particles, points_label, depositions_label,
396398
"The ordering of the stored particles is wrong.")
397399

398400
# Update the particle with its long-form attributes
399-
particle.points = points_label[particle.index]
400-
particle.depositions = depositions_label[particle.index]
401-
if depositions_q_label is not None:
402-
particle.depositions_q = depositions_q_label[particle.index]
403-
if sources_label is not None:
404-
particle.sources = sources_label[particle.index]
401+
if points_label is not None:
402+
particle.points = points_label[particle.index]
403+
particle.depositions = depositions_label[particle.index]
404+
if depositions_q_label is not None:
405+
particle.depositions_q = depositions_q_label[particle.index]
406+
if sources_label is not None:
407+
particle.sources = sources_label[particle.index]
405408

406409
if points is not None:
407410
particle.points_adapt = points[particle.index_adapt]

spine/driver.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
from .io import loader_factory, reader_factory, writer_factory
2424
from .io.write import CSVWriter
2525

26+
from .math import seed as numba_seed
27+
2628
from .utils.logger import logger
2729
from .utils.cuda import set_visible_devices
28-
from .utils.numba_local import seed as numba_seed
2930
from .utils.unwrap import Unwrapper
3031
from .utils.stopwatch import StopwatchManager
3132

@@ -367,7 +368,7 @@ def initialize_io(self, loader=None, reader=None, writer=None):
367368
# Fetch the list of previously run post-processors
368369
# TODO: this only works with two runs in a row, not 3 and above
369370
self.post_list = None
370-
if self.reader.cfg is not None:
371+
if self.reader.cfg is not None and 'post' in self.reader.cfg:
371372
self.post_list = tuple(self.reader.cfg['post'])
372373

373374
# Fetch an appropriate common prefix for all input files

spine/io/parse/cluster.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212

1313
import numpy as np
1414

15+
from spine.math.cluster import DBSCAN
16+
1517
from spine.data import Meta
1618

1719
from spine.utils.globals import DELTA_SHP, SHAPE_PREC
1820
from spine.utils.particles import process_particle_event
1921
from spine.utils.ppn import image_coordinates
2022
from spine.utils.conditional import larcv
21-
from spine.utils.numba_local import dbscan
2223

2324
from .base import ParserBase
2425
from .sparse import (
@@ -185,11 +186,13 @@ def __init__(self, dtype, particle_event=None, add_particle_info=False,
185186
self.type_include_mpr = type_include_mpr
186187
self.type_include_secondary = type_include_secondary
187188
self.primary_include_mpr = primary_include_mpr
188-
self.break_clusters = break_clusters
189-
self.break_eps = break_eps
190-
self.break_metric = break_metric
191189
self.shape_precedence = shape_precedence
192190

191+
# Intialize DBSCAN if the clusters are to be broken up
192+
self.break_clusters = break_clusters
193+
if break_clusters:
194+
self.dbscan = DBSCAN(break_eps, min_samples=1, metric=break_metric)
195+
193196
# Intialize the sparse and particle parsers
194197
self.sparse_parser = Sparse3DParser(dtype, sparse_event='dummy')
195198

@@ -334,8 +337,7 @@ def process(self, cluster_event, particle_event=None,
334337

335338
# If requested, break cluster into detached pieces
336339
if self.break_clusters:
337-
frag_labels = dbscan(
338-
voxels, self.break_eps, self.break_metric)
340+
frag_labels = self.dbscan.fit_predict(voxels)
339341
features[1] = id_offset + frag_labels
340342
id_offset += max(frag_labels) + 1
341343

0 commit comments

Comments
 (0)