Source code for spine.data.larcv.particle

"""Module with a data class object which represents true particle information.

This copies the internal structure of :class:`larcv.Particle`.
"""

from dataclasses import dataclass, field
from warnings import warn

import numpy as np

from spine.constants import ParticlePID, ParticleShape
from spine.data.base import PosDataBase
from spine.data.decorator import stored_property
from spine.data.field import FieldMetadata

__all__ = ["Particle"]


[docs] @dataclass(eq=False, repr=False) class Particle(PosDataBase): """Particle truth information. Attributes ---------- id : int Index of the particle in the list mct_index : int Index in the original MCTruth array from whence it came mcst_index : int Index in the original MCTrack/MCShower array from whence it came group_id : int Index of the group the particle belongs to interaction_id : int Index of the interaction the partile belongs to nu_id : int Index of the neutrino this particle belongs to interaction_primary : int Whether the particle is primary in its interaction or not group_primary : int Whether this particle is primary in its group or not parent_id : int Index of the parent particle children_id : np.ndarray List of indexes of the children particles track_id : int Geant4 track ID parent_track_id : int Geant4 track ID of the parent particle ancestor_track_id : int Geant4 track ID of the ancestor particle shape : int Enumerated semantic type of the particle num_voxels : int Number of voxels matched to this particle instance energy_init : float True initial energy in MeV energy_deposit : float Amount of energy matched to this particle instance in MeV distance_travel : float True amount of distance traveled by the particle in the active volume creation_process : str Creation process parent_creation_process : str Creation process of the parent particle ancestor_creation_process : str Creation process of the ancestor particle pid : int Enumerated particle species type of the particle pdg_code : int Particle PDG code parent_pdg_code : int Particle PDG code of the parent particle ancestor_pdg_code : int Particle PDG code of the ancestor particle t : float Particle creation time (ns) end_t : float Particle death time (ns) parent_t : float Particle creation time of the parent particle (ns) ancestor_t : float Particle creation time of the ancestor particle (ns) position : np.ndarray Location of the creation point of the particle end_position : np.ndarray Location where the particle stopped parent_position : np.ndarry Location of the creation point of the parent particle ancestor_position : np.ndarray Location of the creation point of the ancestor particle first_step : np.ndarray Location of the first energy deposition of the particle last_step : np.ndarray Location of the last energy deposition of the particle momentum : np.ndarray 3-momentum of the particle at the production point end_momentum : np.ndarray 3-momentum of the particle at where it stops or exits the detector p : float Momentum magnitude of the particle at the production point end_p : float Momentum magnitude of the particle where it stops or exits the detector mass : float Rest mass of the particle in MeV/c^2 units : str Units in which the position attributes are expressed """ # Index attributes id: int = field(default=-1, metadata=FieldMetadata(index=True)) parent_id: int = field(default=-1, metadata=FieldMetadata(index=True)) group_id: int = field(default=-1, metadata=FieldMetadata(index=True)) interaction_id: int = field(default=-1, metadata=FieldMetadata(index=True)) nu_id: int = field(default=-1, metadata=FieldMetadata(index=True)) children_id: np.ndarray = field( default_factory=lambda: np.array([], dtype=np.int32), metadata=FieldMetadata(dtype=np.int32, index=True), ) # Scalar attributes mct_index: int = -1 mcst_index: int = -1 group_primary: int = -1 interaction_primary: int = -1 track_id: int = -1 parent_track_id: int = -1 ancestor_track_id: int = -1 pdg_code: int = -1 parent_pdg_code: int = -1 ancestor_pdg_code: int = -1 num_voxels: int = -1 t: float = field(default=np.nan, metadata=FieldMetadata(units="ns")) end_t: float = field(default=np.nan, metadata=FieldMetadata(units="ns")) parent_t: float = field(default=np.nan, metadata=FieldMetadata(units="ns")) ancestor_t: float = field(default=np.nan, metadata=FieldMetadata(units="ns")) energy_init: float = field(default=np.nan, metadata=FieldMetadata(units="MeV")) energy_deposit: float = field(default=np.nan, metadata=FieldMetadata(units="MeV")) distance_travel: float = field(default=np.nan, metadata=FieldMetadata(units="cm")) creation_process: str = "" parent_creation_process: str = "" ancestor_creation_process: str = "" units: str = "cm" # Enumerated attributes shape: int = field(default=-1, metadata=FieldMetadata(enum=ParticleShape)) pid: int = field(default=-1, metadata=FieldMetadata(enum=ParticlePID)) # Vector attributes position: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) end_position: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) parent_position: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) ancestor_position: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) first_step: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) last_step: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata( length=3, dtype=np.float32, position=True, units="instance" ), ) momentum: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata(length=3, dtype=np.float32, vector=True, units="MeV/c"), ) end_momentum: np.ndarray = field( default_factory=lambda: np.full(3, np.nan, dtype=np.float32), metadata=FieldMetadata(length=3, dtype=np.float32, vector=True, units="MeV/c"), ) @property @stored_property(units="MeV/c") def p(self) -> float: """Computes the magnitude of the initial momentum. Returns ------- float Norm of the initial momentum vector """ return float(np.linalg.norm(self.momentum)) @property @stored_property(units="MeV/c") def end_p(self) -> float: """Computes the magnitude of the final momentum. Returns ------- float Norm of the final momentum vector """ return float(np.linalg.norm(self.end_momentum)) @property @stored_property(units="MeV/c^2") def mass(self) -> float: """Computes the rest mass of the particle from its energy/momentum. Returns ------- float Rest mass of the particle in MeV/c^2 """ if np.isnan(self.energy_init) or np.isnan(self.momentum).any(): return np.nan return np.sqrt(max(0.0, self.energy_init**2 - np.sum(self.momentum**2)))
[docs] @classmethod def from_larcv(cls, particle) -> "Particle": """Builds and returns a Particle object from a LArCV Particle object. Parameters ---------- particle : larcv.Particle LArCV-format particle object Returns ------- Particle Particle object """ # Initialize the dictionary to initialize the object with obj_dict = {} # Load the scalar attributes for prefix in ("", "parent_", "ancestor_"): for key in ("track_id", "pdg_code", "creation_process", "t"): obj_dict[prefix + key] = getattr(particle, prefix + key)() for key in ( "id", "group_id", "interaction_id", "parent_id", "mct_index", "mcst_index", "num_voxels", "shape", "energy_init", "energy_deposit", "distance_travel", ): if not hasattr(particle, key): warn( f"The LArCV Particle object is missing the {key} " "attribute. It will miss from the Particle object." ) continue obj_dict[key] = getattr(particle, key)() obj_dict["end_t"] = particle.end_position().t() # Load the positional attribute axes = ("x", "y", "z") for key in ( "position", "end_position", "parent_position", "ancestor_position", "first_step", "last_step", ): vector = getattr(particle, key)() obj_dict[key] = np.asarray( [getattr(vector, a)() for a in axes], dtype=np.float32 ) # Load the other array attributes (special care needed). Note for future # self: need the list comprehension. Direct casting is INSANELY slow... obj_dict["children_id"] = np.asarray( [i for i in particle.children_id()], dtype=np.int32 ) mom_attrs = ("px", "py", "pz") for prefix in ("", "end_"): key = prefix + "momentum" if not hasattr(particle, key): warn( f"The LArCV Particle object is missing the {key} " "attribute. It will miss from the Particle object." ) continue obj_dict[key] = np.asarray( [getattr(particle, prefix + a)() for a in mom_attrs], dtype=np.float32 ) return cls(**obj_dict)