def __init__(self, config, tool, extractor=None, cleaner=None, **kwargs): super().__init__(config=config, parent=tool, **kwargs) self.extractor = extractor if self.extractor is None: self.extractor = NeighbourPeakIntegrator(config, tool) self.cleaner = cleaner if self.cleaner is None: self.cleaner = NullWaveformCleaner(config, tool) self._dl0_empty_warn = False
class CameraDL1Calibrator(Component): """ The calibrator for DL1 charge extraction. Fills the dl1 container. It handles the integration correction and, if required, the list of neighbours. Parameters ---------- config : traitlets.loader.Config Configuration specified by config file or cmdline arguments. Used to set traitlet values. Set to None if no configuration to pass. tool : ctapipe.core.Tool or None Tool executable that is calling this component. Passes the correct logger to the component. Set to None if no Tool to pass. extractor : ctapipe.calib.camera.charge_extractors.ChargeExtractor The extractor to use to extract the charge from the waveforms. By default the NeighbourPeakIntegrator with default configuration is used. cleaner : ctapipe.calib.camera.waveform_cleaners.Cleaner The waveform cleaner to use. By default no cleaning is applied to the waveforms. kwargs """ name = 'CameraCalibrator' radius = Float(None, allow_none=True, help='Pixels within radius from a pixel are considered ' 'neighbours to the pixel. Set to None for the default ' '(1.4 * min_pixel_seperation).').tag(config=True) clip_amplitude = Float(None, allow_none=True, help='Amplitude in p.e. above which the signal is ' 'clipped. Set to None for no ' 'clipping.').tag(config=True) def __init__(self, config, tool, extractor=None, cleaner=None, **kwargs): super().__init__(config=config, parent=tool, **kwargs) self.extractor = extractor if self.extractor is None: self.extractor = NeighbourPeakIntegrator(config, tool) self.cleaner = cleaner if self.cleaner is None: self.cleaner = NullWaveformCleaner(config, tool) self._dl0_empty_warn = False def check_dl0_exists(self, event, telid): """ Check that dl0 data exists. If it does not, then do not change dl1. This ensures that if the containers were filled from a file containing dl1 data, it is not overwritten by non-existant data. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. Returns ------- bool True if dl0.tel[telid].pe_samples is not None, else false. """ dl0 = event.dl0.tel[telid].pe_samples if dl0 is not None: return True else: if not self._dl0_empty_warn: self.log.warning("Encountered an event with no DL0 data. " "DL1 is unchanged in this circumstance.") self._dl0_empty_warn = True return False @staticmethod def get_geometry(event, telid): """ Obtain the geometry for this telescope. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. The neighbours are calculated once per telescope. Returns ------- `CameraGeometry` """ return CameraGeometry.guess(*event.inst.pixel_pos[telid], event.inst.optical_foclen[telid]) def get_correction(self, event, telid): """ Obtain the integration correction for this telescope. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. The integration correction is calculated once per telescope. Returns ------- ndarray """ try: shift = self.extractor.window_shift width = self.extractor.window_width n_chan = event.inst.num_channels[telid] shape = event.mc.tel[telid].reference_pulse_shape step = event.mc.tel[telid].meta['refstep'] time_slice = event.mc.tel[telid].time_slice correction = integration_correction(n_chan, shape, step, time_slice, width, shift) return correction except (AttributeError, KeyError): # Don't apply correction when window_shift or window_width # does not exist in extractor, or when container does not have # a reference pulse shape return np.ones(event.inst.num_channels[telid]) def calibrate(self, event): """ Fill the dl1 container with the calibration data that results from the configuration of this calibrator. Parameters ---------- event : container A `ctapipe` event container """ for telid in event.dl0.tels_with_data: if self.check_dl0_exists(event, telid): waveforms = event.dl0.tel[telid].pe_samples n_samples = waveforms.shape[2] if n_samples == 1: # To handle ASTRI and dst corrected = waveforms[..., 0] window = np.ones(waveforms.shape) peakpos = np.zeros(waveforms.shape[0:2]) cleaned = waveforms else: # Clean waveforms cleaned = self.cleaner.apply(waveforms) # Extract charge if self.extractor.requires_neighbours(): e = self.extractor g = self.get_geometry(event, telid) e.neighbours = g.neighbor_matrix_where extract = self.extractor.extract_charge charge, peakpos, window = extract(cleaned) # Apply integration correction correction = self.get_correction(event, telid)[:, None] corrected = charge * correction # Clip amplitude if self.clip_amplitude: corrected[corrected > self.clip_amplitude] = \ self.clip_amplitude # Store into event container event.dl1.tel[telid].image = corrected event.dl1.tel[telid].extracted_samples = window event.dl1.tel[telid].peakpos = peakpos event.dl1.tel[telid].cleaned = cleaned
class CameraDL1Calibrator(Component): """ The calibrator for DL1 charge extraction. Fills the dl1 container. It handles the integration correction and, if required, the list of neighbours. Parameters ---------- config : traitlets.loader.Config Configuration specified by config file or cmdline arguments. Used to set traitlet values. Set to None if no configuration to pass. tool : ctapipe.core.Tool or None Tool executable that is calling this component. Passes the correct logger to the component. Set to None if no Tool to pass. extractor : ctapipe.calib.camera.charge_extractors.ChargeExtractor The extractor to use to extract the charge from the waveforms. By default the NeighbourPeakIntegrator with default configuration is used. cleaner : ctapipe.calib.camera.waveform_cleaners.Cleaner The waveform cleaner to use. By default no cleaning is applied to the waveforms. kwargs """ name = 'CameraCalibrator' radius = Float(None, allow_none=True, help='Pixels within radius from a pixel are considered ' 'neighbours to the pixel. Set to None for the default ' '(1.4 * min_pixel_seperation).').tag(config=True) clip_amplitude = Float(None, allow_none=True, help='Amplitude in p.e. above which the signal is ' 'clipped. Set to None for no ' 'clipping.').tag(config=True) def __init__(self, config, tool, extractor=None, cleaner=None, **kwargs): super().__init__(config=config, parent=tool, **kwargs) self.extractor = extractor if self.extractor is None: self.extractor = NeighbourPeakIntegrator(config, tool) self.cleaner = cleaner if self.cleaner is None: self.cleaner = NullWaveformCleaner(config, tool) self._dl0_empty_warn = False def check_dl0_exists(self, event, telid): """ Check that dl0 data exists. If it does not, then do not change dl1. This ensures that if the containers were filled from a file containing dl1 data, it is not overwritten by non-existant data. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. Returns ------- bool True if dl0.tel[telid].pe_samples is not None, else false. """ dl0 = event.dl0.tel[telid].pe_samples if dl0 is not None: return True else: if not self._dl0_empty_warn: self.log.warning("Encountered an event with no DL0 data. " "DL1 is unchanged in this circumstance.") self._dl0_empty_warn = True return False @staticmethod def get_geometry(event, telid): """ Obtain the geometry for this telescope. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. The neighbours are calculated once per telescope. Returns ------- `CameraGeometry` """ return CameraGeometry.guess(*event.inst.pixel_pos[telid], event.inst.optical_foclen[telid]) def get_correction(self, event, telid): """ Obtain the integration correction for this telescope. Parameters ---------- event : container A `ctapipe` event container telid : int The telescope id. The integration correction is calculated once per telescope. Returns ------- ndarray """ try: shift = self.extractor.window_shift width = self.extractor.window_width n_chan = event.inst.num_channels[telid] shape = event.mc.tel[telid].reference_pulse_shape step = event.mc.tel[telid].meta['refstep'] time_slice = event.mc.tel[telid].time_slice correction = integration_correction(n_chan, shape, step, time_slice, width, shift) return correction except (AttributeError, KeyError): # Don't apply correction when window_shift or window_width # does not exist in extractor, or when container does not have # a reference pulse shape return np.ones(event.inst.num_channels[telid]) def calibrate(self, event): """ Fill the dl1 container with the calibration data that results from the configuration of this calibrator. Parameters ---------- event : container A `ctapipe` event container """ for telid in event.dl0.tels_with_data: if self.check_dl0_exists(event, telid): waveforms = event.dl0.tel[telid].pe_samples n_samples = waveforms.shape[2] if n_samples == 1: # To handle ASTRI and dst corrected = waveforms window = np.ones(waveforms.shape) peakpos = np.zeros(waveforms.shape[0:2]) cleaned = waveforms else: # Clean waveforms cleaned = self.cleaner.apply(waveforms) # Extract charge if self.extractor.requires_neighbours(): e = self.extractor g = self.get_geometry(event, telid) e.neighbours = g.neighbor_matrix_where extract = self.extractor.extract_charge charge, peakpos, window = extract(cleaned) # Apply integration correction correction = self.get_correction(event, telid)[:, None] corrected = charge * correction # Clip amplitude if self.clip_amplitude: corrected[corrected > self.clip_amplitude] = \ self.clip_amplitude # Store into event container event.dl1.tel[telid].image = corrected event.dl1.tel[telid].extracted_samples = window event.dl1.tel[telid].peakpos = peakpos event.dl1.tel[telid].cleaned = cleaned