class TIOReader(WaveformReader): def __init__(self, path, max_events=None, skip_events=2, skip_end_events=1): """ Utilies TargetIO to read R0 and R1 tio files. Enables easy access to the waveforms for anaylsis. Waveforms can be read from the file by either indexing this reader or iterating over it: >>> path = "/path/to/file_r0.tio" >>> reader = TIOReader(path) >>> wf = reader[3] # Obtain the waveforms for the third event >>> path = "/path/to/file_r0.tio" >>> reader = TIOReader(path) >>> wfs = reader[:10] # Obtain the waveforms for the first 10 events >>> path = "/path/to/file_r0.tio" >>> reader = TIOReader(path) >>> for wf in reader: # Iterate over all events in the file >>> print(wf) Parameters ---------- path : str Path to the _r0.tio or _r1.tio file max_events : int Maximum number of events to read from the file """ super().__init__(path, max_events) try: from target_io import WaveformArrayReader from target_calib import CameraConfiguration except ModuleNotFoundError: msg = ("Cannot find TARGET libraries, please follow installation " "instructions from https://forge.in2p3.fr/projects/gct/" "wiki/Installing_CHEC_Software") raise ModuleNotFoundError(msg) self._reader = WaveformArrayReader( self.path, skip_events, skip_end_events ) self.is_r1 = self._reader.fR1 self._n_events = self._reader.fNEvents self.run_id = self._reader.fRunID self.n_pixels = self._reader.fNPixels self.n_superpixels_per_module = self._reader.fNSuperpixelsPerModule self.n_modules = self._reader.fNModules self.n_tmpix = self.n_pixels // self.n_modules self.n_samples = self._reader.fNSamples self._camera_config = CameraConfiguration(self._reader.fCameraVersion) self.tc_mapping = self._camera_config.GetMapping(self.n_modules == 1) self.n_cells = self._camera_config.GetNCells() self.camera_version = self._camera_config.GetVersion() self.reference_pulse_path = self._camera_config.GetReferencePulsePath() self.current_tack = None self.current_cpu_ns = None self.current_cpu_s = None self.first_cell_ids = np.zeros(self.n_pixels, dtype=np.uint16) self.stale = np.zeros(self.n_pixels, dtype=np.uint8) if self.is_r1: self.samples = np.zeros((self.n_pixels, self.n_samples), dtype=np.float32) self.get_tio_event = self._reader.GetR1Event else: self.samples = np.zeros((self.n_pixels, self.n_samples), dtype=np.uint16) self.get_tio_event = self._reader.GetR0Event if max_events and max_events < self._n_events: self._n_events = max_events def _get_event(self, iev): self.index = iev try: # TODO: Remove try in future version self.get_tio_event(iev, self.samples, self.first_cell_ids, self.stale) except TypeError: warnings.warn( "This call to WaveformArrayReader has been deprecated. " "Please update TargetIO", SyntaxWarning ) self.get_tio_event(iev, self.samples, self.first_cell_ids) self.current_tack = self._reader.fCurrentTimeTack self.current_cpu_ns = self._reader.fCurrentTimeNs self.current_cpu_s = self._reader.fCurrentTimeSec return self.samples @staticmethod def is_compatible(path): with open(path, 'rb') as f: marker_bytes = f.read(1024) # if file is gzip, read the first 4 bytes with gzip again if marker_bytes[0] == 0x1f and marker_bytes[1] == 0x8b: with gzip.open(path, 'rb') as f: marker_bytes = f.read(1024) if b'FITS' not in marker_bytes: return False try: h = fits.getheader(path, 0) if 'EVENT_HEADER_VERSION' not in h: return False except IOError: return False return True @property def n_events(self): return self._n_events @property def t_cpu(self): return pd.to_datetime( np.int64(self.current_cpu_s * 1E9) + np.int64(self.current_cpu_ns), unit='ns' ) @property def mapping(self): return get_clp_mapping_from_tc_mapping(self.tc_mapping) def get_sn(self, tm): """ Get the SN of the TARGET module in a slot Parameters ---------- tm : int Slot number for the TARGET module Returns ------- int Serial number of the TM """ if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) return self._reader.GetSN(tm) def get_sipm_temp(self, tm): if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) return self._reader.GetSiPMTemp(tm) def get_primary_temp(self, tm): if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) return self._reader.GetPrimaryTemp(tm) def get_sp_dac(self, tm, sp): if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) if sp >= self.n_superpixels_per_module: raise IndexError("Requested SP out of range: {}".format(sp)) return self._reader.GetSPDAC(tm, sp) def get_sp_hvon(self, tm, sp): if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) if sp >= self.n_superpixels_per_module: raise IndexError("Requested SP out of range: {}".format(sp)) return self._reader.GetSPHVON(tm, sp)
class TIOReader: """ Reader for the R0 and R1 tio files """ def __init__(self, path, max_events=None): try: from target_io import WaveformArrayReader from target_calib import CameraConfiguration except ModuleNotFoundError: msg = ("Cannot find TARGET libraries, please follow installation " "instructions from https://forge.in2p3.fr/projects/gct/" "wiki/Installing_CHEC_Software") raise ModuleNotFoundError(msg) if not os.path.exists(path): raise FileNotFoundError("File does not exist: {}".format(path)) self.path = path self._reader = WaveformArrayReader(self.path, 2, 1) self.is_r1 = self._reader.fR1 self.n_events = self._reader.fNEvents self.run_id = self._reader.fRunID self.n_pixels = self._reader.fNPixels self.n_modules = self._reader.fNModules self.n_tmpix = self.n_pixels // self.n_modules self.n_samples = self._reader.fNSamples self._camera_config = CameraConfiguration(self._reader.fCameraVersion) self.tc_mapping = self._camera_config.GetMapping(self.n_modules == 1) self._pixel = self._PixelWaveforms(self) self.n_cells = self._camera_config.GetNCells() self.camera_version = self._camera_config.GetVersion() self.reference_pulse_path = self._camera_config.GetReferencePulsePath() self.current_tack = None self.current_cpu_ns = None self.current_cpu_s = None self.first_cell_ids = np.zeros(self.n_pixels, dtype=np.uint16) if self.is_r1: self.samples = np.zeros((self.n_pixels, self.n_samples), dtype=np.float32) self.get_tio_event = self._reader.GetR1Event else: self.samples = np.zeros((self.n_pixels, self.n_samples), dtype=np.uint16) self.get_tio_event = self._reader.GetR0Event if max_events and max_events < self.n_events: self.n_events = max_events def _get_event(self, iev): self.index = iev self.get_tio_event(iev, self.samples, self.first_cell_ids) self.current_tack = self._reader.fCurrentTimeTack self.current_cpu_ns = self._reader.fCurrentTimeNs self.current_cpu_s = self._reader.fCurrentTimeSec return self.samples def __iter__(self): for iev in range(self.n_events): yield self._get_event(iev) def __getitem__(self, iev): return np.copy(self._get_event(iev)) class _PixelWaveforms: def __init__(self, tio_reader): self.reader = tio_reader def __getitem__(self, p): if not isinstance(p, list) and not isinstance(p, np.ndarray): p = [p] n_events = self.reader.n_events n_pixels = len(p) n_samples = self.reader.n_samples waveforms = np.zeros((n_events, n_pixels, n_samples)) for iev, wf in enumerate(self.reader): waveforms[iev] = wf[p] return waveforms @property def pixel(self): return self._pixel @property def mapping(self): return get_clp_mapping_from_tc_mapping(self.tc_mapping) def get_sn(self, tm): if tm >= self.n_modules: raise IndexError("Requested TM out of range: {}".format(tm)) return self._reader.GetSN(tm) @staticmethod def is_compatible(filepath): try: h = fits.getheader(filepath, 0) if 'EVENT_HEADER_VERSION' not in h: return False except IOError: return False return True
class TargetIOEventSource(EventSource): """ EventSource for the targetio unofficial data format, the data format used by cameras containing TARGET modules, such as CHEC for the GCT SST. Extract waveform information from `target_io` to store them into `ctapipe.io.containers`. This Extractor can fill either the R0 or R1 event container, depending on the file being read (The header of the file is checked for a flag indicating that it has had R1 calibration applied to it). This reader requires the TARGET libraries. The instructions to install these libraries can be found here: https://forge.in2p3.fr/projects/gct/wiki/Installing_CHEC_Software Attributes ---------- _tio_reader : target_io.TargetIOEventReader() C++ event reader for TargetIO files. Handles the event building into an array of (n_pixels, n_samples) in C++, avoiding loops in Python _n_events : int number of events in the fits file _n_samples : int number of samples in the waveform _r0_samples : ndarray three dimensional array to store the R0 level waveform for each pixel (n_channels, n_pixels, n_samples) _r1_samples : ndarray three dimensional array to store the R1 level waveform for each pixel (n_channels, n_pixels, n_samples) _samples : ndarray pointer to the first index of either r0_samples or r1_samples (depending if the file has been R1 calibrated) for passing to TargetIO to be filled """ def __init__(self, config=None, parent=None, **kwargs): super().__init__(config=config, parent=parent, **kwargs) self._data = None self._event_index = None self._event_id = 0 self._time_tack = None self._time_sec = None self._time_ns = None self._reader = WaveformArrayReader(self.input_url, 2, 1) self._n_events = self._reader.fNEvents self._first_event_id = self._reader.fFirstEventID self._last_event_id = self._reader.fLastEventID self._obs_id = self._reader.fRunID n_modules = self._reader.fNModules n_pix = self._reader.fNPixels n_samples = self._reader.fNSamples self.camera_config = CameraConfiguration(self._reader.fCameraVersion) self._n_cells = self.camera_config.GetNCells() m = self.camera_config.GetMapping(n_modules == 1) self._optical_foclen = u.Quantity(2.15, u.m) self._mirror_area = u.Quantity(14.126, u.m ** 2) self._n_pixels = m.GetNPixels() self._xpix = np.array(m.GetXPixVector()) * u.m self._ypix = np.array(m.GetYPixVector()) * u.m self._refshape = np.zeros(10) # TODO: Get correct values for CHEC-S self._refstep = 0 # TODO: Get correct values for CHEC-S self._time_slice = 0 # TODO: Get correct values for CHEC-S self._chec_tel = 0 # Init fields self._r0_samples = None self._r1_samples = None self._first_cell_ids = np.zeros(n_pix, dtype=np.uint16) # Check if file is already r1 (Information obtained from a flag # in the file's header) is_r1 = self._reader.fR1 if is_r1: self._r1_samples = np.zeros( (1, n_pix, n_samples), dtype=np.float32 ) self._get_tio_event = self._reader.GetR1Event self._samples = self._r1_samples[0] else: self._r0_samples = np.zeros( (1, n_pix, n_samples), dtype=np.uint16 ) self._get_tio_event = self._reader.GetR0Event self._samples = self._r0_samples[0] self._init_container() @staticmethod def is_compatible(file_path): return file_path.endswith('.tio') def _init_container(self): """ Prepare the ctapipe event container, and fill it with the information that does not change with event, including the instrument information. """ chec_tel = 0 data = TargetIODataContainer() data.meta['origin'] = "targetio" data.meta['input'] = self.input_url data.meta['max_events'] = self.max_events # Instrument information camera = CameraGeometry( "CHEC", pix_id=np.arange(self._n_pixels), pix_x=self._xpix, pix_y=self._ypix, pix_area=None, pix_type='rectangular', ) optics = OpticsDescription( name="ASTRI", num_mirrors=2, equivalent_focal_length=self._optical_foclen, mirror_area=self._mirror_area, num_mirror_tiles=2, ) tel_descriptions = { chec_tel: TelescopeDescription( name="ASTRI", type="SST", camera=camera, optics=optics, ) } tel_positions = { chec_tel: u.Quantity(0, u.m) } data.inst.subarray =SubarrayDescription( "CHECMonoArray", tel_positions=tel_positions, tel_descriptions=tel_descriptions, ) self._data = data def _update_container(self): """ Update the ctapipe event containers with the information from the current event being pointed to in TargetIO. """ data = self._data chec_tel = 0 obs_id = self._obs_id event_id = self._event_id tels = {self._chec_tel} data.r0.obs_id = obs_id data.r0.event_id = event_id data.r0.tels_with_data = tels data.r1.obs_id = obs_id data.r1.event_id = event_id data.r1.tels_with_data = tels data.dl0.obs_id = obs_id data.dl0.event_id = event_id data.dl0.tels_with_data = tels data.trig.tels_with_trigger = [chec_tel] data.meta['tack'] = self._time_tack data.meta['sec'] = self._time_sec data.meta['ns'] = self._time_ns data.trig.gps_time = Time(self._time_sec * u.s, self._time_ns * u.ns, format='unix', scale='utc', precision=9) data.count = self._event_index data.r0.tel.clear() data.r1.tel.clear() data.dl0.tel.clear() data.dl1.tel.clear() data.mc.tel.clear() data.targetio.tel.clear() # load the data per telescope/chan data.r0.tel[chec_tel].waveform = self._r0_samples data.r1.tel[chec_tel].waveform = self._r1_samples # Load the TargetIO specific data per telescope/chan data.targetio.tel[chec_tel].first_cell_ids = self._first_cell_ids data.r0.tel[chec_tel].num_samples = self._samples.shape[-1] # Some information that currently exists in the mc container, but is # useful for real data (essentially the reference pulse shape, # which may be used in charge extraction methods) data.mc.tel[chec_tel].reference_pulse_shape = self._refshape data.mc.tel[chec_tel].meta['refstep'] = self._refstep data.mc.tel[chec_tel].time_slice = self._time_slice @property def _current_event_index(self): return self._event_index @_current_event_index.setter def _current_event_index(self, val): """ Setting the event index will cause the event to be saught from TargetIO, and the Containers to point to the correct event. The ctapipe event containers are then updated with this new event's information. """ self._event_index = val self._get_tio_event(val, self._samples, self._first_cell_ids) self._event_id = self._reader.fCurrentEventID self._time_tack = self._reader.fCurrentTimeTack self._time_sec = self._reader.fCurrentTimeSec self._time_ns = self._reader.fCurrentTimeNs self._update_container() def _generator(self): for self._current_event_index in range(self._n_events): yield self._data return def __len__(self): num = self._n_events if self.max_events and self.max_events < num: num = self.max_events return num def _get_event_by_index(self, index): self._current_event_index = index return self._data def _get_event_by_id(self, event_id): if ((event_id < self._first_event_id) | (event_id > self._last_event_id)): raise IndexError(f"Event id {event_id} not found in file") index = self._reader.GetEventIndex(event_id) return self._get_event_by_index(index)