コード例 #1
0
ファイル: pedestals.py プロジェクト: yukihok/cta-lstchain
class PedestalCalculator(Component):
    """
    Parent class for the pedestal calculators.
    Fills the MON.pedestal container on the base of
    pedestal events (preliminary version)
    """

    tel_id = Int(
        0, help='id of the telescope to calculate the pedestal values').tag(
            config=True)
    sample_duration = Int(60,
                          help='sample duration in seconds').tag(config=True)
    sample_size = Int(10000, help='sample size').tag(config=True)
    n_channels = Int(2,
                     help='number of channels to be treated').tag(config=True)
    charge_cut_outliers = List(
        [3, 3], help='Interval (number of std) of accepted charge values').tag(
            config=True)
    charge_std_cut_outliers = List(
        [3, 3],
        help=
        'Interval (number of std) of accepted charge standard deviation values'
    ).tag(config=True)
    charge_product = Unicode(
        'LocalPeakIntegrator',
        help='Name of the charge extractor to be used').tag(config=True)

    def __init__(self, **kwargs):
        """
        Parent class for pedestal calculators.
        Fills the MON.pedestal container.

        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs

        """
        super().__init__(**kwargs)

        # initialize the output
        self.container = PedestalContainer()

        # load the waveform charge extractor
        self.extractor = ChargeExtractor.from_name(self.charge_product,
                                                   config=self.config)
        self.log.info(f"extractor {self.extractor}")

    @abstractmethod
    def calculate_pedestals(self, event):
        """calculate relative gain from event
コード例 #2
0
ファイル: flatfield.py プロジェクト: dkerszberg/cta-lstchain
class FlatFieldCalculator(Component):
    """
    Parent class for the flat field calculators.
    Fills the MON.flatfield container.
    """

    tel_id = Int(
        0, help='id of the telescope to calculate the flat-field coefficients'
    ).tag(config=True)
    sample_duration = Int(60,
                          help='sample duration in seconds').tag(config=True)
    sample_size = Int(10000, help='sample size').tag(config=True)
    n_channels = Int(2,
                     help='number of channels to be treated').tag(config=True)
    charge_cut_outliers = List(
        [-0.3, 0.3],
        help=
        'Interval of accepted charge values (fraction with respect to camera median value)'
    ).tag(config=True)
    time_cut_outliers = List(
        [10, 30],
        help='Interval (in samples) of accepted time values').tag(config=True)
    charge_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    def __init__(self, **kwargs):
        """
        Parent class for the flat field calculators.
        Fills the flatfield container.

        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs

        """
        super().__init__(**kwargs)
        # load the waveform charge extractor
        self.extractor = ImageExtractor.from_name(self.charge_product,
                                                  config=self.config)

        self.log.info(f"extractor {self.extractor}")

    @abstractmethod
    def calculate_relative_gain(self, event):
        """calculate relative gain from event
コード例 #3
0
ファイル: r0.py プロジェクト: pgrespan/cta-lstchain
class CameraR0Calibrator(Component):
    """
    The base R0-level calibrator. Change the r0 container.
    The R0 calibrator performs the camera-specific R0 calibration that is
    usually performed on the raw data by the camera server. This calibrator
    exists in lstchain for testing and prototyping purposes.
    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.
    kwargs
    """
    tel_id = Int(1,
                 help='id of the telescope to calibrate'
                 ).tag(config=True)

    offset = Int(default_value=400,
                 help='Define the offset of the baseline').tag(config=True)

    r1_sample_start = Int(default_value=3,
                          help='Start sample for r1 waveform',
                          allow_none=True).tag(config=True)

    r1_sample_end = Int(default_value=39,
                        help='End sample for r1 waveform',
                        allow_none=True).tag(config=True)

    def __init__(self, **kwargs):
        """
        Parent class for the r0 calibrators. Change the r0 container.
        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.
        kwargs
        """
        super().__init__(**kwargs)

    @abstractmethod
    def calibrate(self, event):
        """
コード例 #4
0
class DL3FixedCuts(Component):
    """
    Temporary fixed selection cuts for DL2 to DL3 conversion
    """

    fixed_gh_cut = Float(
        help="Fixed selection cut for gh_score (gammaness)",
        default_value=0.6,
    ).tag(config=True)

    fixed_theta_cut = Float(
        help="Fixed selection cut for theta",
        default_value=0.2,
    ).tag(config=True)

    allowed_tels = List(
        help="List of allowed LST telescope ids",
        trait=Int(),
        default_value=[1],
    ).tag(config=True)

    def gh_cut(self, data):
        return data[data["gh_score"] > self.fixed_gh_cut]

    def theta_cut(self, data):
        return data[data["theta"].to_value(u.deg) < self.fixed_theta_cut]

    def allowed_tels_filter(self, data):
        mask = np.zeros(len(data), dtype=bool)
        for tel_id in self.allowed_tels:
            mask |= data["tel_id"] == tel_id
        return data[mask]
コード例 #5
0
class LSTR0Corrections(CameraR0Calibrator):
    """
    The R0 calibrator class for LST Camera.
    """

    pedestal_path = Unicode(
        '', allow_none=True,
        help='Path to the LST pedestal binary file').tag(config=True)

    offset = Int(300,
                 help='Define the offset of the baseline').tag(config=True)

    tel_id = Int(0, help='id of the telescope to calibrate').tag(config=True)

    def __init__(self, **kwargs):
        """
        The R0 calibrator for LST data.
        Fill the r1 container.
        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs
        """
        super().__init__(**kwargs)
        self.n_module = 265
        self.n_gain = 2
        self.n_pix = 7
        self.size4drs = 4 * 1024
        self.roisize = 40
        self.high_gain = 0
        self.low_gain = 1

        self.pedestal_value_array = np.zeros(
            (self.n_gain, self.n_pix * self.n_module, self.size4drs + 40),
            dtype=np.int16)
        self.first_cap_array = np.zeros(
            (self.n_module, self.n_gain, self.n_pix))

        self.first_cap_time_lapse_array = np.zeros(
            (self.n_module, self.n_gain, self.n_pix))
        self.last_reading_time_array = np.zeros(
            (self.n_module, self.n_gain, self.n_pix, self.size4drs))

        self.first_cap_array_spike = np.zeros(
            (self.n_module, self.n_gain, self.n_pix))
        self.first_cap_old_array = np.zeros(
            (self.n_module, self.n_gain, self.n_pix))

        self._load_calib()

    def calibrate(self, event):
        for tel_id in event.r0.tels_with_data:
            self.subtract_pedestal(event)
            self.time_lapse_corr(event)
            self.interpolate_spikes(event)

            event.r1.tel[self.tel_id].trigger_type = event.r0.tel[
                self.tel_id].trigger_type
            event.r1.tel[self.tel_id].trigger_time = event.r1.tel[
                self.tel_id].trigger_time
            samples = event.r1.tel[tel_id].waveform[:, :,
                                                    self.r1_sample_start:self.
                                                    r1_sample_end]
            event.r1.tel[tel_id].waveform = samples.astype('float32')

    def subtract_pedestal(self, event):
        """
        Subtract cell offset using pedestal file.
        Fill the R1 container.
        Parameters
        ----------
        event : `ctapipe` event-container
        """
        n_modules = event.lst.tel[self.tel_id].svc.num_modules

        for nr_module in range(0, n_modules):
            self.first_cap_array[nr_module, :, :] = self._get_first_capacitor(
                event, nr_module)

        expected_pixel_id = event.lst.tel[self.tel_id].svc.pixel_ids
        samples = np.copy(event.r0.tel[self.tel_id].waveform)
        samples.astype('int16')
        samples = subtract_pedestal_jit(samples, expected_pixel_id,
                                        self.first_cap_array,
                                        self.pedestal_value_array, n_modules)
        event.r1.tel[self.tel_id].trigger_type = event.r0.tel[
            self.tel_id].trigger_type
        event.r1.tel[self.tel_id].trigger_time = event.r1.tel[
            self.tel_id].trigger_time
        event.r1.tel[self.tel_id].waveform = samples[:, :, :]

    def time_lapse_corr(self, event):
        """
        Perform time lapse baseline corrections.
        Fill the R1 container or
        modifies R0 container
        Parameters
        ----------
        event : `ctapipe` event-container
        """
        expected_pixel_id = event.lst.tel[self.tel_id].svc.pixel_ids
        local_clock_list = event.lst.tel[self.tel_id].evt.local_clock_counter
        n_modules = event.lst.tel[self.tel_id].svc.num_modules
        for nr_module in range(0, n_modules):
            self.first_cap_time_lapse_array[
                nr_module, :, :] = self._get_first_capacitor(event, nr_module)

        #If R1 container exist modifies it
        if isinstance(event.r1.tel[self.tel_id].waveform, np.ndarray):
            samples = event.r1.tel[self.tel_id].waveform
            do_time_lapse_corr(samples, expected_pixel_id, local_clock_list,
                               self.first_cap_time_lapse_array,
                               self.last_reading_time_array, n_modules)
            event.r1.tel[self.tel_id].trigger_type = event.r0.tel[
                self.tel_id].trigger_type
            event.r1.tel[self.tel_id].trigger_time = event.r1.tel[
                self.tel_id].trigger_time
            event.r1.tel[self.tel_id].waveform = samples[:, :, :]
        else:  # Modifies R0 container. This is for create pedestal file.
            samples = np.copy(event.r0.tel[self.tel_id].waveform)
            do_time_lapse_corr(samples, expected_pixel_id, local_clock_list,
                               self.first_cap_time_lapse_array,
                               self.last_reading_time_array, n_modules)
            event.r0.tel[self.tel_id].waveform = samples[:, :, :]

    def interpolate_spikes(self, event):
        """
        Interpolates spike A & B.
        Fill the R1 container.
        Parameters
        ----------
        event : `ctapipe` event-container
        """
        self.first_cap_old_array[:, :, :] = self.first_cap_array_spike[:, :, :]
        n_modules = event.lst.tel[self.tel_id].svc.num_modules
        for nr_module in range(0, n_modules):
            self.first_cap_array_spike[
                nr_module, :, :] = self._get_first_capacitor(event, nr_module)

        # Interpolate spikes should be done after pedestal subtraction and time lapse correction.
        if isinstance(event.r1.tel[self.tel_id].waveform, np.ndarray):
            waveform = event.r1.tel[self.tel_id].waveform[:, :, :]
            expected_pixel_id = event.lst.tel[self.tel_id].svc.pixel_ids
            samples = waveform.copy()
            samples = samples.astype('int16')
            event.r1.tel[
                self.tel_id].waveform = self.interpolate_pseudo_pulses(
                    samples, expected_pixel_id, self.first_cap_array_spike,
                    self.first_cap_old_array, n_modules)
            event.r1.tel[self.tel_id].trigger_type = event.r0.tel[
                self.tel_id].trigger_type
            event.r1.tel[self.tel_id].trigger_time = event.r1.tel[
                self.tel_id].trigger_time

    @staticmethod
    @jit(parallel=True)
    def interpolate_pseudo_pulses(waveform, expected_pixel_id, fc, fc_old,
                                  n_modules):
        """
        Interpolate Spike A & B.
        Change waveform array.
        Parameters
        ----------
        waveform : ndarray
            Waveform stored in a numpy array of shape
            (n_gain, n_pix, n_samples).
        expected_pixel_id: ndarray
            Array stored expected pixel id
            (n_pix*n_modules).
        fc : ndarray
            Value of first capacitor stored in a numpy array of shape
            (n_clus, n_gain, n_pix).
        fc_old : ndarray
            Value of first capacitor from previous event
            stored in a numpy array of shape
            (n_clus, n_gain, n_pix).
        n_modules : int
            Number of modules
        """
        roisize = 40
        size4drs = 4096
        n_gain = 2
        n_pix = 7
        for nr_module in prange(0, n_modules):
            for gain in prange(0, n_gain):
                for pix in prange(0, n_pix):
                    for k in prange(0, 4):
                        # looking for spike A first case
                        abspos = int(1024 - roisize - 2 -
                                     fc_old[nr_module, gain, pix] + k * 1024 +
                                     size4drs)
                        spike_A_position = int(
                            (abspos - fc[nr_module, gain, pix] + size4drs) %
                            size4drs)
                        if (spike_A_position > 2 and spike_A_position < 38):
                            pixel = expected_pixel_id[nr_module * 7 + pix]
                            interpolate_spike_A(waveform, gain,
                                                spike_A_position, pixel)
                        # looking for spike A second case
                        abspos = int(roisize - 2 +
                                     fc_old[nr_module, gain, pix] + k * 1024)
                        spike_A_position = int(
                            (abspos - fc[nr_module, gain, pix] + size4drs) %
                            size4drs)
                        if (spike_A_position > 2 and spike_A_position < 38):
                            pixel = expected_pixel_id[nr_module * 7 + pix]
                            interpolate_spike_A(waveform, gain,
                                                spike_A_position, pixel)

                    # looking for spike B
                    spike_b_position = int(
                        (fc_old[nr_module, gain, pix] - 1 -
                         fc[nr_module, gain, pix] + 2 * size4drs) % size4drs)
                    if spike_b_position < roisize - 1:
                        pixel = expected_pixel_id[nr_module * 7 + pix]
                        interpolate_spike_B(waveform, gain, spike_b_position,
                                            pixel)
        return waveform

    def _load_calib(self):
        """
        Function to load pedestal file.
        """
        if self.pedestal_path:
            with fits.open(self.pedestal_path) as f:
                pedestal_data = np.int16(f[1].data)
                self.pedestal_value_array[:, :, :self.
                                          size4drs] = pedestal_data - self.offset
                self.pedestal_value_array[:, :, self.size4drs:self.size4drs + 40] \
                    = pedestal_data[:, :, 0:40] - self.offset

    def _get_first_capacitor(self, event, nr_module):
        """
        Get first capacitor values from event for nr module.
        Parameters
        ----------
        event : `ctapipe` event-container
        nr_module : number of module
        """
        fc = np.zeros((2, 7))
        first_cap = event.lst.tel[
            self.tel_id].evt.first_capacitor_id[nr_module * 8:(nr_module + 1) *
                                                8]

        # First capacitor order according Dragon v5 board data format
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [0, 0, 1, 1, 2, 2, 3]):
            fc[self.high_gain, i] = first_cap[j]
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [4, 4, 5, 5, 6, 6, 7]):
            fc[self.low_gain, i] = first_cap[j]
        return fc
コード例 #6
0
class EventSource(Component):
    """
    Parent class for EventFileReaders of different sources.

    A new EventFileReader should be created for each type of event file read
    into ctapipe, e.g. sim_telarray files are read by the `SimTelEventSource`.

    EventFileReader provides a common high-level interface for accessing event
    information from different data sources (simulation or different camera
    file formats). Creating an EventFileReader for a new
    file format ensures that data can be accessed in a common way,
    irregardless of the file format.

    EventFileReader itself is an abstract class. To use an EventFileReader you
    must use a subclass that is relevant for the file format you
    are reading (for example you must use
    `ctapipe.io.SimTelEventSource` to read a hessio format
    file). Alternatively you can use `event_source()` to automatically
    select the correct EventFileReader subclass for the file format you wish
    to read.

    To create an instance of an EventFileReader you must pass the traitlet
    configuration (containing the input_url) and the
    `ctapipe.core.tool.Tool`. Therefore from inside a Tool you would do:

    >>> event_source = EventSource(self.config, self)

    An example of how to use `ctapipe.core.tool.Tool` and `event_source()`
    can be found in ctapipe/tools/display_dl1.py.

    However if you are not inside a Tool, you can still create an instance and
    supply an input_url via:

    >>> event_source = EventSource( input_url="/path/to/file")

    To loop through the events in a file:

    >>> event_source = EventSource( input_url="/path/to/file")
    >>> for event in event_source:
    >>>    print(event.count)

    **NOTE**: Every time a new loop is started through the event_source, it restarts
    from the first event.

    Alternatively one can use EventFileReader in a `with` statement to ensure
    the correct cleanups are performed when you are finished with the event_source:

    >>> with EventSource( input_url="/path/to/file") as event_source:
    >>>    for event in event_source:
    >>>       print(event.count)

    **NOTE**: The "event" that is returned from the generator is a pointer.
    Any operation that progresses that instance of the generator further will
    change the data pointed to by "event". If you wish to ensure a particular
    event is kept, you should perform a `event_copy = copy.deepcopy(event)`.


    Attributes
    ----------
    input_url : str
        Path to the input event file.
    max_events : int
        Maximum number of events to loop through in generator
    metadata : dict
        A dictionary containing the metadata of the file. This could include:
        * is_simulation (bool indicating if the file contains simulated events)
        * Telescope:Camera names (list if file contains multiple)
        * Information in the file header
        * Observation ID
    """

    input_url = Unicode(
        "", help="Path to the input file containing events.").tag(config=True)
    max_events = Int(
        None,
        allow_none=True,
        help="Maximum number of events that will be read from the file",
    ).tag(config=True)

    allowed_tels = Set(
        help=("list of allowed tel_ids, others will be ignored. "
              "If left empty, all telescopes in the input stream "
              "will be included")).tag(config=True)

    def __init__(self, config=None, parent=None, **kwargs):
        """
        Class to handle generic input files. Enables obtaining the "source"
        generator, regardless of the type of file (either hessio or camera
        file).

        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs
        """
        super().__init__(config=config, parent=parent, **kwargs)

        self.metadata = dict(is_simulation=False)
        input_url: Path = Path(self.input_url).expanduser()

        if not input_url.exists:
            raise FileNotFoundError(f"file path does not exist: '{input_url}'")
        self.log.info(f"INPUT PATH = {input_url}")

        if self.max_events:
            self.log.info(f"Max events being read = {self.max_events}")

        Provenance().add_input_file(input_url, role="DL0/Event")

    @staticmethod
    @abstractmethod
    def is_compatible(file_path):
        """
        Abstract method to be defined in child class.

        Perform a set of checks to see if the input file is compatible
        with this file event_source.

        Parameters
        ----------
        file_path : str
            File path to the event file.

        Returns
        -------
        compatible : bool
            True if file is compatible, False if it is incompatible
        """

    @property
    def is_stream(self):
        """
        Bool indicating if input is a stream. If it is then it is incompatible
        with `ctapipe.io.eventseeker.EventSeeker`.

        TODO: Define a method to detect if it is a stream

        Returns
        -------
        bool
            If True, then input is a stream.
        """
        return False

    @property
    @abstractmethod
    def subarray(self):
        """
        Obtain the subarray from the EventSource

        Returns
        -------
        ctapipe.instrument.SubarrayDecription

        """

    @abstractmethod
    def _generator(self):
        """
        Abstract method to be defined in child class.

        Generator where the filling of the `ctapipe.containers` occurs.

        Returns
        -------
        generator
        """

    def __iter__(self):
        """
        Generator that iterates through `_generator`, but keeps track of
        `self.max_events`.

        Returns
        -------
        generator
        """
        for event in self._generator():
            yield event
            if self.max_events and event.count >= self.max_events - 1:
                break

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    @classmethod
    def from_url(cls, input_url, **kwargs):
        """
        Find compatible EventSource for input_url via the `is_compatible`
        method of the EventSource

        Parameters
        ----------
        input_url : str
            Filename or URL pointing to an event file
        kwargs
            Named arguments for the EventSource

        Returns
        -------
        instance
            Instance of a compatible EventSource subclass
        """
        if input_url == "" or input_url is None:
            raise ToolConfigurationError(
                "EventSource: No input_url was specified")

        detect_and_import_io_plugins()
        available_classes = non_abstract_children(cls)

        for subcls in available_classes:
            if subcls.is_compatible(input_url):
                return subcls(input_url=input_url, **kwargs)

        raise ValueError("Cannot find compatible EventSource for \n"
                         "\turl:{}\n"
                         "in available EventSources:\n"
                         "\t{}".format(input_url,
                                       [c.__name__
                                        for c in available_classes]))

    @classmethod
    def from_config(cls, config=None, parent=None, **kwargs):
        """
        Find compatible EventSource for the EventSource.input_url traitlet
        specified via the config.

        This method is typically used in Tools, where the input_url is chosen via
        the command line using the traitlet configuration system.

        Parameters
        ----------
        config : traitlets.config.loader.Config
            Configuration created in the Tool
        kwargs
            Named arguments for the EventSource

        Returns
        -------
        instance
            Instance of a compatible EventSource subclass
        """
        if config is None:
            config = parent.config

        if isinstance(config.EventSource.input_url, LazyConfigValue):
            config.EventSource.input_url = cls.input_url.default_value
        elif not isinstance(config.EventSource.input_url, str):
            raise TraitError("Wrong type specified for input_url traitlet")
        return event_source(config.EventSource.input_url,
                            config=config,
                            **kwargs)
コード例 #7
0
class PointingPosition(Component):
    """
      Pointion position of telescopes

    """
    drive_path = Unicode(
        '',
        allow_none=True,
        help='Path to the LST drive report file'
    ).tag(config=True)

    tel_id = Int(
        0,
        help='id of the telescope to take drive report for'
    ).tag(config=True)

    def _read_drive_report(self):
        """
        Reading drive reports

        Parameters:
        -----------
        str: drive report file

        Returns:
        data:`~astropy.table.Table`
             A table of drive reports

        """
        self.log.info("Drive report file:", self.drive_path)
        if self.drive_path:
                data = ascii.read(self.drive_path)
            # Renaming the columns, since the drive report doesn't contain
            # these information it self
                data['col6'].name = 'time'
                data['col8'].name = 'azimuth_avg'
                data['col13'].name = 'zenith_avg'
                return data
        else:
            raise Exception("No drive report file found")

    def cal_pointingposition(self, ev_time, drive_data):
        """
        Calculating pointing positions by interpolation

        Parameters:
        -----------
        time: array
            times from events

        Drivereport: Container
            a container filled with drive information
        """
        drive_container = LSTDriveContainer()
        drive_container.time = drive_data['time'].data
        drive_container.azimuth_avg = drive_data['azimuth_avg'].data
        drive_container.altitude_avg =  90.0 - drive_data['zenith_avg'].data 
      

        xp = drive_container.time
        lower_drive_time = xp[xp < ev_time].max()
        upper_drive_time = xp[xp > ev_time].min()

        time_in_window = (xp >= lower_drive_time) & (xp <= upper_drive_time)
        run_times = xp[time_in_window]

        if len(run_times) > 1:
            run_azimuth = drive_container.azimuth_avg[time_in_window]
            run_altitude = drive_container.altitude_avg[time_in_window]

            ev_azimuth = np.interp(ev_time, run_times, run_azimuth) * u.deg
            ev_altitude = np.interp(ev_time, run_times, run_altitude) * u.deg
            return ev_azimuth, ev_altitude
        else:
            raise Exception("No drive time in the range of event times")
コード例 #8
0
ファイル: pedestals.py プロジェクト: vikasj78/ctapipe
class PedestalCalculator(Component):
    """
    Parent class for the pedestal calculators.
    Fills the MonitoringCameraContainer.PedestalContainer on the base of a given pedestal sample.
    The sample is defined by a maximal interval of time (sample_duration) or a
    minimal number of events (sample_duration).
    The calculator is supposed to act in an event loop, extract and collect the
    event charge and fill the PedestalContainer

    Parameters
    ----------
    tel_id : int
          id of the telescope (default 0)
    sample_duration : int
         interval of time (s) used to gather the pedestal statistics
    sample_size : int
         number of pedestal events requested for the statistics
    n_channels : int
         number of waveform channel to be considered
    charge_product : str
        Name of the charge extractor to be used
    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.

    kwargs

"""

    tel_id = Int(
        0,
        help='id of the telescope to calculate the pedestal values'
    ).tag(config=True)
    sample_duration = Int(
        60,
        help='sample duration in seconds'
    ).tag(config=True)
    sample_size = Int(
        10000,
        help='sample size'
    ).tag(config=True)
    n_channels = Int(
        2,
        help='number of channels to be treated'
    ).tag(config=True)
    charge_product = Unicode(
        'FixedWindowSum',
        help='Name of the charge extractor to be used'
    ).tag(config=True)

    def __init__(
        self,
        **kwargs
    ):
        """
        Parent class for the pedestal calculators.
        Fills the MonitoringCameraContainer.PedestalContainer on the base of a given pedestal sample.
        The sample is defined by a maximal interval of time (sample_duration) or a
        minimal number of events (sample_duration).
        The calculator is supposed to act in an event loop, extract and collect the
        event charge and fill the PedestalContainer

        Parameters
        ----------
        tel_id : int
              id of the telescope (default 0)
        sample_duration : int
             interval of time (s) used to gather the pedestal statistics
        sample_size : int
             number of pedestal events requested for the statistics
        n_channels : int
             number of waveform channel to be considered
        charge_product : str
            Name of the charge extractor to be used
        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.

        kwargs

    """

        super().__init__(**kwargs)

        # load the waveform charge extractor
        self.extractor = ImageExtractor.from_name(
            self.charge_product,
            config=self.config
        )
        self.log.info(f"extractor {self.extractor}")

    @abstractmethod
    def calculate_pedestals(self, event):
        """
コード例 #9
0
class TimeWaveformFitter(TelescopeComponent):
    """
    Class used to perform event reconstruction by fitting of a model on waveforms.
    """
    sigma_s = FloatTelescopeParameter(
        default_value=1,
        help='Width of the single photo-electron peak distribution.',
        allow_none=False).tag(config=True)
    crosstalk = FloatTelescopeParameter(default_value=0,
                                        help='Average pixel crosstalk.',
                                        allow_none=False).tag(config=True)
    sigma_space = Float(
        4,
        help=
        'Size of the region on which the fit is performed relative to the image extension.',
        allow_none=False).tag(config=True)
    sigma_time = Float(
        3,
        help=
        'Time window on which the fit is performed relative to the image temporal extension.',
        allow_none=False).tag(config=True)
    time_before_shower = FloatTelescopeParameter(
        default_value=10,
        help='Additional time at the start of the fit temporal window.',
        allow_none=False).tag(config=True)
    time_after_shower = FloatTelescopeParameter(
        default_value=20,
        help='Additional time at the end of the fit temporal window.',
        allow_none=False).tag(config=True)
    use_weight = Bool(
        False,
        help=
        'If True, the brightest sample is twice as important as the dimmest pixel in the '
        'likelihood. If false all samples are equivalent.',
        allow_none=False).tag(config=True)
    no_asymmetry = Bool(
        False,
        help='If true, the asymmetry of the spatial model is fixed to 0.',
        allow_none=False).tag(config=True)
    use_interleaved = Path(
        None,
        help=
        'Location of the dl1 file used to estimate the pedestal exploiting interleaved'
        ' events.',
        allow_none=True).tag(config=True)
    n_peaks = Int(
        0,
        help=
        'Maximum brightness (p.e.) for which the full likelihood computation is used. '
        'If the Poisson term for Np.e.>n_peak is more than 1e-6 a Gaussian approximation is used.',
        allow_none=False).tag(config=True)
    bound_charge_factor = FloatTelescopeParameter(
        default_value=4,
        help='Maximum relative change to the fitted charge parameter.',
        allow_none=False).tag(config=True)
    bound_t_cm_value = FloatTelescopeParameter(
        default_value=10,
        help='Maximum change to the t_cm parameter.',
        allow_none=False).tag(config=True)
    bound_centroid_control_parameter = FloatTelescopeParameter(
        default_value=1,
        help='Maximum change of the centroid coordinated in '
        'number of seed length',
        allow_none=False).tag(config=True)
    bound_max_length_factor = FloatTelescopeParameter(
        default_value=2,
        help='Maximum relative increase to the fitted length parameter.',
        allow_none=False).tag(config=True)
    bound_length_asymmetry = FloatTelescopeParameter(
        default_value=9,
        help='Bounds for the fitted rl parameter.',
        allow_none=False).tag(config=True)
    bound_max_v_cm_factor = FloatTelescopeParameter(
        default_value=2,
        help='Maximum relative increase to the fitted v_cm parameter.',
        allow_none=False).tag(config=True)
    default_seed_t_cm = FloatTelescopeParameter(
        default_value=0,
        help='Default starting value of t_cm when the seed extraction failed.',
        allow_none=False).tag(config=True)
    default_seed_v_cm = FloatTelescopeParameter(
        default_value=40,
        help='Default starting value of v_cm when the seed extraction failed.',
        allow_none=False).tag(config=True)
    verbose = Int(
        0,
        help='4 - used for tests: create debug plots\n'
        '3 - create debug plots, wait for input after each event, increase minuit verbose level\n'
        '2 - create debug plots, increase minuit verbose level\n'
        '1 - increase minuit verbose level\n'
        '0 - silent',
        allow_none=False).tag(config=True)

    def __init__(self, subarray, config=None, parent=None, **kwargs):
        super().__init__(subarray=subarray,
                         config=config,
                         parent=parent,
                         **kwargs)
        self.subarray = subarray
        self.template_dict = {}
        self.template_time_of_max_dict = {}
        for tel_id in subarray.tel:
            self.template_dict[
                tel_id] = NormalizedPulseTemplate.load_from_eventsource(
                    subarray.tel[tel_id].camera.readout)
            self.template_time_of_max_dict[tel_id] = self.template_dict[
                tel_id].compute_time_of_max()
        poisson_peaks = np.arange(self.n_peaks + 1, dtype=int)
        poisson_peaks[0] = 1
        self.factorial = np.cumprod(poisson_peaks, dtype='u8')
        # Find the transition charge between full likelihood computation and Gaussian approximation
        # The maximum charge is selected such that each Poisson terms in the full likelihood computation
        # above the n_peaks limit account for less than (1/n_peaks)%
        transition_charges = {}
        for config_crosstalk in self.crosstalk:
            # if n_peaks is set to 0, only the Gaussian approximation is used
            transition_charges[config_crosstalk[2]] = 0.0 if self.n_peaks == 0\
                else self.find_transition_charge(config_crosstalk[2], 1e-2/self.n_peaks)
        self.transition_charges = {}
        for tel_id in subarray.tel:
            self.transition_charges[tel_id] = transition_charges[
                self.crosstalk.tel[tel_id]]

        self.start_parameters = None
        self.names_parameters = None
        self.end_parameters = None
        self.error_parameters = None
        self.bound_parameters = None
        self.fcn = None

    def call_setup(self, event, telescope_id, dl1_container):
        """
        Extract all event dependent quantities used for the fit.

        Parameters
        ----------
        event: ctapipe event container
            Current event container.
        telescope_id: int
            Id of the telescope
        dl1_container: DL1ParametersContainer
            Contains the Hillas parameters used as seed for the fit

        Returns
        -------
        focal_length: astropy.units.Quantity
            Focal length of the telescope
        fit_params: array
            Array containing all the variable needed to compute the likelihood
            during the fir excluding the model parameters
        """

        geometry = self.subarray.tel[telescope_id].camera.geometry
        unit = geometry.pix_x.unit
        pix_x = geometry.pix_x.to_value(unit)
        pix_y = geometry.pix_y.to_value(unit)
        r_max = geometry.guess_radius().to_value(unit)
        pix_radius = np.sqrt(geometry.pix_area[0].to_value(unit**2) /
                             np.pi)  # find linear size of a pixel
        readout = self.subarray.tel[telescope_id].camera.readout
        sampling_rate = readout.sampling_rate.to_value(u.GHz)
        dt = (1.0 / sampling_rate)
        template = self.template_dict[telescope_id]
        image = event.dl1.tel[telescope_id].image
        hillas_signal_pixels = event.dl1.tel[telescope_id].image_mask
        start_x_cm, start_y_cm = init_centroid(dl1_container,
                                               geometry[hillas_signal_pixels],
                                               unit,
                                               image[hillas_signal_pixels],
                                               self.no_asymmetry)

        waveform = event.r1.tel[telescope_id].waveform

        dl1_calib = event.calibration.tel[telescope_id].dl1
        time_shift = dl1_calib.time_shift
        # TODO check if this is correct here or if it is applied to r1 waveform earlier
        if dl1_calib.pedestal_offset is not None:
            waveform = waveform - dl1_calib.pedestal_offset[:, np.newaxis]

        n_pixels, n_samples = waveform.shape
        times = np.arange(0, n_samples) * dt
        selected_gains = event.r1.tel[telescope_id].selected_gain_channel
        is_high_gain = (selected_gains == 0)

        # We assume that the time gradient is given in unit of 'geometry spatial unit'/ns
        v = dl1_container.time_gradient
        psi = dl1_container.psi.to_value(u.rad)
        # We use only positive time gradients and psi is projected in [-pi,pi] from [-pi/2,pi/2]
        if v < 0:
            if psi >= 0:
                psi = psi - np.pi
            else:
                psi = psi + np.pi

        start_length = max(dl1_container.length.to_value(unit), pix_radius)
        # With current likelihood computation, order and type of the parameters are important
        start_parameters = {
            'charge':
            dl1_container.intensity,
            't_cm':
            dl1_container.intercept -
            self.template_time_of_max_dict[telescope_id],
            'x_cm':
            start_x_cm.to_value(unit),
            'y_cm':
            start_y_cm.to_value(unit),
            'length':
            start_length,
            'wl':
            max(dl1_container.wl, 0.01),
            'psi':
            psi,
            'v':
            np.abs(v),
            'rl':
            0.0
        }

        # Temporal parameters extraction fails when cleaning select only 2 pixels, we use defaults values in this case
        if np.isnan(start_parameters['t_cm']):
            start_parameters['t_cm'] = self.default_seed_t_cm.tel[telescope_id]
        if np.isnan(start_parameters['v']):
            start_parameters['v'] = self.default_seed_v_cm.tel[telescope_id]

        t_max = n_samples * dt
        v_min, v_max = 0, max(
            self.bound_max_v_cm_factor.tel[telescope_id] *
            start_parameters['v'], 50)
        rl_min, rl_max = -self.bound_length_asymmetry.tel[
            telescope_id], self.bound_length_asymmetry.tel[telescope_id]
        if self.no_asymmetry:
            rl_min, rl_max = 0.0, 0.0
        bound_centroid = self.bound_centroid_control_parameter.tel[
            telescope_id] * start_length

        bound_parameters = {
            'charge': (dl1_container.intensity /
                       self.bound_charge_factor.tel[telescope_id],
                       dl1_container.intensity *
                       self.bound_charge_factor.tel[telescope_id]),
            't_cm': (-self.bound_t_cm_value.tel[telescope_id],
                     t_max + self.bound_t_cm_value.tel[telescope_id]),
            'x_cm': (start_x_cm.to_value(unit) - bound_centroid,
                     start_x_cm.to_value(unit) + bound_centroid),
            'y_cm': (start_y_cm.to_value(unit) - bound_centroid,
                     start_y_cm.to_value(unit) + bound_centroid),
            'length':
            (pix_radius,
             min(self.bound_max_length_factor.tel[telescope_id] * start_length,
                 r_max)),
            'wl': (0.001, 1.0),
            'psi': (-np.pi * 2.0, np.pi * 2.0),
            'v': (v_min, v_max),
            'rl': (rl_min, rl_max)
        }

        mask_pixel, mask_time = self.clean_data(pix_x, pix_y, pix_radius,
                                                times, start_parameters,
                                                telescope_id)
        spatial_ones = np.ones(np.sum(mask_pixel))

        is_high_gain = is_high_gain[mask_pixel]
        sig_s = spatial_ones * self.sigma_s.tel[telescope_id]
        crosstalks = spatial_ones * self.crosstalk.tel[telescope_id]

        times = (np.arange(0, n_samples) * dt)[mask_time]
        time_shift = time_shift[mask_pixel]

        p_x = pix_x[mask_pixel]
        p_y = pix_y[mask_pixel]
        pix_area = geometry.pix_area[mask_pixel].to_value(unit**2)

        data = waveform
        error = None  # TODO include option to use calibration data

        filter_pixels = np.nonzero(~mask_pixel)
        filter_times = np.nonzero(~mask_time)

        if error is None:
            std = np.std(data[~mask_pixel])
            error = np.full(data.shape[0], std)

        data = np.delete(data, filter_pixels, axis=0)
        data = np.delete(data, filter_times, axis=1)
        error = np.delete(error, filter_pixels, axis=0)

        # Fill the set of non-fitted parameters needed to compute the likelihood. Order and type sensitive.
        fit_params = [
            data, error, is_high_gain, sig_s, crosstalks, times,
            np.float32(time_shift), p_x, p_y,
            np.float64(pix_area), template.dt, template.t0,
            template.amplitude_LG, template.amplitude_HG, self.n_peaks,
            self.transition_charges[telescope_id], self.use_weight,
            self.factorial
        ]

        self.start_parameters = start_parameters
        self.names_parameters = start_parameters.keys()
        self.bound_parameters = bound_parameters

        return unit, fit_params

    def __call__(self, event, telescope_id, dl1_container):
        # setup angle to distance conversion on the camera plane for the current telescope
        focal_length = self.subarray.tel[
            telescope_id].optics.equivalent_focal_length
        angle_dist_eq = [
            (u.rad, u.m, lambda x: np.tan(x) * focal_length.to_value(u.m),
             lambda x: np.arctan(x / focal_length.to_value(u.m))),
            (u.rad**2, u.m**2, lambda x:
             (np.tan(np.sqrt(x)) * focal_length.to_value(u.m))**2, lambda x:
             (np.arctan(np.sqrt(x) / focal_length.to_value(u.m)))**2)
        ]
        with u.set_enabled_equivalencies(angle_dist_eq):
            self.start_parameters = None
            self.names_parameters = None
            unit_cam, fit_params = self.call_setup(event, telescope_id,
                                                   dl1_container)
            self.end_parameters = None
            self.error_parameters = None
            self.fcn = None

            return self.predict(unit_cam, fit_params)

    def clean_data(self, pix_x, pix_y, pix_radius, times, start_parameters,
                   telescope_id):
        """
        Method used to select pixels and time samples used in the fitting procedure.
        The spatial selection takes pixels in an ellipsis obtained from the seed Hillas parameters extended by one pixel
        size and multiplied by a factor sigma_space.
        The temporal selection takes a time window centered on the seed time of center of mass and of duration equal to
        the time of propagation of the signal along the length of the ellipsis times a factor sigma_time.
        An additional fixed duration is also added before and after this time window through the time_before_shower and
        time_after_shower arguments.

        Parameters
        ----------
        pix_x, pix_y: array-like
            Pixels positions
        pix_radius: float
        times: array-like
            Sampling times before timeshift corrections
        start_parameters: dict
            Seed parameters derived from the Hillas parameters
        telescope_id: int

        Returns
        ----------
        mask_pixel, mask_time: array-like
            Mask used to select pixels and times for the fit

        """
        x_cm = start_parameters['x_cm']
        y_cm = start_parameters['y_cm']
        length = start_parameters['length']
        width = start_parameters['wl'] * length
        psi = start_parameters['psi']

        dx = pix_x - x_cm
        dy = pix_y - y_cm

        lon = dx * np.cos(psi) + dy * np.sin(psi)
        lat = dx * np.sin(psi) - dy * np.cos(psi)

        mask_pixel = ((lon / (length + pix_radius))**2 +
                      (lat / (width + pix_radius))**2) < self.sigma_space**2

        v = start_parameters['v']
        t_start = (start_parameters['t_cm'] -
                   (np.abs(v) * length / 2 * self.sigma_time) -
                   self.time_before_shower.tel[telescope_id])
        t_end = (start_parameters['t_cm'] +
                 (np.abs(v) * length / 2 * self.sigma_time) +
                 self.time_after_shower.tel[telescope_id])

        mask_time = (times < t_end) * (times > t_start)

        return mask_pixel, mask_time

    def find_transition_charge(self, crosstalk, poisson_proba_min=1e-2):
        """
        Find the charge below which the full likelihood computation is performed and above which a Gaussian
        approximation is used. For a given pixel crosstalk it finds the maximum charge with a Generalised Poisson term
        below poisson_proba_min for n_peaks photo-electrons. n_peaks here is the configured maximum number of
        photo-electron considered in the full likelihood computation.

        Parameters
        ----------
        crosstalk : float
            Pixels crosstalk
        poisson_proba_min: float

        Returns
        -------
        transition_charge: float32
            Model charge of transition between full and approximated likelihood

        """
        transition_charge = self.n_peaks / (1 + crosstalk)
        step = transition_charge / 100

        def poisson(mu, cross_talk):
            return (mu * pow(mu + self.n_peaks * cross_talk,
                             (self.n_peaks - 1)) /
                    self.factorial[self.n_peaks] *
                    np.exp(-mu - self.n_peaks * cross_talk))

        while poisson(transition_charge, crosstalk) > poisson_proba_min:
            transition_charge -= step
        logger.info(
            f'Transition charge between full and approximated likelihood for camera '
            f'with crosstalk = {crosstalk:.4f} is,  {transition_charge:.4f}, p.e.'
        )
        return np.float32(transition_charge)

    def fit(self, fit_params):
        """
        Performs the fitting procedure.

        Parameters
        ----------
        fit_params: array
            Parameters used to compute the likelihood but not fitted

        """
        def f(*args):
            return -2 * self.log_likelihood(*args, fit_params=fit_params)

        print_level = 2 if self.verbose in [1, 2, 3] else 0
        m = Minuit(f,
                   name=self.names_parameters,
                   *self.start_parameters.values())
        for key, val in self.bound_parameters.items():
            m.limits[key] = val
        m.print_level = print_level
        m.errordef = 0.5
        m.simplex().migrad()
        self.end_parameters = m.values.to_dict()
        self.fcn = m.fval
        self.error_parameters = m.errors.to_dict()

    def predict(self, unit_cam, fit_params):
        """
            Call the fitting procedure and fill the results.

        Parameters
        ----------
        unit_cam: astropy.units.unit
            Unit used for the camera geometry and for spatial variable in the fit
        fit_params: array
            Parameters used to compute the likelihood but not fitted

        Returns
        ----------
        container: DL1LikelihoodParametersContainer
            Filled parameter container

        """
        container = DL1LikelihoodParametersContainer(lhfit_call_status=1)
        try:
            self.fit(fit_params)
            container.lhfit_TS = self.fcn

            container.lhfit_x = (self.end_parameters['x_cm'] * unit_cam).to(
                u.m)
            container.lhfit_x_uncertainty = (self.error_parameters['x_cm'] *
                                             unit_cam).to(u.m)
            container.lhfit_y = (self.end_parameters['y_cm'] * unit_cam).to(
                u.m)
            container.lhfit_y_uncertainty = (self.error_parameters['y_cm'] *
                                             unit_cam).to(u.m)
            container.lhfit_r = np.sqrt(container.lhfit_x**2 +
                                        container.lhfit_y**2)
            container.lhfit_phi = np.arctan2(container.lhfit_y,
                                             container.lhfit_x)
            if self.end_parameters['psi'] > np.pi:
                self.end_parameters['psi'] -= 2 * np.pi
            if self.end_parameters['psi'] < -np.pi:
                self.end_parameters['psi'] += 2 * np.pi
            container.lhfit_psi = self.end_parameters['psi'] * u.rad
            container.lhfit_psi_uncertainty = self.error_parameters[
                'psi'] * u.rad
            length_asy = 1 + self.end_parameters['rl'] if self.end_parameters[
                'rl'] >= 0 else 1 / (1 - self.end_parameters['rl'])
            lhfit_length = ((
                (1.0 + length_asy) * self.end_parameters['length'] / 2.0) *
                            unit_cam).to(u.deg)
            container.lhfit_length = lhfit_length
            lhfit_length_rel_err = self.error_parameters[
                'length'] / self.end_parameters['length']
            # We assume that the relative error is the same in the fitted and saved unit
            container.lhfit_length_uncertainty = lhfit_length_rel_err * container.lhfit_length
            container.lhfit_width = self.end_parameters[
                'wl'] * container.lhfit_length

            container.lhfit_time_gradient = self.end_parameters['v']
            container.lhfit_time_gradient_uncertainty = self.error_parameters[
                'v']
            container.lhfit_ref_time = self.end_parameters['t_cm']
            container.lhfit_ref_time_uncertainty = self.error_parameters[
                't_cm']

            container.lhfit_wl = u.Quantity(self.end_parameters['wl'])
            container.lhfit_wl_uncertainty = u.Quantity(
                self.error_parameters['wl'])
            container.lhfit_intensity = self.end_parameters['charge']
            container.lhfit_intensity_uncertainty = self.error_parameters[
                'charge']
            container.lhfit_log_intensity = np.log10(container.lhfit_intensity)
            container.lhfit_t_68 = container.lhfit_length.value * container.lhfit_time_gradient
            container.lhfit_area = container.lhfit_length * container.lhfit_width
            container.lhfit_length_asymmetry = self.end_parameters['rl']
            container.lhfit_length_asymmetry_uncertainty = self.error_parameters[
                'rl']
        except ZeroDivisionError:
            # TODO Check occurrence rate and solve
            container = DL1LikelihoodParametersContainer(lhfit_call_status=-1)
            logger.error(
                'ZeroDivisionError encounter during the fitting procedure, skipping event.'
            )

        return container

    def __str__(self):
        """
            Define the print format of TimeWaveformFitter objects.

            Returns
            -------
            str: string
                Contains the starting and bound parameters used for the fit,
                and the end results with errors and associated log-likelihood
                in readable format.

        """
        s = 'Event processed\n'
        s += 'Start parameters :\n\t{}\n'.format(self.start_parameters)
        s += 'Bound parameters :\n\t{}\n'.format(self.bound_parameters)
        s += 'End parameters :\n\t{}\n'.format(self.end_parameters)
        s += 'Error parameters :\n\t{}\n'.format(self.error_parameters)
        s += '-2Log-Likelihood :\t{}'.format(self.fcn)

        return s

    @staticmethod
    def log_likelihood(*args, fit_params, **kwargs):
        """Compute the log-likelihood used in the fitting procedure."""
        llh = log_pdf(*args, *fit_params, **kwargs)
        return np.sum(llh)
コード例 #10
0
class EventSource(Component):
    """
    Parent class for EventSources.

    EventSources read input files and generate `ArrayEvents`
    when iterated over.

    A new EventSource should be created for each type of event file read
    into ctapipe, e.g. sim_telarray files are read by the `SimTelEventSource`.

    EventSource provides a common high-level interface for accessing event
    information from different data sources (simulation or different camera
    file formats). Creating an EventSource for a new
    file format or other event source ensures that data can be accessed in a common way,
    irregardless of the file format or data origin.

    EventSource itself is an abstract class, but will create an
    appropriate subclass if a compatible source is found for the given
    ``input_url``.

    >>> dataset = get_dataset_path('gamma_test_large.simtel.gz')
    >>> event_source = EventSource(input_url=dataset)
    <ctapipe.io.simteleventsource.SimTelEventSource at ...>

    An ``EventSource`` can also be created through the configuration system,
    by passing ``config`` or ``parent`` as appropriate.
    E.g. if using ``EventSource`` inside of a ``Tool``, you would do:
    >>> self.event_source = EventSource(parent=self)

    To loop through the events in a file:
    >>> event_source = EventSource(input_url="/path/to/file")
    >>> for event in event_source:
    >>>    print(event.count)

    **NOTE**: Every time a new loop is started through the event_source,
    it tries to restart from the first event, which might not be supported
    by the event source.

    It is encouraged to use ``EventSource`` in a context manager to ensure
    the correct cleanups are performed when you are finished with the event_source:

    >>> with EventSource(input_url="/path/to/file") as event_source:
    >>>    for event in event_source:
    >>>       print(event.count)

    **NOTE**: For effiency reasons, most sources only use a single ``ArrayEvent`` instance
    and update it with new data on iteration, which might lead to surprising
    behaviour if you want to access multiple events at the same time.
    To keep an event and prevent its data from being overwritten with the next event's data,
    perform a deepcopy: ``some_special_event = copy.deepcopy(event)``.


    Attributes
    ----------
    input_url : str
        Path to the input event file.
    max_events : int
        Maximum number of events to loop through in generator
    allowed_tels: Set[int] or None
        Ids of the telescopes to be included in the data.
        If given, only this subset of telescopes will be present in the
        generated events. If None, all available telescopes are used.
    """

    input_url = Path(
        directory_ok=False,
        exists=True,
        help="Path to the input file containing events.",
    ).tag(config=True)

    max_events = Int(
        None,
        allow_none=True,
        help="Maximum number of events that will be read from the file",
    ).tag(config=True)

    allowed_tels = Set(
        default_value=None,
        allow_none=True,
        help=(
            "list of allowed tel_ids, others will be ignored. "
            "If None, all telescopes in the input stream "
            "will be included"
        ),
    ).tag(config=True)

    def __new__(cls, input_url=None, config=None, parent=None, **kwargs):
        """
        Returns a compatible subclass for given input url, either
        directly or via config / parent
        """
        # needed to break recursion, as __new__ of subclass will also
        # call this method
        if cls is not EventSource:
            return super().__new__(cls)

        # check we have at least one of these to be able to determine the subclass
        if input_url is None and config is None and parent is None:
            raise ValueError("One of `input_url`, `config`, `parent` is required")

        if input_url is None:
            input_url = cls._find_input_url_in_config(config=config, parent=parent)

        subcls = cls._find_compatible_source(input_url)
        return super().__new__(subcls)

    def __init__(self, input_url=None, config=None, parent=None, **kwargs):
        """
        Class to handle generic input files. Enables obtaining the "source"
        generator, regardless of the type of file (either hessio or camera
        file).

        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs
        """
        # traitlets differentiates between not getting the kwarg
        # and getting the kwarg with a None value.
        # the latter overrides the value in the config with None, the former
        # enables getting it from the config.
        if input_url is not None:
            kwargs["input_url"] = input_url

        super().__init__(config=config, parent=parent, **kwargs)

        self.metadata = dict(is_simulation=False)
        self.log.info(f"INPUT PATH = {self.input_url}")

        if self.max_events:
            self.log.info(f"Max events being read = {self.max_events}")

        Provenance().add_input_file(str(self.input_url), role="DL0/Event")

    @staticmethod
    @abstractmethod
    def is_compatible(file_path):
        """
        Abstract method to be defined in child class.

        Perform a set of checks to see if the input file is compatible
        with this file event_source.

        Parameters
        ----------
        file_path : str
            File path to the event file.

        Returns
        -------
        compatible : bool
            True if file is compatible, False if it is incompatible
        """

    @property
    def is_stream(self):
        """
        Bool indicating if input is a stream. If it is then it is incompatible
        with `ctapipe.io.eventseeker.EventSeeker`.

        TODO: Define a method to detect if it is a stream

        Returns
        -------
        bool
            If True, then input is a stream.
        """
        return False

    @property
    @abstractmethod
    def subarray(self):
        """
        Obtain the subarray from the EventSource

        Returns
        -------
        ctapipe.instrument.SubarrayDecription

        """

    @property
    @abstractmethod
    def is_simulation(self):
        """
        Weither the currently opened file is simulated

        Returns
        -------
        bool

        """

    @property
    @abstractmethod
    def datalevels(self):
        """
        The datalevels provided by this event source

        Returns
        -------
        tuple[ctapipe.io.DataLevel]
        """

    def has_any_datalevel(self, datalevels):
        """
        Check if any of `datalevels` is in self.datalevels

        Parameters:
        -----------
        datalevels: Iterable
            Iterable of datalevels
        """
        return any(dl in self.datalevels for dl in datalevels)

    @property
    @abstractmethod
    def obs_ids(self):
        """
        The observation ids of the runs located in the file
        Unmerged files should only contain a single obs id.

        Returns
        -------
        list[int]
        """

    @abstractmethod
    def _generator(self):
        """
        Abstract method to be defined in child class.

        Generator where the filling of the `ctapipe.containers` occurs.

        Returns
        -------
        generator
        """

    def __iter__(self):
        """
        Generator that iterates through `_generator`, but keeps track of
        `self.max_events`.

        Returns
        -------
        generator
        """
        for event in self._generator():
            yield event
            if self.max_events and event.count >= self.max_events - 1:
                break

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    @classmethod
    def _find_compatible_source(cls, input_url):
        if input_url == "" or input_url is None:
            raise ToolConfigurationError("EventSource: No input_url was specified")

        # validate input url with the traitel validate method
        # to make sure it's compatible and to raise the correct error
        input_url = EventSource.input_url.validate(obj=None, value=input_url)

        available_classes = non_abstract_children(cls)

        for subcls in available_classes:
            if subcls.is_compatible(input_url):
                return subcls

        raise ValueError(
            "Cannot find compatible EventSource for \n"
            "\turl:{}\n"
            "in available EventSources:\n"
            "\t{}".format(input_url, [c.__name__ for c in available_classes])
        )

    @classmethod
    def from_url(cls, input_url, **kwargs):
        """
        Find compatible EventSource for input_url via the `is_compatible`
        method of the EventSource

        Parameters
        ----------
        input_url : str
            Filename or URL pointing to an event file
        kwargs
            Named arguments for the EventSource

        Returns
        -------
        instance
            Instance of a compatible EventSource subclass
        """
        subcls = cls._find_compatible_source(input_url)
        return subcls(input_url=input_url, **kwargs)

    @classmethod
    def _find_input_url_in_config(cls, config=None, parent=None):
        if config is None and parent is None:
            raise ValueError("One of config or parent must be provided")

        if config is not None and parent is not None:
            raise ValueError("Only one of config or parent must be provided")

        input_url = None

        # config was passed
        if config is not None:
            if not isinstance(config.input_url, LazyConfigValue):
                input_url = config.input_url
            elif not isinstance(config.EventSource.input_url, LazyConfigValue):
                input_url = config.EventSource.input_url
            else:
                input_url = cls.input_url.default_value

        # parent was passed
        else:
            # first look at appropriate position in the config hierarcy
            input_url = find_config_in_hierarchy(parent, "EventSource", "input_url")

            # if not found, check top level
            if isinstance(input_url, LazyConfigValue):
                if not isinstance(parent.config.EventSource.input_url, LazyConfigValue):
                    input_url = parent.config.EventSource.input_url
                else:
                    input_url = cls.input_url.default_value

        return input_url

    @classmethod
    def from_config(cls, config=None, parent=None, **kwargs):
        """
        Find compatible EventSource for the EventSource.input_url traitlet
        specified via the config.

        This method is typically used in Tools, where the input_url is chosen via
        the command line using the traitlet configuration system.

        Parameters
        ----------
        config : traitlets.config.loader.Config
            Configuration created in the Tool
        kwargs
            Named arguments for the EventSource

        Returns
        -------
        instance
            Instance of a compatible EventSource subclass
        """
        input_url = cls._find_input_url_in_config(config=config, parent=parent)
        return cls.from_url(input_url, config=config, parent=parent, **kwargs)
コード例 #11
0
class TimeCorrectionCalculate(Component):
    """
        The TimeCorrectionCalculate class to create h5py
        file with coefficients for time correction curve
        of chip DRS4.
        Description of this method: "Analysis techniques
        and performance of the Domino Ring Sampler version 4
        based readout for the MAGIC telescopes [arxiv:1305.1007]
    """

    minimum_charge = Float(
        200, help='Cut on charge. Default 200 ADC').tag(config=True)

    tel_id = Int(1, help='Id of the telescope to calibrate').tag(config=True)

    n_combine = Int(
        8, help='How many capacitors are combines in a single bin. Default 8'
    ).tag(config=True)

    n_harmonics = Int(
        16, help='Number of harmonic for Fourier series expansion. Default 16'
    ).tag(config=True)

    n_capacitors = Int(
        1024, help='Number of capacitors (1024 or 4096). Default 1024.').tag(
            config=True)

    charge_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    calib_file_path = Unicode(
        '', allow_none=True,
        help='Path to the time calibration file').tag(config=True)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.n_bins = int(self.n_capacitors / self.n_combine)

        self.mean_values_per_bin = np.zeros((n_gain, n_pixels, self.n_bins))
        self.entries_per_bin = np.zeros((n_gain, n_pixels, self.n_bins))

        self.first_cap_array = np.zeros((n_modules, n_gain, n_channel))

        # load the waveform charge extractor
        self.extractor = ImageExtractor.from_name(self.charge_product,
                                                  config=self.config)

        self.log.info(f"extractor {self.extractor}")
        self.sum_events = 0

    def calibrate_pulse_time(self, event):
        """
        Fill bins using time pulse from LocalPeakWindowSum.
        Parameters
        ----------
        event : `ctapipe` event-container
        """
        if event.r1.tel[self.tel_id].trigger_type == 1:
            for nr_module in prange(0, n_modules):
                self.first_cap_array[
                    nr_module, :, :] = self.get_first_capacitor(
                        event, nr_module)

            pixel_ids = event.lst.tel[self.tel_id].svc.pixel_ids
            charge, pulse_time = self.extractor(
                event.r1.tel[self.tel_id].waveform)
            self.calib_pulse_time_jit(charge,
                                      pulse_time,
                                      pixel_ids,
                                      self.first_cap_array,
                                      self.mean_values_per_bin,
                                      self.entries_per_bin,
                                      n_cap=self.n_capacitors,
                                      n_combine=self.n_combine,
                                      min_charge=self.minimum_charge)
            self.sum_events += 1

    @jit(parallel=True)
    def calib_pulse_time_jit(self, charge, pulse_time, pixel_ids,
                             first_cap_array, mean_values_per_bin,
                             entries_per_bin, n_cap, n_combine, min_charge):
        """
        Numba function for calibration pulse time.

        Parameters
        ----------
        pulse : ndarray
            Pulse time stored in a numpy array of shape
            (n_gain, n_pixels).
        charge : ndarray
            Charge in each pixel.
            (n_gain, n_pixels).
        pixel_ids: ndarray
            Array stored expected pixel id
            (n_pixels).
        first_cap_array : ndarray
            Value of first capacitor stored in a numpy array of shape
            (n_clus, n_gain, n_pix).
        mean_values_per_bin : ndarray
            Array to fill using pulse time
            stored in a numpy array of shape
            (n_gain, n_pixels, n_bins).
        entries_per_bin : ndarray
            Array to store number of entries per bin
            stored in a numpy array of shape
            (n_gain, n_pixels, n_bins).
        n_cap : int
            Number of capacitors
        n_combine : int
            Number of combine capacitors in a single bin

        """

        for nr_module in prange(0, n_modules):
            for gain in prange(0, n_gain):
                for pix in prange(0, n_channel):
                    pixel = pixel_ids[nr_module * 7 + pix]
                    if charge[gain, pixel] > min_charge:  # cut change
                        fc = first_cap_array[nr_module, :, :]
                        first_cap = (fc[gain, pix]) % n_cap
                        bin = int(first_cap / n_combine)
                        mean_values_per_bin[gain, pixel,
                                            bin] += pulse_time[gain, pixel]
                        entries_per_bin[gain, pixel, bin] += 1

    def finalize(self):
        if np.sum(self.entries_per_bin == 0) > 0:
            raise RuntimeError(
                "Not enough events to coverage all capacitor. "
                "Please use more events to time calibration file.")
        else:
            self.mean_values_per_bin = self.mean_values_per_bin / self.entries_per_bin
            self.save_to_hdf5_file()

    def fit(self, pixel_id, gain):
        """
            Fit data bins using Fourier series expansion
            Parameters
            ----------
            pixel_id : ndarray
            Array stored expected pixel id of shape
            (n_pixels).
            gain: int
            0 for high gain, 1 for low gain
        """
        self.pos = np.zeros(self.n_bins)
        for i in range(0, self.n_bins):
            self.pos[i] = (i + 0.5) * self.n_combine

        self.fan = np.zeros(self.n_harmonics)  # cos coeff
        self.fbn = np.zeros(self.n_harmonics)  # sin coeff

        for n in range(0, self.n_harmonics):
            self.integrate_with_trig(self.pos,
                                     self.mean_values_per_bin[gain, pixel_id],
                                     n, self.fan, self.fbn)

    def integrate_with_trig(self, x, y, n, an, bn):
        """
            Function to expanding into Fourier series
            Parameters
            ----------
            x : ndarray
            Array stored position in DRS4 ring of shape
            (n_bins).
            y: ndarray
            Array stored mean pulse time per bin of shape
            (n_bins)
            n : int
            n harmonic
            an: ndarray
            Array to fill with cos coeff of shape
            (n_harmonics)
            bn: ndarray
            Array to fill with sin coeff of shape
            (n_harmonics)
        """
        suma = 0
        sumb = 0

        for i in range(0, self.n_bins):
            suma += y[i] * self.n_combine * np.cos(
                2 * np.pi * n * (x[i] / float(self.n_capacitors)))
            sumb += y[i] * self.n_combine * np.sin(
                2 * np.pi * n * (x[i] / float(self.n_capacitors)))

        an[n] = suma * (2. / (self.n_bins * self.n_combine))
        bn[n] = sumb * (2. / (self.n_bins * self.n_combine))

    def get_first_capacitor(self, event, nr):
        fc = np.zeros((n_gain, n_channel))
        first_cap = event.lst.tel[
            self.tel_id].evt.first_capacitor_id[nr * 8:(nr + 1) * 8]
        # First capacitor order according Dragon v5 board data format
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [0, 0, 1, 1, 2, 2, 3]):
            fc[high_gain, i] = first_cap[j]
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [4, 4, 5, 5, 6, 6, 7]):
            fc[low_gain, i] = first_cap[j]
        return fc

    def save_to_hdf5_file(self):
        """
            Function to save Fourier series expansion coeff into hdf5 file
        """
        fan_array = np.zeros((n_gain, n_pixels, self.n_harmonics))
        fbn_array = np.zeros((n_gain, n_pixels, self.n_harmonics))
        for pix_id in range(0, n_pixels):
            self.fit(pix_id, gain=high_gain)
            fan_array[high_gain, pix_id, :] = self.fan
            fbn_array[high_gain, pix_id, :] = self.fbn

            self.fit(pix_id, gain=low_gain)
            fan_array[low_gain, pix_id, :] = self.fan
            fbn_array[low_gain, pix_id, :] = self.fbn

        try:
            hf = h5py.File(self.calib_file_path, 'w')
            hf.create_dataset('fan', data=fan_array)
            hf.create_dataset('fbn', data=fbn_array)
            hf.attrs['n_events'] = self.sum_events
            hf.attrs['n_harm'] = self.n_harmonics

        except Exception as err:
            print("FAILED!", err)
        hf.close()
コード例 #12
0
class PulseTimeCorrection(Component):
    """
        The PulseTimeCorrection class to correct time pulse
        using Fourier series expansion.
    """
    tel_id = Int(1, help='id of the telescope to calibrate').tag(config=True)

    n_capacitors = Int(
        1024, help='number of capacitors (1024 or 4096)').tag(config=True)

    calib_file_path = Unicode(
        '', allow_none=True,
        help='Path to the time calibration file').tag(config=True)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.n_harmonics = None
        self.fan_array = None  # array to store cos coeff for Fourier series expansion
        self.fbn_array = None  # array to store sin coeff for Fourier series expansion
        self.first_cap_array = np.zeros((n_modules, n_gain, n_channel))

        self.load_calib_file()

    def load_calib_file(self):
        """
            Function to load calibration file.
        """

        try:

            with h5py.File(self.calib_file_path, 'r') as hf:
                self.n_harmonics = hf["/"].attrs['n_harm']
                fan = hf.get('fan')
                self.fan_array = np.array(fan)
                fbn = hf.get('fbn')
                self.fbn_array = np.array(fbn)

        except:
            self.log.error(
                f"Problem in reading time from calibration file {self.calib_file_path}"
            )

    def get_corr_pulse(self, event, pulse):
        """
        Return pulse time after time correction.
        Parameters
        ----------
        event : `ctapipe` event-container
        pulse : ndarray
            pulse time in each pixel.
            Stored in a numpy array of shape
            (2, 1855).
        """
        pixel_ids = event.lst.tel[self.tel_id].svc.pixel_ids
        n_modules_from_event = event.lst.tel[self.tel_id].svc.num_modules
        pulse_corr = np.empty((n_gain, n_pixels))
        for nr in prange(0, n_modules_from_event):
            self.first_cap_array[nr, :, :] = self.get_first_capacitor(
                event, nr)
        self.get_corr_pulse_jit(pulse, pulse_corr, pixel_ids,
                                self.first_cap_array, self.fan_array,
                                self.fbn_array, self.n_harmonics,
                                self.n_capacitors)
        return pulse_corr

    @staticmethod
    @njit(parallel=True)
    def get_corr_pulse_jit(pulse, pulse_corr, pixel_ids, first_capacitor,
                           fan_array, fbn_array, n_harmonics, n_cap):
        """
        Numba function for pulse time correction.
        Parameters
        ----------
        pulse : ndarray
            Pulse time stored in a numpy array of shape
            (n_gain, n_pixels).
        pulse_corr : ndarray
            Pulse correction time stored in a numpy array of shape
            (n_gain, n_pixels).
        pixel_ids: ndarray
            Array stored expected pixel id
            (n_pixels).
        first_capacitor : ndarray
            Value of first capacitor stored in a numpy array of shape
            (n_clus, n_gain, n_pix).
        fan_array : ndarray
            Array to store coeff for Fourier series expansion
            stored in a numpy array of shape
            (n_gain, n_pixels, n_harmonics).
        fbn_array : ndarray
            Array to store coeff for Fourier series expansion
            stored in a numpy array of shape
            (n_gain, n_pixels, n_harmonics).
        n_harmonics : int
            Number of harmonics
        """
        for gain in prange(0, n_gain):
            for nr in prange(0, n_modules):
                for pix in prange(0, n_channel):
                    fc = first_capacitor[nr, gain, pix]
                    pixel = pixel_ids[nr * 7 + pix]
                    pulse_corr[gain,
                               pixel] = pulse[gain, pixel] - get_corr_time_jit(
                                   fc % n_cap, fan_array[gain, pixel],
                                   fbn_array[gain, pixel], n_harmonics, n_cap)

    def get_first_capacitor(self, event, nr):
        """
            Get first capacitor values from event for nr module.
            Parameters
            ----------
            event : `ctapipe` event-container
            nr_module : number of module
            tel_id : id of the telescope
        """
        fc = np.zeros((n_gain, n_channel))
        first_cap = event.lst.tel[
            self.tel_id].evt.first_capacitor_id[nr * 8:(nr + 1) * 8]
        # First capacitor order according Dragon v5 board data format
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [0, 0, 1, 1, 2, 2, 3]):
            fc[high_gain, i] = first_cap[j]
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [4, 4, 5, 5, 6, 6, 7]):
            fc[low_gain, i] = first_cap[j]
        return fc
コード例 #13
0
class NectarCAMEventSource(EventSource):
    """
    EventSource for NectarCam r0 data.
    """
    n_gains = Int(2, help='Number of gains at r0/r1 level').tag(config=True)

    baseline = Int(250, help='r0 waveform baseline ').tag(config=True)

    def __init__(self, **kwargs):
        """
        Constructor
        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs: dict
            Additional parameters to be passed.
            NOTE: The file mask of the data to read can be passed with
            the 'input_url' parameter.
        """
        # EventSource can not handle file wild cards as input_url
        # To overcome this we substitute the input_url with first file matching
        # the specified file mask (copied from  MAGICEventSourceROOT).

        if 'input_url' in kwargs.keys():
            self.file_list = glob.glob(kwargs['input_url'])
            self.file_list.sort()
            kwargs['input_url'] = self.file_list[0]
            super().__init__(**kwargs)
        else:
            super().__init__(**kwargs)
            self.file_list = [self.input_url]

        self.multi_file = MultiFiles(self.file_list)
        self.camera_config = self.multi_file.camera_config

        self.log.info("Read {} input files".format(
            self.multi_file.num_inputs()))

    def _generator(self):

        # container for NectarCAM data
        self.data = NectarCAMDataContainer()
        self.data.meta['input_url'] = self.input_url
        self.data.meta['origin'] = 'NectarCAM'

        # fill data from the CameraConfig table
        self.fill_nectarcam_service_container_from_zfile()

        # Instrument information
        for tel_id in self.data.nectarcam.tels_with_data:
            assert (tel_id == 0)  # only one telescope for the moment (id = 0)

            # optics info from standard optics.fits.gz file
            optics = OpticsDescription.from_name("MST")
            optics.tel_subtype = ''  # to correct bug in reading

            # camera info from NectarCam-[geometry_version].camgeom.fits.gz file
            geometry_version = 2
            camera = CameraGeometry.from_name("NectarCam", geometry_version)

            tel_descr = TelescopeDescription(name='MST',
                                             tel_type='NectarCam',
                                             optics=optics,
                                             camera=camera)

            tel_descr.optics.tel_subtype = ''  # to correct bug in reading

            self.n_camera_pixels = tel_descr.camera.n_pixels
            tels = {tel_id: tel_descr}

            # LSTs telescope position
            tel_pos = {tel_id: [0., 0., 0] * u.m}

        self.subarray = SubarrayDescription("MST prototype subarray")
        self.subarray.tels = tels
        self.subarray.positions = tel_pos

        self.data.inst.subarray = self.subarray

        # initialize general monitoring container
        self.initialize_mon_container()

        # loop on events
        for count, event in enumerate(self.multi_file):

            self.data.count = count

            # fill specific NectarCAM event data
            self.fill_nectarcam_event_container_from_zfile(event)

            # fill general R0 data
            self.fill_r0_container_from_zfile(event)

            # copy r0 to r1
            self.fill_r1_container()

            # fill general monitoring data
            self.fill_mon_container_from_zfile(event)

            yield self.data

    @staticmethod
    def is_compatible(file_path):
        try:
            # The file contains two tables:
            #  1: CameraConfig
            #  2: Events
            h = fits.open(file_path)[2].header
            ttypes = [h[x] for x in h.keys() if 'TTYPE' in x]
        except OSError:
            # not even a fits file
            return False

        except IndexError:
            # A fits file of a different format
            return False

        is_protobuf_zfits_file = ((h['XTENSION'] == 'BINTABLE')
                                  and (h['EXTNAME'] == 'Events')
                                  and (h['ZTABLE'] is True)
                                  and (h['ORIGIN'] == 'CTA')
                                  and (h['PBFHEAD'] == 'R1.CameraEvent'))

        is_nectarcam_file = 'nectarcam_counters' in ttypes
        return is_protobuf_zfits_file & is_nectarcam_file

    def fill_nectarcam_service_container_from_zfile(self):

        self.data.nectarcam.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        svc_container = self.data.nectarcam.tel[
            self.camera_config.telescope_id].svc

        svc_container.telescope_id = self.camera_config.telescope_id
        svc_container.cs_serial = self.camera_config.cs_serial
        svc_container.configuration_id = self.camera_config.configuration_id
        svc_container.acquisition_mode = self.camera_config.nectarcam.acquisition_mode
        svc_container.date = self.camera_config.date
        svc_container.num_pixels = self.camera_config.num_pixels
        svc_container.num_samples = self.camera_config.num_samples
        svc_container.pixel_ids = self.camera_config.expected_pixels_id
        svc_container.data_model_version = self.camera_config.data_model_version

        svc_container.num_modules = self.camera_config.nectarcam.num_modules
        svc_container.module_ids = self.camera_config.nectarcam.expected_modules_id
        svc_container.idaq_version = self.camera_config.nectarcam.idaq_version
        svc_container.cdhs_version = self.camera_config.nectarcam.cdhs_version
        svc_container.algorithms = self.camera_config.nectarcam.algorithms
        # svc_container.pre_proc_algorithms = camera_config.nectarcam.pre_proc_algorithms

    def fill_nectarcam_event_container_from_zfile(self, event):
        event_container = self.data.nectarcam.tel[
            self.camera_config.telescope_id].evt

        event_container.configuration_id = event.configuration_id
        event_container.event_id = event.event_id
        event_container.tel_event_id = event.tel_event_id
        event_container.pixel_status = event.pixel_status
        event_container.ped_id = event.ped_id
        event_container.module_status = event.nectarcam.module_status
        event_container.extdevices_presence = event.nectarcam.extdevices_presence
        #event_container.tib_data = event.nectarcam.tib_data
        #event_container.cdts_data = event.nectarcam.cdts_data
        event_container.swat_data = event.nectarcam.swat_data
        event_container.counters = event.nectarcam.counters

        # unpack TIB data
        rec_fmt = '=IHIBB'
        unpacked_tib = struct.unpack(rec_fmt, event.nectarcam.tib_data)
        event_container.tib_event_counter = unpacked_tib[0]
        event_container.tib_pps_counter = unpacked_tib[1]
        event_container.tib_tenMHz_counter = unpacked_tib[2]
        event_container.tib_stereo_pattern = unpacked_tib[3]
        event_container.tib_masked_trigger = unpacked_tib[4]
        event_container.swat_data = event.lstcam.swat_data

        # unpack CDTS data
        rec_fmt = '=IIIQQBBB'
        unpacked_cdts = struct.unpack(rec_fmt, event.nectarcam.cdts_data)
        event_container.ucts_event_counter = unpacked_cdts[0]
        event_container.ucts_pps_counter = unpacked_cdts[1]
        event_container.ucts_clock_counter = unpacked_cdts[2]
        event_container.ucts_timestamp = unpacked_cdts[3]
        event_container.ucts_camera_timestamp = unpacked_cdts[4]
        event_container.ucts_trigger_type = unpacked_cdts[5]
        event_container.ucts_white_rabbit_status = unpacked_cdts[6]

    def fill_r0_camera_container_from_zfile(self, container, event):

        container.trigger_time = event.trigger_time_s
        #container.trigger_type = event.trigger_type
        container.trigger_type = self.data.nectarcam.tel[
            self.camera_config.telescope_id].evt.tib_masked_trigger

        # verify the number of gains

        if event.waveform.shape[
                0] != self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains:
            raise ValueError(
                f"Number of gains not correct, waveform shape is {event.waveform.shape[0]}"
                f" instead of "
                f"{self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains}"
            )

        reshaped_waveform = np.array(event.waveform).reshape(
            self.n_gains, self.camera_config.num_pixels,
            self.camera_config.num_samples)

        # initialize the waveform container to zero
        container.waveform = np.zeros([
            self.n_gains, self.n_camera_pixels, self.camera_config.num_samples
        ])

        # re-order the waveform following the expected_pixels_id values (rank = pixel id)
        container.waveform[:, self.camera_config.expected_pixels_id, :] \
            = reshaped_waveform

    def fill_r0_container_from_zfile(self, event):
        """fill the event r0 container"""

        container = self.data.r0
        container.obs_id = -1
        container.event_id = event.event_id

        container.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        r0_camera_container = container.tel[self.camera_config.telescope_id]

        self.fill_r0_camera_container_from_zfile(r0_camera_container, event)

    def fill_r1_container(self):
        """
           fill the event r1 container
           In the case of nectarCAM:
           r1 waveform = r0 waveform - self.baseline

        """
        self.data.r1.tels_with_data = [
            self.camera_config.telescope_id,
        ]

        r1_camera_container = self.data.r1.tel[self.camera_config.telescope_id]
        r1_camera_container.waveform = self.data.r0.tel[
            self.camera_config.telescope_id].waveform - self.baseline
        r1_camera_container.trigger_type = self.data.r0.tel[
            self.camera_config.telescope_id].trigger_type
        r1_camera_container.trigger_time = self.data.r0.tel[
            self.camera_config.telescope_id].trigger_time

    def initialize_mon_container(self):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """
        container = self.data.mon
        container.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        mon_camera_container = container.tel[self.camera_config.telescope_id]

        # initialize the container
        status_container = PixelStatusContainer()
        status_container.hardware_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.pedestal_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.flatfield_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)

        mon_camera_container.pixel_status = status_container

    def fill_mon_container_from_zfile(self, event):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """

        status_container = self.data.mon.tel[
            self.camera_config.telescope_id].pixel_status

        # reorder the array
        pixel_status = np.zeros(self.n_camera_pixels)
        pixel_status[
            self.camera_config.expected_pixels_id] = event.pixel_status
        status_container.hardware_failing_pixels[:] = pixel_status == 0
        '''
コード例 #14
0
class DL3Cuts(Component):
    """
    Selection cuts for DL2 to DL3 conversion
    """

    global_gh_cut = Float(
        help="Global selection cut for gh_score (gammaness)",
        default_value=0.6,
    ).tag(config=True)

    gh_efficiency = Float(
        help="Gamma efficiency for optimized g/h cuts in %",
        default_value=0.95,
    ).tag(config=True)

    theta_containment = Float(
        help="Percentage containment region for theta cuts",
        default_value=0.68,
    ).tag(config=True)

    global_theta_cut = Float(
        help="Global selection cut for theta",
        default_value=0.2,
    ).tag(config=True)

    global_alpha_cut = Float(
        help="Global selection cut for alpha",
        default_value=20,
    ).tag(config=True)

    allowed_tels = List(
        help="List of allowed LST telescope ids",
        trait=Int(),
        default_value=[1],
    ).tag(config=True)

    def apply_global_gh_cut(self, data):
        """
        Applying a global gammaness cut on a given data
        """
        return data[data["gh_score"] > self.global_gh_cut]

    def energy_dependent_gh_cuts(self,
                                 data,
                                 energy_bins,
                                 min_value=0.1,
                                 max_value=0.99,
                                 smoothing=None,
                                 min_events=10):
        """
        Evaluating energy-dependent gammaness cuts, in a given
        data, with provided reco energy bins, and other parameters to
        pass to the pyirf.cuts.calculate_percentile_cut function
        """

        gh_cuts = calculate_percentile_cut(
            data["gh_score"],
            data["reco_energy"],
            bins=energy_bins,
            min_value=min_value,
            max_value=max_value,
            fill_value=data["gh_score"].max(),
            percentile=100 * (1 - self.gh_efficiency),
            smoothing=smoothing,
            min_events=min_events,
        )
        return gh_cuts

    def apply_global_alpha_cut(self, data):
        """
        Applying a global alpha cut on a given data
        """
        return data[data["alpha"].to_value(u.deg) < self.global_alpha_cut]

    def apply_energy_dependent_gh_cuts(self, data, gh_cuts):
        """
        Applying a given energy-dependent gh cuts to a data file, along the reco
        energy bins provided.
        """

        data["selected_gh"] = evaluate_binned_cut(
            data["gh_score"],
            data["reco_energy"],
            gh_cuts,
            operator.ge,
        )
        return data[data["selected_gh"]]

    def apply_global_theta_cut(self, data):
        """
        Applying a global theta cut on a given data
        """
        return data[data["theta"].to_value(u.deg) < self.global_theta_cut]

    def energy_dependent_theta_cuts(self,
                                    data,
                                    energy_bins,
                                    min_value=0.05 * u.deg,
                                    fill_value=0.32 * u.deg,
                                    max_value=0.32 * u.deg,
                                    smoothing=None,
                                    min_events=10):
        """
        Evaluating an optimized energy-dependent theta cuts, in a given
        data, with provided reco energy bins, and other parameters to
        pass to the pyirf.cuts.calculate_percentile_cut function.

        Note: Using too fine binning will result in too un-smooth cuts.
        """

        theta_cuts = calculate_percentile_cut(
            data["theta"],
            data["reco_energy"],
            bins=energy_bins,
            min_value=min_value,
            max_value=max_value,
            fill_value=fill_value,
            percentile=100 * self.theta_containment,
            smoothing=smoothing,
            min_events=min_events,
        )
        return theta_cuts

    def apply_energy_dependent_theta_cuts(self, data, theta_cuts):
        """
        Applying a given energy-dependent theta cuts to a data file, along the
        reco energy bins provided.
        """

        data["selected_theta"] = evaluate_binned_cut(
            data["theta"],
            data["reco_energy"],
            theta_cuts,
            operator.le,
        )
        return data[data["selected_theta"]]

    def allowed_tels_filter(self, data):
        """
        Applying a filter on telescopes used for observation.
        """
        mask = np.zeros(len(data), dtype=bool)
        for tel_id in self.allowed_tels:
            mask |= data["tel_id"] == tel_id
        return data[mask]
コード例 #15
0
class LSTCameraCalibrator(CameraCalibrator):
    """
    Calibrator to handle the LST camera calibration chain, in order to fill
    the DL1 data level in the event container.
    """
    extractor_product = Unicode(
        'NeighborPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    reducer_product = Unicode(
        'NullDataVolumeReducer',
        help='Name of the DataVolumeReducer to use').tag(config=True)

    calibration_path = Unicode(
        '', allow_none=True,
        help='Path to LST calibration file').tag(config=True)

    time_calibration_path = Unicode(
        '', allow_none=True,
        help='Path to drs4 time calibration file').tag(config=True)

    allowed_tels = List(
        [1], help='List of telescope to be calibrated').tag(config=True)

    gain_threshold = Int(
        4094, allow_none=True,
        help='Threshold for the gain selection in ADC').tag(config=True)

    def __init__(self, **kwargs):
        """
        Parameters
        ----------

        reducer_product : ctapipe.image.reducer.DataVolumeReducer
            The DataVolumeReducer to use. If None, then
            NullDataVolumeReducer will be used by default, and waveforms
            will not be reduced.
        extractor_product : ctapipe.image.extractor.ImageExtractor
            The ImageExtractor to use. If None, then NeighborPeakWindowSum
            will be used by default.
        calibration_path :
            Path to LST calibration file to get the pedestal and flat-field corrections


        kwargs
        """
        super().__init__(**kwargs)

        # load the waveform charge extractor
        self.image_extractor = ImageExtractor.from_name(self.extractor_product,
                                                        config=self.config)
        self.log.info(f"extractor {self.extractor_product}")

        self.data_volume_reducer = DataVolumeReducer.from_name(
            self.reducer_product, config=self.config)
        self.log.info(f" {self.reducer_product}")

        # declare gain selector if the threshold is defined
        if self.gain_threshold:
            self.gain_selector = gainselection.ThresholdGainSelector(
                threshold=self.gain_threshold)

        # declare time calibrator if correction file exist
        if os.path.exists(self.time_calibration_path):
            self.time_corrector = PulseTimeCorrection(
                calib_file_path=self.time_calibration_path)
        else:
            self.time_corrector = None
            self.log.info(
                f"File {self.time_calibration_path} not found. No drs4 time corrections"
            )

        # calibration data container
        self.mon_data = MonitoringContainer()

        # initialize the MonitoringContainer() for the moment it reads it from a hdf5 file
        self._initialize_correction()

    def _initialize_correction(self):
        """
        Read the correction from hdf5 calibration file
        """

        self.mon_data.tels_with_data = self.allowed_tels
        self.log.info(f"read {self.calibration_path}")

        try:
            with HDF5TableReader(self.calibration_path) as h5_table:
                assert h5_table._h5file.isopen == True
                for telid in self.allowed_tels:
                    # read the calibration data for the moment only one event
                    table = '/tel_' + str(telid) + '/calibration'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].calibration))
                    # eliminate inf values (should be done probably before)
                    dc_to_pe = self.mon_data.tel[telid].calibration.dc_to_pe

                    dc_to_pe[np.isinf(dc_to_pe)] = 0
                    self.log.info(
                        f"read {self.mon_data.tel[telid].calibration.dc_to_pe}"
                    )
        except:
            self.log.error(
                f"Problem in reading calibration file {self.calibration_path}")

    def _calibrate_dl0(self, event, telid):
        """
        create dl0 level, for the moment copy the r1
        """
        waveforms = event.r1.tel[telid].waveform
        if self._check_r1_empty(waveforms):
            return

        event.dl0.event_id = event.r1.event_id
        event.mon.tel[telid].calibration = self.mon_data.tel[telid].calibration

        # subtract the pedestal per sample (should we do it?) and multiply for the calibration coefficients
        #
        event.dl0.tel[telid].waveform = (
            (event.r1.tel[telid].waveform - self.mon_data.tel[telid].
             calibration.pedestal_per_sample[:, :, np.newaxis]) *
            self.mon_data.tel[telid].calibration.dc_to_pe[:, :, np.newaxis])

    def _calibrate_dl1(self, event, telid):
        """
        create calibrated dl1 image and calibrate it
        """
        waveforms = event.dl0.tel[telid].waveform

        if self._check_dl0_empty(waveforms):
            return

        if self.image_extractor.requires_neighbors():
            camera = event.inst.subarray.tel[telid].camera
            self.image_extractor.neighbors = camera.neighbor_matrix_where
        charge, pulse_time = self.image_extractor(waveforms)

        # correct time with drs4 correction if available
        if self.time_corrector:
            pulse_corr_array = self.time_corrector.get_corr_pulse(
                event, pulse_time)

        # otherwise use the ff time correction (not drs4 corrected)
        else:
            pulse_corr_array = pulse_time + self.mon_data.tel[
                telid].calibration.time_correction

        # perform the gain selection if the threshold is defined
        if self.gain_threshold:
            waveforms, gain_mask = self.gain_selector(
                event.r1.tel[telid].waveform)
            event.dl1.tel[telid].image = charge[gain_mask,
                                                np.arange(charge.shape[1])]
            event.dl1.tel[telid].pulse_time = pulse_corr_array[
                gain_mask, np.arange(pulse_corr_array.shape[1])]

            # remember the mask in the lst pixel_status array (this info is missing for the moment in the
            # r1 container). I follow the prescription given in the document
            # "R1 & DL0 Telescope Event Interfaces and Prototype Evaluation" of K. Kosack

            # bit 2 = LG
            gain_mask *= 4

            # bit 3 = HG
            gain_mask[np.where(gain_mask == 0)] = 8

            # bit 1 = pixel broken pixel (coming from the EvB)
            gain_mask += event.lst.tel[telid].evt.pixel_status >> 1 & 1

            # update pixel status
            event.lst.tel[telid].evt.pixel_status = gain_mask

        # if threshold == None
        else:
            event.dl1.tel[telid].image = charge
            event.dl1.tel[telid].pulse_time = pulse_corr_array
コード例 #16
0
class LSTCameraCalibrator(CameraCalibrator):
    """
    Calibrator to handle the LST camera calibration chain, in order to fill
    the DL1 data level in the event container.
    """
    extractor_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    reducer_product = Unicode(
        'NullDataVolumeReducer',
        help='Name of the DataVolumeReducer to use').tag(config=True)

    calibration_path = Unicode(
        '', help='Path to LST calibration file').tag(config=True)

    time_calibration_path = Unicode(
        '', help='Path to drs4 time calibration file').tag(config=True)

    allowed_tels = List(
        [1], help='List of telescope to be calibrated').tag(config=True)

    gain_threshold = Int(
        4094, allow_none=True,
        help='Threshold for the gain selection in ADC').tag(config=True)

    def __init__(self, **kwargs):
        """
        Parameters
        ----------

        reducer_product : ctapipe.image.reducer.DataVolumeReducer
            The DataVolumeReducer to use. If None, then
            NullDataVolumeReducer will be used by default, and waveforms
            will not be reduced.
        extractor_product : ctapipe.image.extractor.ImageExtractor
            The ImageExtractor to use. If None, then LocalPeakWindowSum
            will be used by default.
        calibration_path :
            Path to LST calibration file to get the pedestal and flat-field corrections


        kwargs
        """
        super().__init__(**kwargs)

        # load the waveform charge extractor
        self.image_extractor = ImageExtractor.from_name(self.extractor_product,
                                                        config=self.config)
        self.log.info(f"extractor {self.extractor_product}")

        print("EXTRACTOR", self.image_extractor)

        self.data_volume_reducer = DataVolumeReducer.from_name(
            self.reducer_product, config=self.config)
        self.log.info(f" {self.reducer_product}")

        # declare gain selector if the threshold is defined
        if self.gain_threshold:
            self.gain_selector = gainselection.ThresholdGainSelector(
                threshold=self.gain_threshold)

        # declare time calibrator if correction file exist
        if os.path.exists(self.time_calibration_path):
            self.time_corrector = PulseTimeCorrection(
                calib_file_path=self.time_calibration_path)
        else:
            raise IOError(
                f"Time calibration file {self.time_calibration_path} not found!"
            )

        # calibration data container
        self.mon_data = MonitoringContainer()

        # initialize the MonitoringContainer() for the moment it reads it from a hdf5 file
        self._initialize_correction()

    def _initialize_correction(self):
        """
        Read the correction from hdf5 calibration file
        """

        self.mon_data.tels_with_data = self.allowed_tels
        self.log.info(f"read {self.calibration_path}")

        try:
            with HDF5TableReader(self.calibration_path) as h5_table:
                for telid in self.allowed_tels:
                    # read the calibration data
                    table = '/tel_' + str(telid) + '/calibration'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].calibration))

                    # read pedestal data
                    table = '/tel_' + str(telid) + '/pedestal'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pedestal))

                    # read flat-field data
                    table = '/tel_' + str(telid) + '/flatfield'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].flatfield))

                    # read the pixel_status container
                    table = '/tel_' + str(telid) + '/pixel_status'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pixel_status))
        except Exception:
            self.log.exception(
                f"Problem in reading calibration file {self.calibration_path}")
            raise

    def _calibrate_dl0(self, event, telid):
        """
        create dl0 level, for the moment copy the r1
        """
        waveforms = event.r1.tel[telid].waveform
        if self._check_r1_empty(waveforms):
            return

        event.dl0.event_id = event.r1.event_id

        # if not already done, initialize the event monitoring containers
        if event.mon.tel[telid].calibration.dc_to_pe is None:
            event.mon.tel[telid].calibration = self.mon_data.tel[
                telid].calibration
            event.mon.tel[telid].flatfield = self.mon_data.tel[telid].flatfield
            event.mon.tel[telid].pedestal = self.mon_data.tel[telid].pedestal
            event.mon.tel[telid].pixel_status = self.mon_data.tel[
                telid].pixel_status

        #
        # subtract the pedestal per sample and multiply for the calibration coefficients
        #
        event.dl0.tel[telid].waveform = (
            (waveforms - self.mon_data.tel[telid].calibration.
             pedestal_per_sample[:, :, np.newaxis]) *
            self.mon_data.tel[telid].calibration.dc_to_pe[:, :, np.newaxis])

    def _calibrate_dl1(self, event, telid):
        """
        create calibrated dl1 image and calibrate it
        """
        waveforms = event.dl0.tel[telid].waveform

        if self._check_dl0_empty(waveforms):
            return

        if self.image_extractor.requires_neighbors():
            camera = event.inst.subarray.tel[telid].camera
            self.image_extractor.neighbors = camera.neighbor_matrix_where

        charge, pulse_time = self.image_extractor(waveforms)

        # correct time with drs4 correction if available
        if self.time_corrector:
            pulse_time = self.time_corrector.get_corr_pulse(event, pulse_time)

        # add flat-fielding time correction
        pulse_time_ff_corrected = pulse_time + self.mon_data.tel[
            telid].calibration.time_correction

        # perform the gain selection if the threshold is defined
        if self.gain_threshold:
            waveforms, gain_mask = self.gain_selector(
                event.r1.tel[telid].waveform)

            event.dl1.tel[telid].image = charge[gain_mask,
                                                np.arange(charge.shape[1])]
            event.dl1.tel[telid].pulse_time = pulse_time_ff_corrected[
                gain_mask,
                np.arange(pulse_time_ff_corrected.shape[1])]

            # remember which channel has been selected
            event.r1.tel[telid].selected_gain_channel = gain_mask

        # if threshold == None
        else:
            event.dl1.tel[telid].image = charge
            event.dl1.tel[telid].pulse_time = pulse_time_ff_corrected
コード例 #17
0
class NectarCAMEventSource(EventSource):
    """
    EventSource for NectarCam r0 data.
    """
    n_gains = Int(2, help='Number of gains at r0/r1 level').tag(config=True)

    baseline = Int(250, help='r0 waveform baseline ').tag(config=True)

    geometry_version = Int(
        3, help='Version of the camera geometry to be used ').tag(config=True)

    def __init__(self, **kwargs):
        """
        Constructor
        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs: dict
            Additional parameters to be passed.
            NOTE: The file mask of the data to read can be passed with
            the 'input_url' parameter.
        """
        # EventSource can not handle file wild cards as input_url
        # To overcome this we substitute the input_url with first file matching
        # the specified file mask (copied from  MAGICEventSourceROOT).

        if 'input_url' in kwargs.keys():
            self.file_list = glob.glob(str(kwargs['input_url']))
            self.file_list.sort()
            kwargs['input_url'] = self.file_list[0]
            super().__init__(**kwargs)
        else:
            super().__init__(**kwargs)
            self.file_list = [self.input_url]

        self.multi_file = MultiFiles(self.file_list)
        self.camera_config = self.multi_file.camera_config
        self.data = None
        self.log.info("Read {} input files".format(
            self.multi_file.num_inputs()))
        self._tel_id = self.camera_config.telescope_id
        self._subarray_info = self.prepare_subarray_info(self._tel_id)

    @property
    def subarray(self):
        return self._subarray_info

    def prepare_subarray_info(self, tel_id=0):
        """
        Constructs a SubarrayDescription object.
        Parameters
        ----------
        tel_id: int
            Telescope identifier.
        Returns
        -------
        SubarrayDescription :
            instrumental information
        """
        tel_descriptions = {}  # tel_id : TelescopeDescription
        tel_positions = {}  # tel_id : TelescopeDescription

        # optics info from standard optics.fits.gz file
        optics = OpticsDescription.from_name("MST")
        optics.tel_subtype = ''  # to correct bug in reading

        # camera info from NectarCam-[geometry_version].camgeom.fits.gz file
        camera = CameraGeometry.from_name("NectarCam", self.geometry_version)

        tel_descr = TelescopeDescription(name='MST',
                                         tel_type='NectarCam',
                                         optics=optics,
                                         camera=camera)
        tel_descr.optics.tel_subtype = ''  # to correct bug in reading

        self.n_camera_pixels = tel_descr.camera.n_pixels

        # MST telescope position
        tel_positions[tel_id] = [0., 0., 0] * u.m
        tel_descriptions[tel_id] = tel_descr

        return SubarrayDescription(
            "Adlershof",
            tel_positions=tel_positions,
            tel_descriptions=tel_descriptions,
        )

    @property
    def is_simulation(self):
        return False

    @property
    def datalevels(self):
        return (DataLevel.R0, DataLevel.R1)

    @property
    def obs_id(self):
        return self.camera_config.nectarcam.run_id

    def _generator(self):

        # container for NectarCAM data
        self.data = NectarCAMDataContainer()
        self.data.meta['input_url'] = self.input_url
        self.data.meta['origin'] = 'NectarCAM'

        # fill data from the CameraConfig table
        self.fill_nectarcam_service_container_from_zfile()

        # initialize general monitoring container
        self.initialize_mon_container()

        # loop on events
        for count, event in enumerate(self.multi_file):

            self.data.count = count

            # fill specific NectarCAM event data
            self.fill_nectarcam_event_container_from_zfile(event)

            # fill general R0 data
            self.fill_r0_container_from_zfile(event)

            # copy r0 to r1
            self.fill_r1_container()

            # fill general monitoring data
            self.fill_mon_container_from_zfile(event)

            yield self.data

    @staticmethod
    def is_compatible(file_path):
        try:
            # The file contains two tables:
            #  1: CameraConfig
            #  2: Events
            h = fits.open(file_path)[2].header
            ttypes = [h[x] for x in h.keys() if 'TTYPE' in x]
        except OSError:
            # not even a fits file
            return False

        except IndexError:
            # A fits file of a different format
            return False

        is_protobuf_zfits_file = ((h['XTENSION'] == 'BINTABLE')
                                  and (h['EXTNAME'] == 'Events')
                                  and (h['ZTABLE'] is True)
                                  and (h['ORIGIN'] == 'CTA')
                                  and (h['PBFHEAD'] == 'R1.CameraEvent'))

        is_nectarcam_file = 'nectarcam_counters' in ttypes
        return is_protobuf_zfits_file & is_nectarcam_file

    def fill_nectarcam_service_container_from_zfile(self):

        self.data.nectarcam.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        svc_container = self.data.nectarcam.tel[
            self.camera_config.telescope_id].svc

        svc_container.telescope_id = self.camera_config.telescope_id
        svc_container.cs_serial = self.camera_config.cs_serial
        svc_container.configuration_id = self.camera_config.configuration_id
        svc_container.acquisition_mode = self.camera_config.nectarcam.acquisition_mode
        svc_container.date = self.camera_config.date
        svc_container.num_pixels = self.camera_config.num_pixels
        svc_container.num_samples = self.camera_config.num_samples
        svc_container.pixel_ids = self.camera_config.expected_pixels_id
        svc_container.data_model_version = self.camera_config.data_model_version

        svc_container.num_modules = self.camera_config.nectarcam.num_modules
        svc_container.module_ids = self.camera_config.nectarcam.expected_modules_id
        svc_container.idaq_version = self.camera_config.nectarcam.idaq_version
        svc_container.cdhs_version = self.camera_config.nectarcam.cdhs_version
        svc_container.algorithms = self.camera_config.nectarcam.algorithms
        # svc_container.pre_proc_algorithms = camera_config.nectarcam.pre_proc_algorithms

    def fill_nectarcam_event_container_from_zfile(self, event):
        event_container = self.data.nectarcam.tel[
            self.camera_config.telescope_id].evt

        event_container.configuration_id = event.configuration_id
        event_container.event_id = event.event_id
        event_container.tel_event_id = event.tel_event_id
        event_container.pixel_status = event.pixel_status
        event_container.ped_id = event.ped_id
        event_container.module_status = event.nectarcam.module_status
        event_container.extdevices_presence = event.nectarcam.extdevices_presence
        #event_container.tib_data = event.nectarcam.tib_data
        #event_container.cdts_data = event.nectarcam.cdts_data
        event_container.swat_data = event.nectarcam.swat_data
        event_container.counters = event.nectarcam.counters

        # unpack TIB data
        rec_fmt = '=IHIBB'
        unpacked_tib = struct.unpack(rec_fmt, event.nectarcam.tib_data)
        event_container.tib_event_counter = unpacked_tib[0]
        event_container.tib_pps_counter = unpacked_tib[1]
        event_container.tib_tenMHz_counter = unpacked_tib[2]
        event_container.tib_stereo_pattern = unpacked_tib[3]
        event_container.tib_masked_trigger = unpacked_tib[4]
        event_container.swat_data = event.lstcam.swat_data

        # unpack CDTS data
        is_old_cdts = len(event.nectarcam.cdts_data) < 36
        rec_fmt = '=IIIQQBBB' if is_old_cdts else '=QIIIIIBBBBI'
        unpacked_cdts = struct.unpack(rec_fmt, event.nectarcam.cdts_data)
        if is_old_cdts:
            event_container.ucts_event_counter = unpacked_cdts[0]
            event_container.ucts_pps_counter = unpacked_cdts[1]
            event_container.ucts_clock_counter = unpacked_cdts[2]
            event_container.ucts_timestamp = unpacked_cdts[3]
            event_container.ucts_camera_timestamp = unpacked_cdts[4]
            event_container.ucts_trigger_type = unpacked_cdts[5]
            event_container.ucts_white_rabbit_status = unpacked_cdts[6]
        else:
            event_container.ucts_timestamp = unpacked_cdts[0]
            event_container.ucts_address = unpacked_cdts[1]
            event_container.ucts_event_counter = unpacked_cdts[2]
            event_container.ucts_busy_counter = unpacked_cdts[3]
            event_container.ucts_pps_counter = unpacked_cdts[4]
            event_container.ucts_clock_counter = unpacked_cdts[5]
            event_container.ucts_trigger_type = unpacked_cdts[6]
            event_container.ucts_white_rabbit_status = unpacked_cdts[7]
            event_container.ucts_stereo_pattern = unpacked_cdts[8]
            event_container.ucts_num_in_bunch = unpacked_cdts[9]
            event_container.cdts_version = unpacked_cdts[10]

        # Unpack FEB counters and trigger pattern
        self.unpack_feb_data(event)

    def unpack_feb_data(self, event):
        '''Unpack FEB counters and trigger pattern'''
        event_container = self.data.nectarcam.tel[
            self.camera_config.telescope_id].evt

        # Deduce data format version
        bytes_per_module = len(event.nectarcam.counters
                               ) // self.camera_config.nectarcam.num_modules
        # Remain compatible with data before addition of trigger pattern
        module_fmt = 'IHHIBBBBBBBB' if bytes_per_module > 16 else 'IHHIBBBB'
        n_fields = len(module_fmt)
        rec_fmt = '=' + module_fmt * self.camera_config.nectarcam.num_modules
        # Unpack
        unpacked_feb = struct.unpack(rec_fmt, event.nectarcam.counters)
        # Initialize field containers
        n_camera_modules = self.n_camera_pixels // 7
        event_container.feb_abs_event_id = np.zeros(shape=(n_camera_modules, ),
                                                    dtype=np.uint32)
        event_container.feb_event_id = np.zeros(shape=(n_camera_modules, ),
                                                dtype=np.uint16)
        event_container.feb_pps_cnt = np.zeros(shape=(n_camera_modules, ),
                                               dtype=np.uint16)
        event_container.feb_ts1 = np.zeros(shape=(n_camera_modules, ),
                                           dtype=np.uint32)
        event_container.feb_ts2_trig = np.zeros(shape=(n_camera_modules, ),
                                                dtype=np.int16)
        event_container.feb_ts2_pps = np.zeros(shape=(n_camera_modules, ),
                                               dtype=np.int16)
        if bytes_per_module > 16:
            n_patterns = 4
            event_container.trigger_pattern = np.zeros(
                shape=(n_patterns, self.n_camera_pixels), dtype=bool)

        # Unpack absolute event ID
        event_container.feb_abs_event_id[
            self.camera_config.nectarcam.
            expected_modules_id] = unpacked_feb[0::n_fields]
        # Unpack PPS counter
        event_container.feb_pps_cnt[
            self.camera_config.nectarcam.
            expected_modules_id] = unpacked_feb[1::n_fields]
        # Unpack relative event ID
        event_container.feb_event_id[
            self.camera_config.nectarcam.
            expected_modules_id] = unpacked_feb[2::n_fields]
        # Unpack TS1 counter
        event_container.feb_ts1[
            self.camera_config.nectarcam.
            expected_modules_id] = unpacked_feb[3::n_fields]
        # Unpack TS2 counters
        ts2_decimal = lambda bits: bits - (1 << 8
                                           ) if bits & 0x80 != 0 else bits
        ts2_decimal_vec = np.vectorize(ts2_decimal)
        event_container.feb_ts2_trig[self.camera_config.nectarcam.
                                     expected_modules_id] = ts2_decimal_vec(
                                         unpacked_feb[4::n_fields])
        event_container.feb_ts2_pps[self.camera_config.nectarcam.
                                    expected_modules_id] = ts2_decimal_vec(
                                        unpacked_feb[5::n_fields])
        # Loop over modules
        for module_idx, module_id in enumerate(
                self.camera_config.nectarcam.expected_modules_id):
            offset = module_id * 7
            if bytes_per_module > 16:
                field_id = 8
                # Decode trigger pattern
                for pattern_id in range(n_patterns):
                    value = unpacked_feb[n_fields * module_idx + field_id +
                                         pattern_id]
                    module_pattern = [
                        int(digit)
                        for digit in reversed(bin(value)[2:].zfill(7))
                    ]
                    event_container.trigger_pattern[pattern_id, offset:offset +
                                                    7] = module_pattern

        # Unpack native charge
        if len(event.nectarcam.charges_gain1) > 0:
            event_container.native_charge = np.zeros(
                shape=(self.n_gains, self.n_camera_pixels), dtype=np.uint16)
            rec_fmt = '=' + 'H' * self.camera_config.num_pixels
            for gain_id in range(self.n_gains):
                unpacked_charge = struct.unpack(
                    rec_fmt,
                    getattr(event.nectarcam, f'charges_gain{gain_id + 1}'))
                event_container.native_charge[
                    gain_id,
                    self.camera_config.expected_pixels_id] = unpacked_charge

    def fill_r0_camera_container_from_zfile(self, container, event):

        container.trigger_time = event.trigger_time_s
        #container.trigger_type = event.trigger_type
        container.trigger_type = self.data.nectarcam.tel[
            self.camera_config.telescope_id].evt.tib_masked_trigger

        # verify the number of gains

        if event.waveform.shape[
                0] != self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains:
            raise ValueError(
                f"Number of gains not correct, waveform shape is {event.waveform.shape[0]}"
                f" instead of "
                f"{self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains}"
            )

        reshaped_waveform = np.array(event.waveform).reshape(
            self.n_gains, self.camera_config.num_pixels,
            self.camera_config.num_samples)

        # initialize the waveform container to zero
        container.waveform = np.zeros([
            self.n_gains, self.n_camera_pixels, self.camera_config.num_samples
        ])

        # re-order the waveform following the expected_pixels_id values (rank = pixel id)
        container.waveform[:, self.camera_config.expected_pixels_id, :] \
            = reshaped_waveform

    def fill_r0_container_from_zfile(self, event):
        """fill the event r0 container"""
        self.data.index.obs_id = self.obs_id
        self.data.index.event_id = event.event_id

        container = self.data.r0
        container.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        r0_camera_container = container.tel[self.camera_config.telescope_id]

        self.fill_r0_camera_container_from_zfile(r0_camera_container, event)

    def fill_r1_container(self):
        """
           fill the event r1 container
           In the case of nectarCAM:
           r1 waveform = r0 waveform - self.baseline

        """
        self.data.r1.tels_with_data = [
            self.camera_config.telescope_id,
        ]

        r1_camera_container = self.data.r1.tel[self.camera_config.telescope_id]
        r1_camera_container.waveform = self.data.r0.tel[
            self.camera_config.telescope_id].waveform - self.baseline
        r1_camera_container.trigger_type = self.data.r0.tel[
            self.camera_config.telescope_id].trigger_type
        r1_camera_container.trigger_time = self.data.r0.tel[
            self.camera_config.telescope_id].trigger_time

    def initialize_mon_container(self):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """
        container = self.data.mon
        container.tels_with_data = [
            self.camera_config.telescope_id,
        ]
        mon_camera_container = container.tel[self.camera_config.telescope_id]

        # initialize the container
        status_container = PixelStatusContainer()
        status_container.hardware_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.pedestal_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.flatfield_failing_pixels = np.zeros(
            (self.n_gains, self.n_camera_pixels), dtype=bool)

        mon_camera_container.pixel_status = status_container

    def fill_mon_container_from_zfile(self, event):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """

        status_container = self.data.mon.tel[
            self.camera_config.telescope_id].pixel_status

        # reorder the array
        pixel_status = np.zeros(self.n_camera_pixels)
        pixel_status[
            self.camera_config.expected_pixels_id] = event.pixel_status
        status_container.hardware_failing_pixels[:] = pixel_status == 0
        '''
コード例 #18
0
class TimeCorrectionCalculate(Component):
    """
        The TimeCorrectionCalculate class to create h5py
        file with coefficients for time correction curve
        of chip DRS4.
        Description of this method: "Analysis techniques
        and performance of the Domino Ring Sampler version 4
        based readout for the MAGIC telescopes [arxiv:1305.1007]
    """

    minimum_charge = Float(
        200, help='Cut on charge. Default 200 ADC').tag(config=True)

    tel_id = Int(1, help='Id of the telescope to calibrate').tag(config=True)

    n_combine = Int(
        8, help='How many capacitors are combines in a single bin. Default 8'
    ).tag(config=True)

    n_harmonics = Int(
        16, help='Number of harmonic for Fourier series expansion. Default 16'
    ).tag(config=True)

    n_capacitors = Int(
        1024, help='Number of capacitors (1024 or 4096). Default 1024.').tag(
            config=True)

    charge_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    calib_file_path = Unicode(
        '', allow_none=True,
        help='Path to the time calibration file').tag(config=True)

    def __init__(self, subarray, **kwargs):
        """
        The TimeCorrectionCalculate class to create h5py
        file with coefficients for time correction curve of chip DRS4.
        Description of this method: "Analysis techniques and performance
        of the Domino Ring Sampler version 4 based readout
        for the MAGIC telescopes [arxiv:1305.1007]

        Parameters
        ----------
        subarray: ctapipe.instrument.SubarrayDescription
            Description of the subarray. Provides information about the
            camera which are useful in charge extraction, such as reference
            pulse shape, sampling rate, neighboring pixels. Also required for
            configuring the TelescopeParameter traitlets.
        kwargs
        """
        super().__init__(**kwargs)

        self.n_bins = int(self.n_capacitors / self.n_combine)

        self.mean_values_per_bin = np.zeros((n_gain, n_pixels, self.n_bins))
        self.entries_per_bin = np.zeros((n_gain, n_pixels, self.n_bins))

        self.first_cap_array = np.zeros((n_modules, n_gain, n_channel))

        # load the waveform charge extractor
        self.extractor = ImageExtractor.from_name(self.charge_product,
                                                  config=self.config,
                                                  subarray=subarray)

        self.log.info(f"extractor {self.extractor}")
        self.sum_events = 0

    def calibrate_peak_time(self, event):
        """
        Fill bins using time pulse from LocalPeakWindowSum.
        Parameters
        ----------
        event : `ctapipe` event-container
        """

        if event.trigger.event_type == EventType.FLATFIELD:
            for nr_module in prange(0, n_modules):
                self.first_cap_array[
                    nr_module, :, :] = self.get_first_capacitor(
                        event, nr_module)

            pixel_ids = event.lst.tel[self.tel_id].svc.pixel_ids
            waveforms = event.r1.tel[self.tel_id].waveform
            no_gain_selection = np.zeros(
                (waveforms.shape[0], waveforms.shape[1]), dtype=np.int64)
            # select both gain
            charge, peak_time = self.extractor(
                event.r1.tel[self.tel_id].waveform[:, :, :], self.tel_id,
                no_gain_selection)
            self.calib_peak_time_jit(charge,
                                     peak_time,
                                     pixel_ids,
                                     self.first_cap_array,
                                     self.mean_values_per_bin,
                                     self.entries_per_bin,
                                     n_cap=self.n_capacitors,
                                     n_combine=self.n_combine,
                                     min_charge=self.minimum_charge)
            self.sum_events += 1

    @staticmethod
    @njit(parallel=True)
    def calib_peak_time_jit(charge, peak_time, pixel_ids, first_cap_array,
                            mean_values_per_bin, entries_per_bin, n_cap,
                            n_combine, min_charge):
        """
        Numba function for calibration pulse time.

        Parameters
        ----------
        pulse : ndarray
            Pulse time stored in a numpy array of shape
            (n_gain, n_pixels).
        charge : ndarray
            Charge in each pixel.
            (n_gain, n_pixels).
        pixel_ids: ndarray
            Array stored expected pixel id
            (n_pixels).
        first_cap_array : ndarray
            Value of first capacitor stored in a numpy array of shape
            (n_clus, n_gain, n_pix).
        mean_values_per_bin : ndarray
            Array to fill using pulse time
            stored in a numpy array of shape
            (n_gain, n_pixels, n_bins).
        entries_per_bin : ndarray
            Array to store number of entries per bin
            stored in a numpy array of shape
            (n_gain, n_pixels, n_bins).
        n_cap : int
            Number of capacitors
        n_combine : int
            Number of combine capacitors in a single bin

        """

        for nr_module in prange(n_modules):
            for gain in prange(n_gain):
                for pix in prange(n_channel):
                    pixel = pixel_ids[nr_module * 7 + pix]
                    if charge[gain, pixel] > min_charge:  # cut change
                        fc = first_cap_array[nr_module, :, :]
                        first_cap = (fc[gain, pix]) % n_cap
                        bin = int(first_cap / n_combine)
                        mean_values_per_bin[gain, pixel,
                                            bin] += peak_time[gain, pixel]
                        entries_per_bin[gain, pixel, bin] += 1

    def finalize(self):
        n_total = self.entries_per_bin.size
        n_available = np.count_nonzero(self.entries_per_bin)
        if n_available < n_total:
            raise RuntimeError(
                "No data available for some capacitors. "
                "It might help to use more events to create the calibration file. "
                f"Available: {n_available / n_total:.3%}, Missing: {n_total - n_available}"
            )
        else:
            self.mean_values_per_bin = self.mean_values_per_bin / self.entries_per_bin
            self.save_to_hdf5_file()

    def fit(self, pixel_id, gain):
        """
            Fit data bins using Fourier series expansion
            Parameters
            ----------
            pixel_id : ndarray
            Array stored expected pixel id of shape
            (n_pixels).
            gain: int
            0 for high gain, 1 for low gain
        """
        self.pos = np.zeros(self.n_bins)
        for i in range(0, self.n_bins):
            self.pos[i] = (i + 0.5) * self.n_combine

        self.fan = np.zeros(self.n_harmonics)  # cos coeff
        self.fbn = np.zeros(self.n_harmonics)  # sin coeff

        for n in range(0, self.n_harmonics):
            self.integrate_with_trig(self.pos,
                                     self.mean_values_per_bin[gain, pixel_id],
                                     n, self.fan, self.fbn)

    def integrate_with_trig(self, x, y, n, an, bn):
        """
            Function to expanding into Fourier series
            Parameters
            ----------
            x : ndarray
            Array stored position in DRS4 ring of shape
            (n_bins).
            y: ndarray
            Array stored mean pulse time per bin of shape
            (n_bins)
            n : int
            n harmonic
            an: ndarray
            Array to fill with cos coeff of shape
            (n_harmonics)
            bn: ndarray
            Array to fill with sin coeff of shape
            (n_harmonics)
        """
        suma = 0
        sumb = 0

        for i in range(0, self.n_bins):
            suma += y[i] * self.n_combine * np.cos(
                2 * np.pi * n * (x[i] / float(self.n_capacitors)))
            sumb += y[i] * self.n_combine * np.sin(
                2 * np.pi * n * (x[i] / float(self.n_capacitors)))

        an[n] = suma * (2. / (self.n_bins * self.n_combine))
        bn[n] = sumb * (2. / (self.n_bins * self.n_combine))

    def get_first_capacitor(self, event, nr):
        fc = np.zeros((n_gain, n_channel))
        first_cap = event.lst.tel[
            self.tel_id].evt.first_capacitor_id[nr * 8:(nr + 1) * 8]
        # First capacitor order according Dragon v5 board data format
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [0, 0, 1, 1, 2, 2, 3]):
            fc[high_gain, i] = first_cap[j]
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [4, 4, 5, 5, 6, 6, 7]):
            fc[low_gain, i] = first_cap[j]
        return fc

    def save_to_hdf5_file(self):
        """
            Function to save Fourier series expansion coeff into hdf5 file
        """
        fan_array = np.zeros((n_gain, n_pixels, self.n_harmonics))
        fbn_array = np.zeros((n_gain, n_pixels, self.n_harmonics))

        for pix_id in range(n_pixels):
            self.fit(pix_id, gain=high_gain)
            fan_array[high_gain, pix_id, :] = self.fan
            fbn_array[high_gain, pix_id, :] = self.fbn

            self.fit(pix_id, gain=low_gain)
            fan_array[low_gain, pix_id, :] = self.fan
            fbn_array[low_gain, pix_id, :] = self.fbn

        try:
            with h5py.File(self.calib_file_path, 'w') as hf:
                hf.create_dataset('fan', data=fan_array)
                hf.create_dataset('fbn', data=fbn_array)
                hf.attrs['n_events'] = self.sum_events
                hf.attrs['n_harm'] = self.n_harmonics
                # need pytables and time calib container
                # to use lstchain.io.add_config_metadata
                hf.attrs['config'] = str(self.config)

            metadata = global_metadata()
            write_metadata(metadata, self.calib_file_path)

        except Exception:
            raise IOError(f"Failed to create the file {self.calib_file_path}")
コード例 #19
0
ファイル: calibrator.py プロジェクト: morcuended/cta-lstchain
class LSTCameraCalibrator(CameraCalibrator):
    """
    Calibrator to handle the LST camera calibration chain, in order to fill
    the DL1 data level in the event container.
    """
    extractor_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    reducer_product = Unicode(
        'NullDataVolumeReducer',
        help='Name of the DataVolumeReducer to use').tag(config=True)

    calibration_path = Unicode(
        '', help='Path to LST calibration file').tag(config=True)

    time_calibration_path = Unicode(
        '', help='Path to drs4 time calibration file').tag(config=True)

    allowed_tels = List(
        [1], help='List of telescope to be calibrated').tag(config=True)

    gain_threshold = Int(
        4094, allow_none=True,
        help='Threshold for the gain selection in ADC').tag(config=True)

    charge_scale = List(
        [1, 1],
        help='Multiplicative correction factor for charge estimation [HG,LG]'
    ).tag(config=True)

    def __init__(self, subarray, **kwargs):
        """
        Parameters
        ----------

        reducer_product : ctapipe.image.reducer.DataVolumeReducer
            The DataVolumeReducer to use. If None, then
            NullDataVolumeReducer will be used by default, and waveforms
            will not be reduced.
        extractor_product : ctapipe.image.extractor.ImageExtractor
            The ImageExtractor to use. If None, then LocalPeakWindowSum
            will be used by default.
        calibration_path :
            Path to LST calibration file to get the pedestal and flat-field corrections


        kwargs
        """
        super().__init__(subarray, **kwargs)

        # load the waveform charge extractor
        self.image_extractor = ImageExtractor.from_name(self.extractor_product,
                                                        subarray=self.subarray,
                                                        config=self.config)
        self.log.info(f"extractor {self.extractor_product}")

        print("EXTRACTOR", self.image_extractor)

        self.data_volume_reducer = DataVolumeReducer.from_name(
            self.reducer_product, subarray=self.subarray, config=self.config)
        self.log.info(f" {self.reducer_product}")

        # declare gain selector if the threshold is defined
        if self.gain_threshold:
            self.gain_selector = gainselection.ThresholdGainSelector(
                threshold=self.gain_threshold)

        # declare time calibrator if correction file exist
        if os.path.exists(self.time_calibration_path):
            self.time_corrector = PulseTimeCorrection(
                calib_file_path=self.time_calibration_path)
        else:
            raise IOError(
                f"Time calibration file {self.time_calibration_path} not found!"
            )

        # calibration data container
        self.mon_data = MonitoringContainer()

        # initialize the MonitoringContainer() for the moment it reads it from a hdf5 file
        self._initialize_correction()

        self.log.info(f"Global charge scale {self.charge_scale}")

    def _initialize_correction(self):
        """
        Read the correction from hdf5 calibration file
        """

        self.mon_data.tels_with_data = self.allowed_tels
        self.log.info(f"read {self.calibration_path}")

        try:
            with HDF5TableReader(self.calibration_path) as h5_table:
                for telid in self.allowed_tels:
                    # read the calibration data
                    table = '/tel_' + str(telid) + '/calibration'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].calibration))

                    # read pedestal data
                    table = '/tel_' + str(telid) + '/pedestal'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pedestal))

                    # read flat-field data
                    table = '/tel_' + str(telid) + '/flatfield'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].flatfield))

                    # read the pixel_status container
                    table = '/tel_' + str(telid) + '/pixel_status'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pixel_status))
        except Exception:
            self.log.exception(
                f"Problem in reading calibration file {self.calibration_path}")
            raise

    def _calibrate_dl0(self, event, telid):
        """
        create dl0 level, for the moment copy the r1
        """
        waveforms = event.r1.tel[telid].waveform

        if self._check_r1_empty(waveforms):
            return

        # if not already done, initialize the event monitoring containers
        if event.mon.tel[telid].calibration.dc_to_pe is None:
            event.mon.tel[telid].calibration = self.mon_data.tel[
                telid].calibration
            event.mon.tel[telid].flatfield = self.mon_data.tel[telid].flatfield
            event.mon.tel[telid].pedestal = self.mon_data.tel[telid].pedestal
            event.mon.tel[telid].pixel_status = self.mon_data.tel[
                telid].pixel_status

        #
        # subtract the pedestal per sample and multiply for the calibration coefficients
        #
        event.dl0.tel[telid].waveform = (
            (waveforms - self.mon_data.tel[telid].calibration.
             pedestal_per_sample[:, :, np.newaxis]) *
            self.mon_data.tel[telid].calibration.dc_to_pe[:, :,
                                                          np.newaxis]).astype(
                                                              np.float32)

    def _calibrate_dl1(self, event, telid):
        """
        create calibrated dl1 image and calibrate it
        """
        waveforms = event.dl0.tel[telid].waveform

        if self._check_dl0_empty(waveforms):
            return

        # for the moment we do the gain selection afterwards
        # use gain mask without gain selection

        # TBD: - perform calibration of the R1 waveform (not DL1)
        #      - gain selection before charge integration

        # In case of no gain selection the selected gain channels are  [0,0,..][1,1,..]
        no_gain_selection = np.zeros((waveforms.shape[0], waveforms.shape[1]),
                                     dtype=np.int)
        no_gain_selection[1] = 1

        charge = np.zeros((waveforms.shape[0], waveforms.shape[1]),
                          dtype='float32')
        peak_time = np.zeros((waveforms.shape[0], waveforms.shape[1]),
                             dtype='float32')
        # image extraction for each channel:
        for i in range(waveforms.shape[0]):
            charge[i], peak_time[i] = self.image_extractor(
                waveforms[i], telid, no_gain_selection[i])

        # correct charge for global scale
        corrected_charge = charge * np.array(self.charge_scale,
                                             dtype=np.float32)[:, np.newaxis]

        # correct time with drs4 correction if available
        if self.time_corrector:
            peak_time = self.time_corrector.get_corr_pulse(event, peak_time)

        # add flat-fielding time correction
        peak_time_ff_corrected = peak_time + self.mon_data.tel[
            telid].calibration.time_correction.value

        # perform the gain selection if the threshold is defined
        if self.gain_threshold:
            gain_mask = self.gain_selector(event.r1.tel[telid].waveform)

            event.dl1.tel[telid].image = corrected_charge[
                gain_mask, np.arange(charge.shape[1])]
            event.dl1.tel[telid].peak_time = \
                peak_time_ff_corrected[gain_mask, np.arange(peak_time_ff_corrected.shape[1])].astype(np.float32)

            # remember which channel has been selected
            event.r1.tel[telid].selected_gain_channel = gain_mask

        # if threshold == None
        else:
            event.dl1.tel[telid].image = corrected_charge
            event.dl1.tel[telid].peak_time = peak_time_ff_corrected
コード例 #20
0
ファイル: drs4.py プロジェクト: mstrzys/cta-lstchain
class DragonPedestal(Component):
    """
        The DragonPedestal class to create pedestal
        for LST readout system using chip DRS4.
    """

    r0_sample_start = Int(default_value=11,
                          help='Start sample for waveform'
                          ).tag(config=True)

    def __init__(self, tel_id, n_module, **kwargs):
        super().__init__(**kwargs)
        self.tel_id = tel_id
        self.n_module = n_module # This is number of module read from data

        # Readout system of LST has 265 modules.
        # Each module has 7 channels (pixels)
        self.n_pixels = n_module_in_camera*n_channel
        self.meanped = np.zeros((n_gain, self.n_pixels, size4drs))
        self.numped = np.zeros((n_gain, self.n_pixels, size4drs))
        self.first_cap_array = np.zeros((self.n_module, n_gain, n_channel))
        self.failing_pixels_array = np.full((self.n_pixels), False)

    def fill_pedestal_event(self, event):
        expected_pixel_id = event.lst.tel[self.tel_id].svc.pixel_ids
        waveform = event.r0.tel[self.tel_id].waveform
        for nr_module in prange(0, self.n_module):
            self.first_cap_array[nr_module, :, :] = self.get_first_capacitor(event, nr_module)

        self._fill_pedestal_event_jit(waveform,
                                      expected_pixel_id,
                                      self.first_cap_array,
                                      self.meanped,
                                      self.numped,
                                      self.n_module,
                                      self.r0_sample_start)

    @staticmethod
    @jit(parallel=True)
    def _fill_pedestal_event_jit(waveform, expected_pixel_id, first_cap_array,
                                 meanped, numped, n_module, start_sample_r0):
        for nr_module in prange(0, n_module):
            first_cap = first_cap_array[nr_module, :, :]
            for gain in prange(0, n_gain):
                for pix in prange(0, n_channel):
                    fc = first_cap[gain, pix]
                    pixel = expected_pixel_id[nr_module * 7 + pix]
                    posads0 = int((start_sample_r0+fc)%size4drs)
                    if posads0 + roi_size < size4drs:
                        # the first 9 samples have occasionally increased signal due to Tsutomu pattern,
                        # hence we skip them. Start sample might be set as script argument. Default = 11.
                        meanped[gain, pixel, posads0:((posads0-start_sample_r0) + roi_size-2)] += waveform[gain, pixel, start_sample_r0:roi_size-2]
                        numped[gain, pixel, posads0:((posads0-start_sample_r0) + roi_size-2)] += 1
                    else:
                        for k in prange(start_sample_r0, roi_size - 2):
                            # the first 9 samples have occasionally increased signal due to Tsutomu pattern,
                            # hence we skip them. Start sample might be set as script argument. Default = 11.
                            posads = int((k + fc) % size4drs)
                            val = waveform[gain, pixel, k]
                            meanped[gain, pixel, posads] += val
                            numped[gain, pixel, posads] += 1

    def finalize_pedestal(self):
        self.meanped = self.meanped / self.numped
        pixels_with_nan_value = np.where(np.isnan(self.meanped).any(axis=0))
        if len(pixels_with_nan_value[0]) > 0:
            # Find failing pixels id
            index_failing_pixels = np.unique(pixels_with_nan_value[0])
            self.failing_pixels_array[index_failing_pixels] = True
            print("Failing pixels:")
            print(index_failing_pixels)

    def get_first_capacitor(self, event, nr):
        fc = np.zeros((2, 7))
        first_cap = event.lst.tel[self.tel_id].evt.first_capacitor_id[nr * 8:(nr + 1) * 8]
        # First capacitor order according Dragon v5 board data format
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [0, 0, 1, 1, 2, 2, 3]):
            fc[high_gain, i] = first_cap[j]
        for i, j in zip([0, 1, 2, 3, 4, 5, 6], [4, 4, 5, 5, 6, 6, 7]):
            fc[low_gain, i] = first_cap[j]
        return fc
コード例 #21
0
class CleanigPedestalImage(Component):
    """
        Class to chceck pedestal image
    """

    tel_id = Int(1, help='Id of the telescope to calibrate').tag(config=True)

    charge_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    calib_file = Unicode('',
                         allow_none=True,
                         help='Path to the calibration file').tag(config=True)

    calib_time_file = Unicode(
        '', allow_none=True,
        help="Path to the time calibration file").tag(config=True)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.cleaning_parameters = self.config["tailcut"]
        print(self.config)
        self.r1_dl1_calibrator = LSTCameraCalibrator(
            calibration_path=self.calib_file,
            time_calibration_path=self.calib_time_file,
            extractor_product=self.charge_product,
            config=self.config,
            gain_threshold=Config(
                self.config).gain_selector_config['threshold'],
            allowed_tels=[1])

    def run(self, list_of_file, max_events):

        signal_place_after_clean = np.zeros(1855)
        sum_ped_ev = 0
        alive_ped_ev = 0

        for input_file in list_of_file:
            print(input_file)

            r0_r1_calibrator = LSTR0Corrections(pedestal_path=None,
                                                r1_sample_start=3,
                                                r1_sample_end=39)
            reader = LSTEventSource(input_url=input_file,
                                    max_events=max_events)
            for i, ev in enumerate(reader):
                r0_r1_calibrator.calibrate(ev)
                if i % 10000 == 0:
                    print(ev.r0.event_id)

                if ev.lst.tel[1].evt.tib_masked_trigger == 32:
                    sum_ped_ev += 1
                    self.r1_dl1_calibrator(ev)

                    img = ev.dl1.tel[1].image

                    geom = ev.inst.subarray.tel[1].camera
                    clean = tailcuts_clean(geom, img,
                                           **self.cleaning_parameters)

                    cleaned = img.copy()
                    cleaned[~clean] = 0.0

                    signal_place_after_clean[np.where(clean == True)] += 1

                    if np.sum(cleaned > 0) > 0:
                        alive_ped_ev += 1

        fig, ax = plt.subplots(figsize=(10, 8))
        geom = ev.inst.subarray.tel[1].camera

        disp0 = CameraDisplay(geom, ax=ax)
        disp0.image = signal_place_after_clean / sum_ped_ev
        disp0.add_colorbar(ax=ax,
                           label="N times signal remain after cleaning [%]")
        disp0.cmap = 'gnuplot2'
        ax.set_title("{} \n {}/{}".format(
            input_file.split("/")[-1][8:21], alive_ped_ev, sum_ped_ev),
                     fontsize=25)

        print("{}/{}".format(alive_ped_ev, sum_ped_ev))

        ax.set_xlabel(" ")
        ax.set_ylabel(" ")
        plt.tight_layout()
        plt.show()

    def remove_star_and_run(self, list_of_file, max_events,
                            noise_pixels_id_list):
        signal_place_after_clean = np.zeros(1855)
        sum_ped_ev = 0
        alive_ped_ev = 0

        for input_file in list_of_file:
            print(input_file)

            r0_r1_calibrator = LSTR0Corrections(pedestal_path=None,
                                                r1_sample_start=3,
                                                r1_sample_end=39)
            reader = LSTEventSource(input_url=input_file,
                                    max_events=max_events)
            for i, ev in enumerate(reader):
                r0_r1_calibrator.calibrate(ev)
                if i % 10000 == 0:
                    print(ev.r0.event_id)

                if ev.lst.tel[1].evt.tib_masked_trigger == 32:
                    sum_ped_ev += 1
                    self.r1_dl1_calibrator(ev)

                    img = ev.dl1.tel[1].image
                    img[noise_pixels_id_list] = 0

                    geom = ev.inst.subarray.tel[1].camera
                    clean = tailcuts_clean(geom, img,
                                           **self.cleaning_parameters)

                    cleaned = img.copy()
                    cleaned[~clean] = 0.0

                    signal_place_after_clean[np.where(clean == True)] += 1

                    if np.sum(cleaned > 0) > 0:
                        alive_ped_ev += 1

        fig, ax = plt.subplots(figsize=(10, 8))
        geom = ev.inst.subarray.tel[1].camera

        disp0 = CameraDisplay(geom, ax=ax)
        disp0.image = signal_place_after_clean / sum_ped_ev
        disp0.highlight_pixels(noise_pixels_id_list, linewidth=3)
        disp0.add_colorbar(ax=ax,
                           label="N times signal remain after cleaning [%]")
        disp0.cmap = 'gnuplot2'
        ax.set_title("{} \n {}/{}".format(
            input_file.split("/")[-1][8:21], alive_ped_ev, sum_ped_ev),
                     fontsize=25)

        print("{}/{}".format(alive_ped_ev, sum_ped_ev))

        ax.set_xlabel(" ")
        ax.set_ylabel(" ")
        plt.tight_layout()
        plt.show()

    def plot_camera_display(self, image, input_file, noise_pixels_id_list,
                            alive_ped_ev, sum_ped_ev):
        fig, ax = plt.subplots(figsize=(10, 8))
        geom = CameraGeometry.from_name('LSTCam-003')

        disp0 = CameraDisplay(geom, ax=ax)
        disp0.image = image
        disp0.highlight_pixels(noise_pixels_id_list, linewidth=3)
        disp0.add_colorbar(ax=ax,
                           label="N times signal remain after cleaning [%]")
        disp0.cmap = 'gnuplot2'
        ax.set_title("{} \n {}/{}".format(
            input_file.split("/")[-1][8:21], alive_ped_ev, sum_ped_ev),
                     fontsize=25)

        print("{}/{}".format(alive_ped_ev, sum_ped_ev))

        ax.set_xlabel(" ")
        ax.set_ylabel(" ")
        plt.tight_layout()
        plt.show()

    def check_interleave_pedestal_cleaning(self, list_of_file, max_events,
                                           sigma, dl1_file):

        high_gain = 0
        ped_mean_pe, ped_rms_pe = get_bias_and_rms(dl1_file)
        bad_pixel_ids = np.where(ped_rms_pe[1, high_gain, :] == 0)[0]
        print(bad_pixel_ids)
        th = get_threshold(ped_mean_pe[1, high_gain, :],
                           ped_rms_pe[1, high_gain, :], sigma)

        make_camera_binary_image(th, sigma,
                                 self.cleaning_parameters['picture_thresh'],
                                 bad_pixel_ids)

        signal_place_after_clean = np.zeros(1855)
        sum_ped_ev = 0
        alive_ped_ev = 0

        for input_file in list_of_file:
            print(input_file)

            r0_r1_calibrator = LSTR0Corrections(pedestal_path=None,
                                                r1_sample_start=3,
                                                r1_sample_end=39)

            reader = LSTEventSource(input_url=input_file,
                                    max_events=max_events)

            for i, ev in enumerate(reader):
                r0_r1_calibrator.calibrate(ev)
                if i % 10000 == 0:
                    print(ev.r0.event_id)

                if ev.lst.tel[1].evt.tib_masked_trigger == 32:
                    sum_ped_ev += 1
                    self.r1_dl1_calibrator(ev)

                    img = ev.dl1.tel[1].image
                    img[bad_pixel_ids] = 0
                    geom = ev.inst.subarray.tel[1].camera
                    clean = tailcuts_pedestal_clean(geom, img, th,
                                                    **self.cleaning_parameters)

                    cleaned = img.copy()
                    cleaned[~clean] = 0.0

                    signal_place_after_clean[np.where(clean == True)] += 1
                    if np.sum(cleaned > 0) > 0:
                        alive_ped_ev += 1

        noise_remain = signal_place_after_clean / sum_ped_ev

        self.plot_camera_display(noise_remain, input_file, bad_pixel_ids,
                                 alive_ped_ev, sum_ped_ev)
コード例 #22
0
class SingleTelEventDisplay(Tool):
    name = "ctapipe-display-televents"
    description = Unicode(__doc__)

    infile = Unicode(help="input file to read", default='').tag(config=True)
    tel = Int(help='Telescope ID to display', default=0).tag(config=True)
    channel = Integer(help="channel number to display", min=0,
                      max=1).tag(config=True)
    write = Bool(help="Write out images to PNG files",
                 default=False).tag(config=True)
    clean = Bool(help="Apply image cleaning", default=False).tag(config=True)
    hillas = Bool(help="Apply and display Hillas parametrization",
                  default=False).tag(config=True)
    samples = Bool(help="Show each sample", default=False).tag(config=True)
    display = Bool(help="Display results in interactive window",
                   default_value=True).tag(config=True)
    delay = Float(help='delay between events in s',
                  default_value=0.01,
                  min=0.001).tag(config=True)
    progress = Bool(help='display progress bar',
                    default_value=True).tag(config=True)

    aliases = Dict({
        'infile': 'SingleTelEventDisplay.infile',
        'tel': 'SingleTelEventDisplay.tel',
        'max-events': 'EventSource.max_events',
        'channel': 'SingleTelEventDisplay.channel',
        'write': 'SingleTelEventDisplay.write',
        'clean': 'SingleTelEventDisplay.clean',
        'hillas': 'SingleTelEventDisplay.hillas',
        'samples': 'SingleTelEventDisplay.samples',
        'display': 'SingleTelEventDisplay.display',
        'delay': 'SingleTelEventDisplay.delay',
        'progress': 'SingleTelEventDisplay.progress'
    })

    classes = List([EventSource, CameraCalibrator])

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def setup(self):
        print('TOLLES INFILE', self.infile)
        self.event_source = EventSource.from_url(self.infile, parent=self)
        self.event_source.allowed_tels = {
            self.tel,
        }

        self.calibrator = CameraCalibrator(parent=self)

        self.log.info(f'SELECTING EVENTS FROM TELESCOPE {self.tel}')

    def start(self):

        disp = None

        for event in tqdm(self.event_source,
                          desc=f'Tel{self.tel}',
                          total=self.event_source.max_events,
                          disable=~self.progress):

            self.log.debug(event.trig)
            self.log.debug(f"Energy: {event.mc.energy}")

            self.calibrator(event)

            if disp is None:
                geom = event.inst.subarray.tel[self.tel].camera
                self.log.info(geom)
                disp = CameraDisplay(geom)
                # disp.enable_pixel_picker()
                disp.add_colorbar()
                if self.display:
                    plt.show(block=False)

            # display the event
            disp.axes.set_title('CT{:03d} ({}), event {:06d}'.format(
                self.tel, geom.cam_id, event.r0.event_id))

            if self.samples:
                # display time-varying event
                data = event.dl0.tel[self.tel].waveform[self.channel]
                for ii in range(data.shape[1]):
                    disp.image = data[:, ii]
                    disp.set_limits_percent(70)
                    plt.suptitle(f"Sample {ii:03d}")
                    if self.display:
                        plt.pause(self.delay)
                    if self.write:
                        plt.savefig(
                            f'CT{self.tel:03d}_EV{event.r0.event_id:10d}'
                            f'_S{ii:02d}.png')
            else:
                # display integrated event:
                im = event.dl1.tel[self.tel].image[self.channel]

                if self.clean:
                    mask = tailcuts_clean(geom,
                                          im,
                                          picture_thresh=10,
                                          boundary_thresh=7)
                    im[~mask] = 0.0

                disp.image = im

                if self.hillas:
                    try:
                        ellipses = disp.axes.findobj(Ellipse)
                        if len(ellipses) > 0:
                            ellipses[0].remove()

                        params = hillas_parameters(geom, image=im)
                        disp.overlay_moments(params,
                                             color='pink',
                                             lw=3,
                                             with_label=False)
                    except HillasParameterizationError:
                        pass

                if self.display:
                    plt.pause(self.delay)
                if self.write:
                    plt.savefig(
                        f'CT{self.tel:03d}_EV{event.r0.event_id:010d}.png')

        self.log.info("FINISHED READING DATA FILE")

        if disp is None:
            self.log.warning(
                'No events for tel {} were found in {}. Try a '
                'different EventIO file or another telescope'.format(
                    self.tel, self.infile), )
コード例 #23
0
class LSTEventSource(EventSource):
    """EventSource for LST r0 data."""

    n_gains = Int(
        2,
        help='Number of gains at r0/r1 level'
    ).tag(config=True)

    baseline = Int(
        400,
        help='r0 waveform baseline (default from EvB v3)'
    ).tag(config=True)

    multi_streams = Bool(
        True,
        help='Read in parallel all streams '
    ).tag(config=True)

    def __init__(self, **kwargs):
        """
        Constructor
        Parameters
        ----------
        n_gains = number of gains expected in input file

        baseline = baseline to be subtracted at r1 level (not used for the moment)

        multi_streams = enable the reading of input files from all streams

        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.\
        kwargs: dict
            Additional parameters to be passed.
            NOTE: The file mask of the data to read can be passed with
            the 'input_url' parameter.
        """

        # EventSource can not handle file wild cards as input_url
        # To overcome this we substitute the input_url with first file matching
        # the specified file mask (copied from  MAGICEventSourceROOT).


        super().__init__(**kwargs)

        if self.multi_streams:
            # test how many streams are there:
            # file name must be [stream name]Run[all the rest]
            # All the files with the same [all the rest] are opened

            if '/' in self.input_url:
                dir, name = self.input_url.rsplit('/', 1)
            else:
                dir = getcwd()
                name = self.input_url

            if 'Run' in name:
                stream, run = name.split('Run', 1)
            else:
                run = name

            ls = listdir(dir)
            self.file_list = []

            for file_name in ls:
                if run in file_name:
                    full_name = dir + '/' + file_name
                    self.file_list.append(full_name)
                    Provenance().add_input_file(full_name, role='dl0.sub.evt')
        else:
            self.file_list = [self.input_url]

        self.multi_file = MultiFiles(self.file_list)

        self.camera_config = self.multi_file.camera_config
        self.log.info(
            "Read {} input files".format(
                self.multi_file.num_inputs()
            )
        )

    def rewind(self):
        self.multi_file.rewind()

    def _generator(self):

        # container for LST data
        self.data = LSTDataContainer()
        self.data.meta['input_url'] = self.input_url
        self.data.meta['max_events'] = self.max_events
        self.data.meta['origin'] = 'LSTCAM'

        # fill LST data from the CameraConfig table
        self.fill_lst_service_container_from_zfile()

        # Instrument information
        for tel_id in self.data.lst.tels_with_data:

            assert (tel_id == 0 or tel_id == 1) # only LST1 (for the moment id = 0)

            # optics info from standard optics.fits.gz file
            optics = OpticsDescription.from_name("LST")

            # camera info from LSTCam-[geometry_version].camgeom.fits.gz file
            geometry_version = 2
            camera = CameraGeometry.from_name("LSTCam", geometry_version)

            tel_descr = TelescopeDescription(
                name='LST', tel_type='LST', optics=optics, camera=camera
            )

            self.n_camera_pixels = tel_descr.camera.n_pixels
            tels = {tel_id: tel_descr}

            # LSTs telescope position taken from MC from the moment
            tel_pos = {tel_id: [50., 50., 16] * u.m}

        subarray = SubarrayDescription("LST1 subarray")
        subarray.tels = tels
        subarray.positions = tel_pos

        self.data.inst.subarray = subarray

        # initialize general monitoring container
        self.initialize_mon_container()

        # loop on events
        for count, event in enumerate(self.multi_file):

            self.data.count = count

            # fill specific LST event data
            self.fill_lst_event_container_from_zfile(event)

            # fill general monitoring data
            self.fill_mon_container_from_zfile(event)

            # fill general R0 data
            self.fill_r0_container_from_zfile(event)

            yield self.data

    @staticmethod
    def is_compatible(file_path):
        from astropy.io import fits
        try:
            # The file contains two tables:
            #  1: CameraConfig
            #  2: Events
            h = fits.open(file_path)[2].header
            ttypes = [
                h[x] for x in h.keys() if 'TTYPE' in x
            ]
        except OSError:
            # not even a fits file
            return False

        except IndexError:
            # A fits file of a different format
            return False

        is_protobuf_zfits_file = (
            (h['XTENSION'] == 'BINTABLE') and
            (h['EXTNAME'] == 'Events') and
            (h['ZTABLE'] is True) and
            (h['ORIGIN'] == 'CTA') and
            (h['PBFHEAD'] == 'R1.CameraEvent')
        )

        is_lst_file = 'lstcam_counters' in ttypes
        return is_protobuf_zfits_file & is_lst_file

    def fill_lst_service_container_from_zfile(self):
        """
        Fill LSTServiceContainer with specific LST service data data
        (from the CameraConfig table of zfit file)

        """

        self.data.lst.tels_with_data = [self.camera_config.telescope_id, ]
        svc_container = self.data.lst.tel[self.camera_config.telescope_id].svc

        svc_container.telescope_id = self.camera_config.telescope_id
        svc_container.cs_serial = self.camera_config.cs_serial
        svc_container.configuration_id = self.camera_config.configuration_id
        svc_container.date = self.camera_config.date
        svc_container.num_pixels = self.camera_config.num_pixels
        svc_container.num_samples = self.camera_config.num_samples
        svc_container.pixel_ids = self.camera_config.expected_pixels_id
        svc_container.data_model_version = self.camera_config.data_model_version

        svc_container.num_modules = self.camera_config.lstcam.num_modules
        svc_container.module_ids = self.camera_config.lstcam.expected_modules_id
        svc_container.idaq_version = self.camera_config.lstcam.idaq_version
        svc_container.cdhs_version = self.camera_config.lstcam.cdhs_version
        svc_container.algorithms = self.camera_config.lstcam.algorithms
        svc_container.pre_proc_algorithms = self.camera_config.lstcam.pre_proc_algorithms

    def fill_lst_event_container_from_zfile(self, event):
        """
        Fill LSTEventContainer with specific LST service data
        (from the Event table of zfit file)

        """

        event_container = self.data.lst.tel[self.camera_config.telescope_id].evt

        event_container.configuration_id = event.configuration_id
        event_container.event_id = event.event_id
        event_container.tel_event_id = event.tel_event_id
        event_container.pixel_status = event.pixel_status
        event_container.ped_id = event.ped_id
        event_container.module_status = event.lstcam.module_status
        event_container.extdevices_presence = event.lstcam.extdevices_presence

        # unpack TIB data
        rec_fmt = '=IHIBB'
        unpacked_tib = struct.unpack(rec_fmt, event.lstcam.tib_data)
        event_container.tib_event_counter = unpacked_tib[0]
        event_container.tib_pps_counter = unpacked_tib[1]
        event_container.tib_tenMHz_counter = unpacked_tib[2]
        event_container.tib_stereo_pattern = unpacked_tib[3]
        event_container.tib_masked_trigger = unpacked_tib[4]
        event_container.swat_data = event.lstcam.swat_data

        # unpack CDTS data
        rec_fmt = '=IIIQQBBB'
        unpacked_cdts =  struct.unpack(rec_fmt, event.lstcam.cdts_data)
        event_container.ucts_event_counter = unpacked_cdts[0]
        event_container.ucts_pps_counter = unpacked_cdts[1]
        event_container.ucts_clock_counter = unpacked_cdts[2]
        event_container.ucts_timestamp = unpacked_cdts[3]
        event_container.ucts_camera_timestamp = unpacked_cdts[4]
        event_container.ucts_trigger_type = unpacked_cdts[5]
        event_container.ucts_white_rabbit_status = unpacked_cdts[6]

        # unpack Dragon counters
        rec_fmt = '=HIIIQ'
        rec_len = struct.calcsize(rec_fmt)
        rec_unpack = struct.Struct(rec_fmt).unpack_from

        event_container.pps_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.tenMHz_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.event_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.trigger_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.local_clock_counter = np.zeros(self.camera_config.lstcam.num_modules)
        for mod in range(self.camera_config.lstcam.num_modules):

            words=event.lstcam.counters[mod*rec_len:(mod+1)*rec_len]
            unpacked_counter = rec_unpack(words)
            event_container.pps_counter[mod] = unpacked_counter[0]
            event_container.tenMHz_counter[mod] = unpacked_counter[1]
            event_container.event_counter[mod] = unpacked_counter[2]
            event_container.trigger_counter[mod] = unpacked_counter[3]
            event_container.local_clock_counter[mod] = unpacked_counter[4]

        event_container.chips_flags = event.lstcam.chips_flags
        event_container.first_capacitor_id = event.lstcam.first_capacitor_id
        event_container.drs_tag_status = event.lstcam.drs_tag_status
        event_container.drs_tag = event.lstcam.drs_tag

    def fill_r0_camera_container_from_zfile(self, r0_container, event):
        """
        Fill with R0CameraContainer

        """


        # temporary patch to have an event time set
        r0_container.trigger_time = (
            self.data.lst.tel[self.camera_config.telescope_id].evt.tib_pps_counter +
            self.data.lst.tel[self.camera_config.telescope_id].evt.tib_tenMHz_counter * 10**(-7))

        if r0_container.trigger_time is None:
            r0_container.trigger_time = 0
        #r0_container.trigger_type = event.trigger_type
        r0_container.trigger_type = self.data.lst.tel[self.camera_config.telescope_id].evt.tib_masked_trigger

        # verify the number of gains
        if event.waveform.shape[0] != self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains:
            raise ValueError(f"Number of gains not correct, waveform shape is {event.waveform.shape[0]}"
                             f" instead of "
                             f"{self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains}")

        reshaped_waveform = np.array(
            event.waveform
        ).reshape(
            self.n_gains,
            self.camera_config.num_pixels,
            self.camera_config.num_samples
        )

        # initialize the waveform container to zero
        r0_container.waveform = np.zeros([self.n_gains, self.n_camera_pixels,
                                          self.camera_config.num_samples])

        # re-order the waveform following the expected_pixels_id values
        # (rank = pixel id)
        r0_container.waveform[:, self.camera_config.expected_pixels_id, :] =\
            reshaped_waveform

    def fill_r0_container_from_zfile(self, event):
        """
        Fill with R0Container

        """
        container = self.data.r0

        container.obs_id = -1
        container.event_id = event.event_id

        container.tels_with_data = [self.camera_config.telescope_id, ]
        r0_camera_container = container.tel[self.camera_config.telescope_id]
        self.fill_r0_camera_container_from_zfile(
            r0_camera_container,
            event
        )

    def initialize_mon_container(self):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """
        container = self.data.mon
        container.tels_with_data = [self.camera_config.telescope_id, ]
        mon_camera_container = container.tel[self.camera_config.telescope_id]

        # initialize the container
        status_container = PixelStatusContainer()
        status_container.hardware_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.pedestal_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.flatfield_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)

        mon_camera_container.pixel_status = status_container

    def fill_mon_container_from_zfile(self, event):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """

        status_container = self.data.mon.tel[self.camera_config.telescope_id].pixel_status

        # reorder the array
        pixel_status = np.zeros(self.n_camera_pixels)
        pixel_status[self.camera_config.expected_pixels_id] = event.pixel_status
        status_container.hardware_failing_pixels[:] = pixel_status == 0
コード例 #24
0
class LSTCameraCalibrator(CameraCalibrator):
    """
    Calibrator to handle the LST camera calibration chain, in order to fill
    the DL1 data level in the event container.
    """
    extractor_product = Unicode(
        'LocalPeakWindowSum',
        help='Name of the charge extractor to be used').tag(config=True)

    reducer_product = Unicode(
        'NullDataVolumeReducer',
        help='Name of the DataVolumeReducer to use').tag(config=True)

    calibration_path = Path(
        exists=True, directory_ok=False,
        help='Path to LST calibration file').tag(config=True)

    time_calibration_path = Path(
        exists=True,
        directory_ok=False,
        help='Path to drs4 time calibration file').tag(config=True)

    time_sampling_correction_path = Path(
        exists=True,
        directory_ok=False,
        help='Path to time sampling correction file',
        allow_none=True,
    ).tag(config=True)

    allowed_tels = List(
        [1], help='List of telescope to be calibrated').tag(config=True)

    gain_threshold = Int(
        4094, allow_none=True,
        help='Threshold for the gain selection in ADC').tag(config=True)

    charge_scale = List(
        [1, 1],
        help='Multiplicative correction factor for charge estimation [HG,LG]'
    ).tag(config=True)

    def __init__(self, subarray, **kwargs):
        """
        Parameters
        ----------

        reducer_product : ctapipe.image.reducer.DataVolumeReducer
            The DataVolumeReducer to use. If None, then
            NullDataVolumeReducer will be used by default, and waveforms
            will not be reduced.
        extractor_product : ctapipe.image.extractor.ImageExtractor
            The ImageExtractor to use. If None, then LocalPeakWindowSum
            will be used by default.
        calibration_path :
            Path to LST calibration file to get the pedestal and flat-field corrections


        kwargs
        """
        super().__init__(subarray, **kwargs)

        # load the waveform charge extractor
        self.image_extractor = ImageExtractor.from_name(self.extractor_product,
                                                        subarray=self.subarray,
                                                        config=self.config)
        self.log.info(f"extractor {self.extractor_product}")

        print("EXTRACTOR", self.image_extractor)

        self.data_volume_reducer = DataVolumeReducer.from_name(
            self.reducer_product, subarray=self.subarray, config=self.config)
        self.log.info(f" {self.reducer_product}")

        # declare gain selector if the threshold is defined
        if self.gain_threshold:
            self.gain_selector = gainselection.ThresholdGainSelector(
                threshold=self.gain_threshold)

        # declare time calibrator if correction file exist

        if os.path.exists(self.time_calibration_path):

            self.time_corrector = PulseTimeCorrection(
                calib_file_path=self.time_calibration_path)
        else:
            raise IOError(
                f"Time calibration file {self.time_calibration_path} not found!"
            )

        # declare the charge sampling corrector
        if self.time_sampling_correction_path is not None:
            # search the file in resources if not found
            if not os.path.exists(self.time_sampling_correction_path):
                self.time_sampling_correction_path = resource_filename(
                    'lstchain',
                    f"resources/{self.time_sampling_correction_path}")

            if os.path.exists(self.time_sampling_correction_path):
                self.time_sampling_corrector = TimeSamplingCorrection(
                    time_sampling_correction_path=self.
                    time_sampling_correction_path)
            else:
                raise IOError(
                    f"Sampling correction file {self.time_sampling_correction_path} not found!"
                )
        else:
            self.time_sampling_corrector = None

        # calibration data container
        self.mon_data = MonitoringContainer()

        # initialize the MonitoringContainer() for the moment it reads it from a hdf5 file
        self._initialize_correction()

        self.log.info(f"Global charge scale {self.charge_scale}")

    def _initialize_correction(self):
        """
        Read the correction from hdf5 calibration file
        """

        self.log.info(f"read {self.calibration_path}")

        try:
            with HDF5TableReader(self.calibration_path) as h5_table:
                for telid in self.allowed_tels:
                    # read the calibration data
                    table = '/tel_' + str(telid) + '/calibration'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].calibration))

                    # read pedestal data
                    table = '/tel_' + str(telid) + '/pedestal'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pedestal))

                    # read flat-field data
                    table = '/tel_' + str(telid) + '/flatfield'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].flatfield))

                    # read the pixel_status container
                    table = '/tel_' + str(telid) + '/pixel_status'
                    next(
                        h5_table.read(table,
                                      self.mon_data.tel[telid].pixel_status))
        except Exception:
            self.log.exception(
                f"Problem in reading calibration file {self.calibration_path}")
            raise

    def _calibrate_dl0(self, event, telid):
        """
        create dl0 level, with gain-selected and calibrated waveform
        """
        waveforms = event.r1.tel[telid].waveform

        if self._check_r1_empty(waveforms):
            return

        # if not already done, initialize the event monitoring containers
        if event.mon.tel[telid].calibration.dc_to_pe is None:
            event.mon.tel[telid].calibration = self.mon_data.tel[
                telid].calibration
            event.mon.tel[telid].flatfield = self.mon_data.tel[telid].flatfield
            event.mon.tel[telid].pedestal = self.mon_data.tel[telid].pedestal
            event.mon.tel[telid].pixel_status = self.mon_data.tel[
                telid].pixel_status

        #
        # subtract the pedestal per sample and multiply for the calibration coefficients
        #
        calibrated_waveform = (
            (waveforms - self.mon_data.tel[telid].calibration.
             pedestal_per_sample[:, :, np.newaxis]) *
            self.mon_data.tel[telid].calibration.dc_to_pe[:, :,
                                                          np.newaxis]).astype(
                                                              np.float32)

        # If requested, perform gain selection (this will be done by the EvB in future)
        # find the gain selection mask
        if waveforms.ndim == 3:

            # if threshold defined, perform gain selection
            if self.gain_threshold:
                gain_mask = self.gain_selector(waveforms)

                # select the samples
                calibrated_waveform = calibrated_waveform[
                    gain_mask, np.arange(waveforms.shape[1])]

            else:
                # keep both HG and LG
                gain_mask = np.zeros((waveforms.shape[0], waveforms.shape[1]),
                                     dtype=np.int64)
                gain_mask[1] = 1
        else:
            # gain selection already performed in EvB: (0=HG, 1=LG)
            gain_mask = event.lst.tel[telid].evt.pixel_status >> 2 & 1

        # remember the calibrated and gain selected waveform
        # (this should be the r1 waveform to be compliant with ctapipe (?))
        event.dl0.tel[telid].waveform = calibrated_waveform

        # remember which channel has been selected
        event.r1.tel[telid].selected_gain_channel = gain_mask
        event.dl0.tel[telid].selected_gain_channel = gain_mask

    def _calibrate_dl1(self, event, telid):
        """
        create calibrated dl1 image and calibrate it
        """

        n_pixels = self.subarray.tels[telid].camera.geometry.n_pixels

        # copy the waveform be cause I do not want to change it
        waveforms = np.copy(event.dl0.tel[telid].waveform)
        gain_mask = event.dl0.tel[telid].selected_gain_channel

        if self._check_dl0_empty(waveforms):
            return

        # In case of no gain selection the selected gain channels are  [0,0,..][1,1,..]
        no_gain_selection = np.zeros((waveforms.shape[0], waveforms.shape[1]),
                                     dtype=np.int64)
        no_gain_selection[1] = 1

        # correct the dl0 waveform for the sampling time corrections
        if self.time_sampling_corrector:
            waveforms *= self.time_sampling_corrector.get_corrections(
                event, telid)[gain_mask, np.arange(n_pixels)]

        # extract the charge
        charge, peak_time = self.image_extractor(waveforms, telid, gain_mask)

        # correct charge for global scale
        corrected_charge = charge * np.array(self.charge_scale,
                                             dtype=np.float32)[gain_mask]

        # correct time with drs4 correction if available
        if self.time_corrector:
            peak_time_drs4_corrected = (
                peak_time - self.time_corrector.get_pulse_time_corrections(
                    event)[gain_mask, np.arange(n_pixels)])

        # add flat-fielding time correction
        peak_time_ff_corrected = (
            peak_time_drs4_corrected +
            self.mon_data.tel[telid].calibration.time_correction.value[
                gain_mask, np.arange(n_pixels)])

        # fill dl1 container
        event.dl1.tel[telid].image = corrected_charge
        event.dl1.tel[telid].peak_time = peak_time_ff_corrected.astype(
            np.float32)
コード例 #25
0
class ChargeResolutionCalculator(Component):
    """
    Class to handle the calculation of Charge Resolution.

    Attributes
    ----------
    max_pe : int
        Maximum pe to calculate the charge resolution up to.
    sum_dict : dict
        Dictionary to store the running sum for each true charge.
    n_dict : dict
        Dictionary to store the running number for each true charge.
    variation_hist_nbins : float
        Number of bins for the variation histogram.
    variation_hist_range : list
        X and Y range for the variation histogram.
    variation_hist : `np.histogram2d`
    variation_xedges : ndarray
        Edges of the X bins for the variation histogram.
    variation_yedges : ndarray
        Edges of the Y bins for the variation histogram.
    """
    max_pe = Int(2000,
                 help='Maximum pe to calculate the charge resolution '
                 'up to').tag(config=True)
    binning = Int(60,
                  allow_none=True,
                  help='Number of bins for the Charge Resolution. If None, '
                  'no binning is performed.').tag(config=True)
    log_bins = Bool(True,
                    help='Bin the x axis linearly instead of '
                    'logarithmic.').tag(config=True)

    def __init__(self, config=None, tool=None, **kwargs):
        """
        Calculator of charge resolution.

        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
            Tool executable that is calling this component.
            Passes the correct logger to the component.
            Set to None if no Tool to pass.
        kwargs
        """
        super().__init__(config=config, parent=tool, **kwargs)

        self.sum_array = np.zeros(self.max_pe)
        self.n_array = np.zeros(self.max_pe)
        self.sum_dict = {}
        self.n_dict = {}

        self.variation_hist_nbins = log10(self.max_pe) * 50
        self.variation_hist_range = [[log10(1), log10(self.max_pe)],
                                     [log10(1), log10(self.max_pe)]]
        h, xedges, yedges = np.histogram2d([np.nan], [np.nan],
                                           bins=self.variation_hist_nbins,
                                           range=self.variation_hist_range)
        self.variation_hist = h
        self.variation_xedges = xedges
        self.variation_yedges = yedges

        self.storage_arrays = [
            'max_pe', 'sum_array', 'n_array', 'variation_hist_nbins',
            'variation_hist_range', 'variation_hist', 'variation_xedges',
            'variation_yedges'
        ]

    def add_charges(self, true_charge, measured_charge):
        """
        Fill the class parameters with a numpy array of true charge and
        measured (calibrated) charge from an event. The two arrays must be the
        same size.

        Parameters
        ----------
        true_charge : ndarray
            Array of true (MC) charge.
            Obtained from event.mc.tel[telid].image[channel]
        measured_charge : ndarray
            Array of measured (dl1 calibrated) charge.
            Obtained from event.mc.tel[tel_id].photo_electron_image
        """
        above_0 = (measured_charge > 0) & (true_charge > 0)
        x = true_charge[above_0]
        y = measured_charge[above_0]
        h, _, _ = np.histogram2d(np.log10(y),
                                 np.log10(x),
                                 bins=self.variation_hist_nbins,
                                 range=self.variation_hist_range)
        self.variation_hist += h

        in_range = (true_charge > 0) & (true_charge <= self.max_pe)
        true_q = true_charge[in_range]
        measured_q = measured_charge[in_range]
        np.add.at(self.sum_array, true_q - 1, np.power(measured_q - true_q, 2))
        np.add.at(self.n_array, true_q - 1, 1)

    def get_charge_resolution(self):
        """
        Calculate and obtain the charge resolution graph arrays.

        Returns
        -------
        true_charge : ndarray
            The X axis true charges.
        chargeres : ndarray
            The Y axis charge resolution values.
        chargeres_error : ndarray
            The error on the charge resolution.
        scaled_chargeres : ndarray
            The Y axis charge resolution divided by the Goal.
        scaled_chargeres_error : ndarray
            The error on the charge resolution divided by the Goal.
        """
        self.log.debug('[chargeres] Calculating charge resolution')

        n_1 = self.n_array > 0
        n = self.n_array[n_1]
        true = (np.arange(self.max_pe) + 1)[n_1]
        sum_ = self.sum_array[n_1]

        res = np.sqrt((sum_ / n) + true) / true
        res_error = res * (1 / np.sqrt(2 * n))

        scale = self.goal(true)
        scaled_res = res / scale
        scaled_res_error = res_error / scale

        if self.binning is not None:
            x = true
            if self.log_bins:
                x = np.log10(true)

            def binning(array):
                return bs(x, array, 'mean', bins=self.binning)

            def sum_errors(array):
                return np.sqrt(np.sum(np.power(array, 2))) / array.size

            def bin_errors(array):
                return bs(x, array, sum_errors, bins=self.binning)

            true, _, _ = binning(true)
            res, _, _ = binning(res)
            res_error, _, _ = bin_errors(res_error)
            scaled_res, _, _ = binning(scaled_res)
            scaled_res_error, _, _ = bin_errors(scaled_res_error)

        return true, res, res_error, scaled_res, scaled_res_error

    @staticmethod
    def limit_curves(npe, n_nsb, n_add, enf, sigma2):
        """
        Equation for calculating the Goal and Requirement curves, as defined
        in SCI-MC/121113.
        https://portal.cta-observatory.org/recordscentre/Records/SCI/
        SCI-MC/measurment_errors_system_performance_1YQCBC.pdf

        Parameters
        ----------
        npe : ndarray
            Number of photoeletrons (variable).
        n_nsb : float
            Number of NSB photons.
        n_add : float
            Number of photoelectrons from additional noise sources.
        enf : float
            Excess noise factor.
        sigma2 : float
            Percentage ofmultiplicative errors.
        """
        return (np.sqrt((n_nsb + n_add) + np.power(enf, 2) * npe +
                        np.power(sigma2 * npe, 2)) / npe).astype(float)

    @staticmethod
    def requirement(npe):
        """
        CTA requirement curve.

        Parameters
        ----------
        npe : ndarray
            Number of photoeletrons (variable).
        """
        n_nsb = sqrt(4.0 + 3.0)
        n_add = 0
        enf = 1.2
        sigma2 = 0.1
        defined_npe = 1000

        # If npe is not an array, temporarily convert it to one
        npe = np.array([npe])

        lc = ChargeResolutionCalculator.limit_curves
        requirement = lc(npe, n_nsb, n_add, enf, sigma2)
        requirement[npe > defined_npe] = np.nan

        return requirement[0]

    @staticmethod
    def goal(npe):
        """
        CTA goal curve.

        Parameters
        ----------
        npe : ndarray
            Number of photoeletrons (variable).
        """
        n_nsb = 2
        n_add = 0
        enf = 1.1152
        sigma2 = 0.05
        defined_npe = 2000

        # If npe is not an array, temporarily convert it to one
        npe = np.array([npe])

        lc = ChargeResolutionCalculator.limit_curves
        goal = lc(npe, n_nsb, n_add, enf, sigma2)
        goal[npe > defined_npe] = np.nan

        return goal[0]

    @staticmethod
    def poisson(npe):
        """
        Poisson limit curve.

        Parameters
        ----------
        npe : ndarray
            Number of photoeletrons (variable).
        """
        # If npe is not an array, temporarily convert it to one
        npe = np.array([npe])
        poisson = np.sqrt(npe) / npe

        return poisson[0]

    def save(self, path):
        output_dir = dirname(path)
        if not exists(output_dir):
            self.log.info("[output] Creating directory: {}".format(output_dir))
            makedirs(output_dir)
        self.log.info("Saving Charge Resolution file: {}".format(path))

        with open_file(path, mode="w", title="ChargeResolutionFile") as f:
            group = f.create_group("/", 'ChargeResolution', '')
            for arr in self.storage_arrays:
                f.create_array(group, arr, getattr(self, arr), arr)

    def load(self, path):
        self.log.info("Loading Charge Resolution file: {}".format(path))
        with open_file(path, mode="r") as f:
            for arr in self.storage_arrays:
                setattr(self, arr, f.get_node("/ChargeResolution", arr).read())
コード例 #26
0
class DataBinning(Component):
    """
    Collects information on generating energy and angular bins for
    generating IRFs as per pyIRF requirements.
    """

    true_energy_min = Float(
        help="Minimum value for True Energy bins in TeV units",
        default_value=0.01,
    ).tag(config=True)

    true_energy_max = Float(
        help="Maximum value for True Energy bins in TeV units",
        default_value=100,
    ).tag(config=True)

    true_energy_n_bins_per_decade = Float(
        help="Number of edges per decade for True Energy bins",
        default_value=5.5,
    ).tag(config=True)

    reco_energy_min = Float(
        help="Minimum value for Reco Energy bins in TeV units",
        default_value=0.01,
    ).tag(config=True)

    reco_energy_max = Float(
        help="Maximum value for Reco Energy bins in TeV units",
        default_value=100,
    ).tag(config=True)

    reco_energy_n_bins_per_decade = Float(
        help="Number of edges per decade for Reco Energy bins",
        default_value=5.5,
    ).tag(config=True)

    energy_migration_min = Float(
        help="Minimum value of Energy Migration matrix",
        default_value=0.2,
    ).tag(config=True)

    energy_migration_max = Float(
        help="Maximum value of Energy Migration matrix",
        default_value=5,
    ).tag(config=True)

    energy_migration_n_bins = Int(
        help="Number of bins in log scale for Energy Migration matrix",
        default_value=31,
    ).tag(config=True)

    fov_offset_min = Float(
        help="Minimum value for FoV Offset bins",
        default_value=0.1,
    ).tag(config=True)

    fov_offset_max = Float(
        help="Maximum value for FoV offset bins",
        default_value=1.1,
    ).tag(config=True)

    fov_offset_n_edges = Int(
        help="Number of edges for FoV offset bins",
        default_value=9,
    ).tag(config=True)

    bkg_fov_offset_min = Float(
        help="Minimum value for FoV offset bins for Background IRF",
        default_value=0,
    ).tag(config=True)

    bkg_fov_offset_max = Float(
        help="Maximum value for FoV offset bins for Background IRF",
        default_value=10,
    ).tag(config=True)

    bkg_fov_offset_n_edges = Int(
        help="Number of edges for FoV offset bins for Background IRF",
        default_value=21,
    ).tag(config=True)

    source_offset_min = Float(
        help="Minimum value for Source offset for PSF IRF",
        default_value=0.0001,
    ).tag(config=True)

    source_offset_max = Float(
        help="Maximum value for Source offset for PSF IRF",
        default_value=1.0001,
    ).tag(config=True)

    source_offset_n_edges = Int(
        help="Number of edges for Source offset for PSF IRF",
        default_value=1000,
    ).tag(config=True)

    def true_energy_bins(self):
        """
        Creates bins per decade for true MC energy using pyirf function.

        The overflow binning added is not needed at the current stage
        It can be used as - add_overflow_bins(***)[1:-1]
        """
        true_energy = create_bins_per_decade(
            self.true_energy_min * u.TeV,
            self.true_energy_max * u.TeV,
            self.true_energy_n_bins_per_decade,
        )
        return true_energy

    def reco_energy_bins(self):
        """
        Creates bins per decade for reconstructed MC energy using pyirf function.

        The overflow binning added is not needed at the current stage
        It can be used as - add_overflow_bins(***)[1:-1]
        """
        reco_energy = create_bins_per_decade(
            self.reco_energy_min * u.TeV,
            self.reco_energy_max * u.TeV,
            self.reco_energy_n_bins_per_decade,
        )
        return reco_energy

    def energy_migration_bins(self):
        """
        Creates bins for energy migration.
        """
        energy_migration = np.geomspace(
            self.energy_migration_min,
            self.energy_migration_max,
            self.energy_migration_n_bins,
        )
        return energy_migration

    def fov_offset_bins(self):
        """
        Creates bins for single/multiple FoV offset
        """
        fov_offset = (
            np.linspace(
                self.fov_offset_min,
                self.fov_offset_max,
                self.fov_offset_n_edges,
            )
            * u.deg
        )
        return fov_offset

    def bkg_fov_offset_bins(self):
        """
        Creates bins for FoV offset for Background IRF,
        Using the same binning as in pyirf example.
        """
        background_offset = (
            np.linspace(
                self.bkg_fov_offset_min,
                self.bkg_fov_offset_max,
                self.bkg_fov_offset_n_edges,
            )
            * u.deg
        )
        return background_offset

    def source_offset_bins(self):
        """
        Creates bins for source offset for generating PSF IRF.
        Using the same binning as in pyirf example.
        """

        source_offset = (
            np.linspace(
                self.source_offset_min,
                self.source_offset_max,
                self.source_offset_n_edges,
            )
            * u.deg
        )
        return source_offset
コード例 #27
0
ファイル: flatfield.py プロジェクト: watsonjj/ctapipe
class FlatFieldCalculator(Component):
    """
    Parent class for the flat-field calculators.
    Fills the MonitoringCameraContainer.FlatfieldContainer on the base of a given
    flat-field event sample.
    The sample is defined by a maximal interval of time (sample_duration) or a
    minimal number of events (sample_duration).
    The calculator is supposed to be called in an event loop, extract and collect the
    event charge and fill the PedestalContainer

    Parameters
    ----------
    tel_id : int
          id of the telescope (default 0)
    sample_duration : int
         interval of time (s) used to gather the pedestal statistics
    sample_size : int
         number of pedestal events requested for the statistics
    n_channels : int
         number of waveform channel to be considered
    charge_product : str
        Name of the charge extractor to be used
    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.

    kwargs

    """

    tel_id = Int(
        0, help="id of the telescope to calculate the flat-field coefficients"
    ).tag(config=True)
    sample_duration = Int(60, help="sample duration in seconds").tag(config=True)
    sample_size = Int(10000, help="sample size").tag(config=True)
    n_channels = Int(2, help="number of channels to be treated").tag(config=True)
    charge_product = Unicode(
        "LocalPeakWindowSum", help="Name of the charge extractor to be used"
    ).tag(config=True)

    def __init__(self, subarray, **kwargs):

        """
        Parent class for the flat-field calculators.
        Fills the MonitoringCameraContainer.FlatfieldContainer on the base of a given
        flat-field event sample.
        The sample is defined by a maximal interval of time (sample_duration) or a
        minimal number of events (sample_duration).
        The calculator is supposed to be called in an event loop, extract and collect the
        event charge and fill the PedestalContainer

        Parameters
        ----------
        subarray: ctapipe.instrument.SubarrayDescription
            Description of the subarray
        tel_id : int
              id of the telescope (default 0)
        sample_duration : int
             interval of time (s) used to gather the pedestal statistics
        sample_size : int
             number of pedestal events requested for the statistics
        n_channels : int
             number of waveform channel to be considered
        charge_product : str
            Name of the charge extractor to be used
        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.

        kwargs

        """

        super().__init__(**kwargs)
        # load the waveform charge extractor
        self.extractor = ImageExtractor.from_name(
            self.charge_product, config=self.config, subarray=subarray
        )

        self.log.info(f"extractor {self.extractor}")

    @abstractmethod
    def calculate_relative_gain(self, event):
        """
コード例 #28
0
class SingleTelEventDisplay(Tool):
    name = "ctapipe-display-televents"
    description = Unicode(__doc__)

    infile = Path(help="input file to read", exists=True,
                  directory_ok=False).tag(config=True)
    tel = Int(help="Telescope ID to display", default_value=0).tag(config=True)
    write = Bool(help="Write out images to PNG files",
                 default_value=False).tag(config=True)
    clean = Bool(help="Apply image cleaning",
                 default_value=False).tag(config=True)
    hillas = Bool(help="Apply and display Hillas parametrization",
                  default_value=False).tag(config=True)
    samples = Bool(help="Show each sample",
                   default_value=False).tag(config=True)
    display = Bool(help="Display results in interactive window",
                   default_value=True).tag(config=True)
    delay = Float(help="delay between events in s",
                  default_value=0.01,
                  min=0.001).tag(config=True)
    progress = Bool(help="display progress bar",
                    default_value=True).tag(config=True)

    aliases = Dict({
        "infile": "SingleTelEventDisplay.infile",
        "tel": "SingleTelEventDisplay.tel",
        "max-events": "EventSource.max_events",
        "write": "SingleTelEventDisplay.write",
        "clean": "SingleTelEventDisplay.clean",
        "hillas": "SingleTelEventDisplay.hillas",
        "samples": "SingleTelEventDisplay.samples",
        "display": "SingleTelEventDisplay.display",
        "delay": "SingleTelEventDisplay.delay",
        "progress": "SingleTelEventDisplay.progress",
    })

    classes = List([EventSource, CameraCalibrator])

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def setup(self):
        print("TOLLES INFILE", self.infile)
        self.event_source = EventSource.from_url(self.infile, parent=self)
        self.event_source.allowed_tels = {self.tel}

        self.calibrator = CameraCalibrator(parent=self,
                                           subarray=self.event_source.subarray)
        self.log.info(f"SELECTING EVENTS FROM TELESCOPE {self.tel}")

    def start(self):

        disp = None

        for event in tqdm(
                self.event_source,
                desc=f"Tel{self.tel}",
                total=self.event_source.max_events,
                disable=~self.progress,
        ):

            self.log.debug(event.trigger)
            self.log.debug(f"Energy: {event.simulation.shower.energy}")

            self.calibrator(event)

            if disp is None:
                geom = self.event_source.subarray.tel[self.tel].camera.geometry
                self.log.info(geom)
                disp = CameraDisplay(geom)
                # disp.enable_pixel_picker()
                disp.add_colorbar()
                if self.display:
                    plt.show(block=False)

            # display the event
            disp.axes.set_title("CT{:03d} ({}), event {:06d}".format(
                self.tel, geom.camera_name, event.index.event_id))

            if self.samples:
                # display time-varying event
                data = event.dl0.tel[self.tel].waveform
                for ii in range(data.shape[1]):
                    disp.image = data[:, ii]
                    disp.set_limits_percent(70)
                    plt.suptitle(f"Sample {ii:03d}")
                    if self.display:
                        plt.pause(self.delay)
                    if self.write:
                        plt.savefig(
                            f"CT{self.tel:03d}_EV{event.index.event_id:10d}"
                            f"_S{ii:02d}.png")
            else:
                # display integrated event:
                im = event.dl1.tel[self.tel].image

                if self.clean:
                    mask = tailcuts_clean(geom,
                                          im,
                                          picture_thresh=10,
                                          boundary_thresh=7)
                    im[~mask] = 0.0

                disp.image = im

                if self.hillas:
                    try:
                        ellipses = disp.axes.findobj(Ellipse)
                        if len(ellipses) > 0:
                            ellipses[0].remove()

                        params = hillas_parameters(geom, image=im)
                        disp.overlay_moments(params,
                                             color="pink",
                                             lw=3,
                                             with_label=False)
                    except HillasParameterizationError:
                        pass

                if self.display:
                    plt.pause(self.delay)
                if self.write:
                    plt.savefig(
                        f"CT{self.tel:03d}_EV{event.index.event_id:010d}.png")

        self.log.info("FINISHED READING DATA FILE")

        if disp is None:
            self.log.warning(
                "No events for tel {} were found in {}. Try a "
                "different EventIO file or another telescope".format(
                    self.tel, self.infile))
コード例 #29
0
class LSTEventSource(EventSource):
    """EventSource for LST r0 data."""

    n_gains = Int(
        2,
        help='Number of gains at r0/r1 level'
    ).tag(config=True)

    baseline = Int(
        400,
        help='r0 waveform baseline (default from EvB v3)'
    ).tag(config=True)

    multi_streams = Bool(
        True,
        help='Read in parallel all streams '
    ).tag(config=True)

    def __init__(self, **kwargs):
        """
        Constructor
        Parameters
        ----------
        n_gains = number of gains expected in input file

        baseline = baseline to be subtracted at r1 level (not used for the moment)

        multi_streams = enable the reading of input files from all streams

        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.\
        kwargs: dict
            Additional parameters to be passed.
            NOTE: The file mask of the data to read can be passed with
            the 'input_url' parameter.
        """

        super().__init__(**kwargs)

        if self.multi_streams:
            # test how many streams are there:
            # file name must be [stream name]Run[all the rest]
            # All the files with the same [all the rest] are opened

            path, name = os.path.split(os.path.abspath(self.input_url))
            if 'Run' in name:
                stream, run = name.split('Run', 1)
            else:
                run = name

            ls = listdir(path)
            self.file_list = []

            for file_name in ls:
                if run in file_name:
                    full_name = os.path.join(path, file_name)
                    self.file_list.append(full_name)

        else:
            self.file_list = [self.input_url]

        self.multi_file = MultiFiles(self.file_list)
        self.geometry_version = 4

        self.camera_config = self.multi_file.camera_config
        self.log.info(
            "Read {} input files".format(
                self.multi_file.num_inputs()
            )
        )
        self.tel_id = self.camera_config.telescope_id
        self._subarray = self.create_subarray(self.tel_id)
        self.n_camera_pixels = self.subarray.tel[self.tel_id].camera.n_pixels

    @property
    def subarray(self):
        return self._subarray

    @property
    def is_simulation(self):
        return False

    @property
    def obs_id(self):
        # currently no obs id is available from the input files
        return self.camera_config.configuration_id

    @property
    def datalevels(self):
        return (DataLevel.R0, )

    def rewind(self):
        self.multi_file.rewind()

    def create_subarray(self, tel_id=1):
        """
        Obtain the subarray from the EventSource
        Returns
        -------
        ctapipe.instrument.SubarrayDecription
        """

        # camera info from LSTCam-[geometry_version].camgeom.fits.gz file
        camera = load_camera_geometry(version=self.geometry_version)

        tel_descr = TelescopeDescription(
            name='LST', tel_type='LST', optics=OPTICS, camera=camera
        )

        tels = {tel_id: tel_descr}

        # LSTs telescope position taken from MC from the moment
        tel_pos = {tel_id: [50., 50., 16] * u.m}

        subarray = SubarrayDescription("LST1 subarray")
        subarray.tels = tels
        subarray.positions = tel_pos

        return subarray

    def _generator(self):

        # container for LST data
        self.data = LSTDataContainer()
        self.data.meta['input_url'] = self.input_url
        self.data.meta['max_events'] = self.max_events
        self.data.meta['origin'] = 'LSTCAM'

        # fill LST data from the CameraConfig table
        self.fill_lst_service_container_from_zfile()

        # initialize general monitoring container
        self.initialize_mon_container()

        # loop on events
        for count, event in enumerate(self.multi_file):

            self.data.count = count
            self.data.index.event_id = event.event_id
            self.data.index.obs_id = self.obs_id

            # fill specific LST event data
            self.fill_lst_event_container_from_zfile(event)

            # fill general monitoring data
            self.fill_mon_container_from_zfile(event)

            # fill general R0 data
            self.fill_r0_container_from_zfile(event)

            yield self.data

    @staticmethod
    def is_compatible(file_path):
        from astropy.io import fits
        try:
            # The file contains two tables:
            #  1: CameraConfig
            #  2: Events
            h = fits.open(file_path)[2].header
            ttypes = [
                h[x] for x in h.keys() if 'TTYPE' in x
            ]
        except OSError:
            # not even a fits file
            return False

        except IndexError:
            # A fits file of a different format
            return False

        is_protobuf_zfits_file = (
            (h['XTENSION'] == 'BINTABLE') and
            (h['EXTNAME'] == 'Events') and
            (h['ZTABLE'] is True) and
            (h['ORIGIN'] == 'CTA') and
            (h['PBFHEAD'] == 'R1.CameraEvent')
        )

        is_lst_file = 'lstcam_counters' in ttypes
        return is_protobuf_zfits_file & is_lst_file

    def fill_lst_service_container_from_zfile(self):
        """
        Fill LSTServiceContainer with specific LST service data data
        (from the CameraConfig table of zfit file)

        """

        self.data.lst.tels_with_data = [self.tel_id, ]
        svc_container = self.data.lst.tel[self.tel_id].svc

        svc_container.telescope_id = self.tel_id
        svc_container.cs_serial = self.camera_config.cs_serial
        svc_container.configuration_id = self.camera_config.configuration_id
        svc_container.date = self.camera_config.date
        svc_container.num_pixels = self.camera_config.num_pixels
        svc_container.num_samples = self.camera_config.num_samples
        svc_container.pixel_ids = self.camera_config.expected_pixels_id
        svc_container.data_model_version = self.camera_config.data_model_version
        svc_container.num_modules = self.camera_config.lstcam.num_modules
        svc_container.module_ids = self.camera_config.lstcam.expected_modules_id
        svc_container.idaq_version = self.camera_config.lstcam.idaq_version
        svc_container.cdhs_version = self.camera_config.lstcam.cdhs_version
        svc_container.algorithms = self.camera_config.lstcam.algorithms
        svc_container.pre_proc_algorithms = self.camera_config.lstcam.pre_proc_algorithms

    def fill_lst_event_container_from_zfile(self, event):
        """
        Fill LSTEventContainer with specific LST service data
        (from the Event table of zfit file)

        """

        event_container = self.data.lst.tel[self.tel_id].evt

        event_container.configuration_id = event.configuration_id
        event_container.event_id = event.event_id
        event_container.tel_event_id = event.tel_event_id
        event_container.pixel_status = event.pixel_status
        event_container.ped_id = event.ped_id
        event_container.module_status = event.lstcam.module_status
        event_container.extdevices_presence = event.lstcam.extdevices_presence

        # if TIB data are there
        if event_container.extdevices_presence & 1:
            # unpack TIB data
            rec_fmt = '=IHIBB'
            unpacked_tib = struct.unpack(rec_fmt, event.lstcam.tib_data)
            event_container.tib_event_counter = unpacked_tib[0]
            event_container.tib_pps_counter = unpacked_tib[1]
            event_container.tib_tenMHz_counter = unpacked_tib[2]
            event_container.tib_stereo_pattern = unpacked_tib[3]
            event_container.tib_masked_trigger = unpacked_tib[4]

        # if UCTS data are there
        if event_container.extdevices_presence & 2:

            if int(self.data.lst.tel[self.tel_id].svc.idaq_version) > 37201:

                # unpack UCTS-CDTS data (new version)
                rec_fmt = '=QIIIIIBBBBI'
                unpacked_cdts = struct.unpack(rec_fmt, event.lstcam.cdts_data)
                event_container.ucts_timestamp = unpacked_cdts[0]
                event_container.ucts_address = unpacked_cdts[1]        # new
                event_container.ucts_event_counter = unpacked_cdts[2]
                event_container.ucts_busy_counter = unpacked_cdts[3]   # new
                event_container.ucts_pps_counter = unpacked_cdts[4]
                event_container.ucts_clock_counter = unpacked_cdts[5]
                event_container.ucts_trigger_type = unpacked_cdts[6]
                event_container.ucts_white_rabbit_status = unpacked_cdts[7]
                event_container.ucts_stereo_pattern = unpacked_cdts[8] # new
                event_container.ucts_num_in_bunch = unpacked_cdts[9]   # new
                event_container.ucts_cdts_version = unpacked_cdts[10]  # new

            else:
                # unpack UCTS-CDTS data (old version)
                rec_fmt = '=IIIQQBBB'
                unpacked_cdts =  struct.unpack(rec_fmt, event.lstcam.cdts_data)
                event_container.ucts_event_counter = unpacked_cdts[0]
                event_container.ucts_pps_counter = unpacked_cdts[1]
                event_container.ucts_clock_counter = unpacked_cdts[2]
                event_container.ucts_timestamp = unpacked_cdts[3]
                event_container.ucts_camera_timestamp = unpacked_cdts[4]
                event_container.ucts_trigger_type = unpacked_cdts[5]
                event_container.ucts_white_rabbit_status = unpacked_cdts[6]

        # if SWAT data are there
        if event_container.extdevices_presence & 4:
            # unpack SWAT data
            rec_fmt = '=QIIBBIBI'
            unpacked_swat = struct.unpack(rec_fmt, event.lstcam.swat_data)
            event_container.swat_timestamp = unpacked_swat[0]
            event_container.swat_counter1 = unpacked_swat[1]
            event_container.swat_counter2 = unpacked_swat[2]
            event_container.swat_event_type = unpacked_swat[3]
            event_container.swat_camera_flag = unpacked_swat[4]
            event_container.swat_camera_event_num = unpacked_swat[5]
            event_container.swat_array_flag = unpacked_swat[6]
            event_container.swat_array_event_num = unpacked_swat[7]

        # unpack Dragon counters
        rec_fmt = '=HIIIQ'
        rec_len = struct.calcsize(rec_fmt)
        rec_unpack = struct.Struct(rec_fmt).unpack_from

        event_container.pps_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.tenMHz_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.event_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.trigger_counter = np.zeros(self.camera_config.lstcam.num_modules)
        event_container.local_clock_counter = np.zeros(self.camera_config.lstcam.num_modules)
        for mod in range(self.camera_config.lstcam.num_modules):

            words=event.lstcam.counters[mod*rec_len:(mod+1)*rec_len]
            unpacked_counter = rec_unpack(words)
            event_container.pps_counter[mod] = unpacked_counter[0]
            event_container.tenMHz_counter[mod] = unpacked_counter[1]
            event_container.event_counter[mod] = unpacked_counter[2]
            event_container.trigger_counter[mod] = unpacked_counter[3]
            event_container.local_clock_counter[mod] = unpacked_counter[4]

        event_container.chips_flags = event.lstcam.chips_flags
        event_container.first_capacitor_id = event.lstcam.first_capacitor_id
        event_container.drs_tag_status = event.lstcam.drs_tag_status
        event_container.drs_tag = event.lstcam.drs_tag

    def fill_r0_camera_container_from_zfile(self, r0_container, event):
        """
        Fill with R0CameraContainer
        """

        # look for correct trigger_time (TAI time in s), first in UCTS and then in TIB
        #if self.data.lst.tel[self.tel_id].evt.ucts_timestamp > 0:
        #    r0_container.trigger_time = self.data.lst.tel[self.tel_id].evt.ucts_timestamp/1e9

        # consider for the moment only TIB time since UCTS seems not correct
        #if self.data.lst.tel[self.tel_id].evt.tib_pps_counter > 0:
        #    r0_container.trigger_time = (
        #        self.data.lst.tel[self.tel_id].svc.date +
        #        self.data.lst.tel[self.tel_id].evt.tib_pps_counter +
        #        self.data.lst.tel[self.tel_id].evt.tib_tenMHz_counter * 10**(-7))
        #else:
        #    r0_container.trigger_time = 0

        #consider for the moment trigger time from central dragon module
        module_rank = np.where(self.data.lst.tel[self.tel_id].svc.module_ids == 132)
        r0_container.trigger_time = (
                    self.data.lst.tel[self.tel_id].svc.date +
                    self.data.lst.tel[self.tel_id].evt.pps_counter[module_rank] +
                    self.data.lst.tel[self.tel_id].evt.tenMHz_counter[module_rank] * 10**(-7))

        # look for correct trigger type first in UCTS and then in TIB
        #if self.data.lst.tel[self.tel_id].evt.ucts_trigger_type > 0:
        #    r0_container.trigger_type = self.data.lst.tel[self.tel_id].evt.ucts_trigger_type

        # consider for the moment only TIB trigger since UCTS seems not correct
        if self.data.lst.tel[self.tel_id].evt.tib_masked_trigger > 0:
            r0_container.trigger_type = self.data.lst.tel[self.tel_id].evt.tib_masked_trigger
        else:
            r0_container.trigger_type = -1

        # verify the number of gains
        if event.waveform.shape[0] != self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains:
            raise ValueError(f"Number of gains not correct, waveform shape is {event.waveform.shape[0]}"
                             f" instead of "
                             f"{self.camera_config.num_pixels * self.camera_config.num_samples * self.n_gains}")

        reshaped_waveform = np.array(
            event.waveform
        ).reshape(
            self.n_gains,
            self.camera_config.num_pixels,
            self.camera_config.num_samples
        )

        # initialize the waveform container to zero
        r0_container.waveform = np.zeros([self.n_gains, self.n_camera_pixels,
                                          self.camera_config.num_samples])

        # re-order the waveform following the expected_pixels_id values
        # (rank = pixel id)
        r0_container.waveform[:, self.camera_config.expected_pixels_id, :] =\
            reshaped_waveform

    def fill_r0_container_from_zfile(self, event):
        """
        Fill with R0Container

        """
        container = self.data.r0

        container.tels_with_data = [self.tel_id, ]
        r0_camera_container = container.tel[self.tel_id]
        self.fill_r0_camera_container_from_zfile(
            r0_camera_container,
            event
        )

    def initialize_mon_container(self):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """
        container = self.data.mon
        container.tels_with_data = [self.tel_id, ]
        mon_camera_container = container.tel[self.tel_id]

        # initialize the container
        status_container = PixelStatusContainer()
        status_container.hardware_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.pedestal_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)
        status_container.flatfield_failing_pixels = np.zeros((self.n_gains, self.n_camera_pixels), dtype=bool)

        mon_camera_container.pixel_status = status_container

    def fill_mon_container_from_zfile(self, event):
        """
        Fill with MonitoringContainer.
        For the moment, initialize only the PixelStatusContainer

        """

        status_container = self.data.mon.tel[self.tel_id].pixel_status

        # reorder the array
        pixel_status = np.zeros(self.n_camera_pixels)
        pixel_status[self.camera_config.expected_pixels_id] = event.pixel_status
        status_container.hardware_failing_pixels[:] = pixel_status == 0