Source code for nectarchain.makers.component.waveforms_component
import copy
import logging
from argparse import ArgumentError
import numpy as np
import tqdm
from ctapipe.containers import EventType
from ctapipe.instrument import SubarrayDescription
from ctapipe_io_nectarcam import constants
from ctapipe_io_nectarcam.containers import NectarCAMDataContainer
from ...data.container import WaveformsContainer, WaveformsContainers
from .core import ArrayDataComponent
logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
log.handlers = logging.getLogger("__main__").handlers
__all__ = ["WaveformsComponent"]
[docs]
class WaveformsComponent(ArrayDataComponent):
SubComponents = copy.deepcopy(ArrayDataComponent.SubComponents)
SubComponents.read_only = True
def __init__(self, subarray, config=None, parent=None, *args, **kwargs):
super().__init__(
subarray=subarray, config=config, parent=parent, *args, **kwargs
)
self.__wfs_hg = {}
self.__wfs_lg = {}
[docs]
@staticmethod
def create_from_events_list(
events_list: list,
run_number: np.uint16,
npixels: np.uint16,
nsamples: np.uint8,
subarray: SubarrayDescription,
pixels_id: int,
tel_id: int,
camera: str,
) -> WaveformsContainer:
"""Create a container for the extracted waveforms from a list of events.
Args:
events_list (list[NectarCAMDataContainer]): A list of events to extract
waveforms from.
run_number (int): The ID of the run to be loaded.
npixels (int): The number of pixels in the waveforms.
nsamples (int): The number of samples in the waveforms.
subarray (SubarrayDescription): The subarray description instance.
pixels_id (int): The ID of the pixels to extract waveforms from.
tel_id (int): The ID of the telescope to extract waveforms from.
camera (str): The name of the camera.
Returns:
WaveformsContainer: A container object that contains the extracted waveforms
and other relevant information.
"""
container = WaveformsContainer(
run_number=run_number,
npixels=npixels,
nsamples=nsamples,
subarray=subarray,
camera=camera,
pixels_id=pixels_id,
)
ucts_timestamp = []
ucts_busy_counter = []
ucts_event_counter = []
event_type = []
event_id = []
trig_pattern_all = []
wfs_hg = []
wfs_lg = []
for event in tqdm(events_list):
ucts_timestamp.append(event.nectarcam.tel[tel_id].evt.ucts_timestamp)
ucts_busy_counter.append(event.nectarcam.tel[tel_id].evt.ucts_busy_counter)
ucts_event_counter.append(
event.nectarcam.tel[tel_id].evt.ucts_event_counter
)
event_type.append(event.trigger.event_type.value)
event_id.append(event.index.event_id)
trig_pattern_all.append(event.nectarcam.tel[tel_id].evt.trigger_pattern.T)
wfs_hg.append(event.r0.tel[tel_id].waveform[constants.HIGH_GAIN][pixels_id])
wfs_lg.append(event.r0.tel[tel_id].waveform[constants.HIGH_GAIN][pixels_id])
container.wfs_hg = np.array(wfs_hg, dtype=np.uint16)
container.wfs_lg = np.array(wfs_lg, dtype=np.uint16)
container.ucts_timestamp = np.array(ucts_timestamp, dtype=np.uint64)
container.ucts_busy_counter = np.array(ucts_busy_counter, dtype=np.uint32)
container.ucts_event_counter = np.array(ucts_event_counter, dtype=np.uint32)
container.event_type = np.array(event_type, dtype=np.uint8)
container.event_id = np.array(event_id, dtype=np.uint32)
container.trig_pattern_all = np.array(trig_pattern_all, dtype=bool)
container.trig_pattern = container.trig_pattern_all.any(axis=2)
container.multiplicity = np.uint16(
np.count_nonzero(container.trig_pattern, axis=1)
)
broken_pixels = __class__._compute_broken_pixels(
container.wfs_hg, container.wfs_lg
)
container.broken_pixels_hg = broken_pixels[0]
container.broken_pixels_lg = broken_pixels[1]
return container
def _init_trigger_type(self, trigger_type: EventType, **kwargs):
"""Initialize the waveformsMaker following the trigger type.
Args:
trigger_type: The type of trigger.
"""
super()._init_trigger_type(trigger_type, **kwargs)
name = __class__._get_name_trigger(trigger_type)
log.info(
f"initialization of the waveformsMaker following trigger type : {name}"
)
self.__wfs_hg[f"{name}"] = []
self.__wfs_lg[f"{name}"] = []
[docs]
def __call__(self, event: NectarCAMDataContainer, *args, **kwargs):
"""Process an event and extract waveforms.
Args:
event (NectarCAMDataContainer): The event to process and extract waveforms
from.
trigger (EventType): The type of trigger for the event.
"""
wfs_hg_tmp = np.zeros((self.npixels, self.nsamples), dtype=np.uint16)
wfs_lg_tmp = np.zeros((self.npixels, self.nsamples), dtype=np.uint16)
wfs_hg_tmp, wfs_lg_tmp = super(WaveformsComponent, self).__call__(
event=event, return_wfs=True, *args, **kwargs
)
name = __class__._get_name_trigger(event.trigger.event_type)
self.__wfs_hg[f"{name}"].append(wfs_hg_tmp)
self.__wfs_lg[f"{name}"].append(wfs_lg_tmp)
[docs]
def finish(self, *args, **kwargs):
"""Make the output container for the selected trigger types.
Args:
trigger_type (EventType): The selected trigger types.
Returns:
list[WaveformsContainer]: A list of output containers for the selected
trigger types.
"""
output = WaveformsContainers()
for i, trigger in enumerate(self.trigger_list):
waveformsContainer = WaveformsContainer(
run_number=WaveformsContainer.fields["run_number"].type(
self._run_number
),
npixels=WaveformsContainer.fields["npixels"].type(self._npixels),
nsamples=WaveformsContainer.fields["nsamples"].type(self._nsamples),
# subarray=self.subarray,
camera=self.camera_name,
pixels_id=WaveformsContainer.fields["pixels_id"].dtype.type(
self._pixels_id
),
nevents=self.nevents(trigger),
wfs_hg=self.wfs_hg(trigger),
wfs_lg=self.wfs_lg(trigger),
broken_pixels_hg=self.broken_pixels_hg(trigger),
broken_pixels_lg=self.broken_pixels_lg(trigger),
ucts_timestamp=self.ucts_timestamp(trigger),
ucts_busy_counter=self.ucts_busy_counter(trigger),
ucts_event_counter=self.ucts_event_counter(trigger),
event_type=self.event_type(trigger),
event_id=self.event_id(trigger),
trig_pattern_all=self.trig_pattern_all(trigger),
trig_pattern=self.trig_pattern(trigger),
multiplicity=self.multiplicity(trigger),
)
output.containers[trigger] = waveformsContainer
return output
[docs]
@staticmethod
def sort(waveformsContainer: WaveformsContainer, method: str = "event_id"):
"""Sort the waveformsContainer based on a specified method.
Args:
waveformsContainer (WaveformsContainer): The waveformsContainer
to be sorted.
method (str, optional): The sorting method. Defaults to 'event_id'.
Returns:
WaveformsContainer: The sorted waveformsContainer.
"""
output = WaveformsContainer(
run_number=waveformsContainer.run_number,
npixels=waveformsContainer.npixels,
nsamples=waveformsContainer.nsamples,
camera=waveformsContainer.camera,
pixels_id=waveformsContainer.pixels_id,
nevents=waveformsContainer.nevents,
)
if method == "event_id":
index = np.argsort(waveformsContainer.event_id)
for field in waveformsContainer.keys():
if not (
field
in [
"run_number",
"npixels",
"subarray",
"camera",
"pixels_id",
"nevents",
"nsamples",
]
):
output[field] = waveformsContainer[field][index]
else:
raise ArgumentError(None, f"{method} is not a valid method for sorting")
return output
[docs]
@staticmethod
def select_waveforms_hg(
waveformsContainer: WaveformsContainer,
pixel_id: np.ndarray,
):
"""Select HIGH GAIN waveforms from the container.
Args:
waveformsContainer (WaveformsContainer): The container object that contains
the waveforms.
pixel_id (np.ndarray): An array of pixel IDs to select specific waveforms
from the container.
Returns:
np.ndarray: An array of selected waveforms from the container.
"""
res = __class__.select_container_array_field(
container=waveformsContainer, pixel_id=pixel_id, field="wfs_hg"
)
res = res.transpose(1, 0, 2)
return res
[docs]
@staticmethod
def select_waveforms_lg(
waveformsContainer: WaveformsContainer, pixel_id: np.ndarray
):
"""Select LOW GAIN waveforms from the container.
Args:
waveformsContainer (WaveformsContainer): The container object that contains
the waveforms.
pixel_id (np.ndarray): An array of pixel IDs to select specific waveforms
from the container.
Returns:
np.ndarray: An array of selected waveforms from the container.
"""
res = __class__.select_container_array_field(
container=waveformsContainer, pixel_id=pixel_id, field="wfs_lg"
)
res = res.transpose(1, 0, 2)
return res
@property
def geometry(self):
"""Returns a deep copy of the geometry attribute.
Returns:
A deep copy of the geometry attribute.
"""
return self.camera.geometry
@property
def _wfs_lg(self):
"""Returns a deep copy of the wfs_lg attribute.
Returns:
A deep copy of the wfs_lg attribute.
"""
return copy.deepcopy(self.__wfs_lg)
@property
def _wfs_hg(self):
"""Returns a deep copy of the wfs_hg attribute.
Returns:
A deep copy of the wfs_hg attribute.
"""
return copy.deepcopy(self.__wfs_hg)
[docs]
def wfs_hg(self, trigger: EventType):
"""Returns the waveform data for the specified trigger type.
Args:
trigger (EventType): The type of trigger for which the waveform data is
requested.
Returns:
An array of waveform data for the specified trigger type.
"""
return np.array(
self.__wfs_hg[__class__._get_name_trigger(trigger)],
dtype=WaveformsContainer.fields["wfs_hg"].dtype,
)
[docs]
def wfs_lg(self, trigger: EventType):
"""Returns the waveform data for the specified trigger type in the low gain
channel.
Args:
trigger (EventType): The type of trigger for which the waveform data is
requested.
Returns:
An array of waveform data for the specified trigger type in the low gain
channel.
"""
return np.array(
self.__wfs_lg[__class__._get_name_trigger(trigger)],
dtype=WaveformsContainer.fields["wfs_lg"].dtype,
)