Example #1
0
class DisplayDL1Calib(Tool):
    name = "ctapipe-display-dl1"
    description = __doc__

    telescope = Int(
        None,
        allow_none=True,
        help="Telescope to view. Set to None to display all telescopes.",
    ).tag(config=True)

    aliases = Dict(
        dict(
            input="EventSource.input_url",
            max_events="EventSource.max_events",
            T="DisplayDL1Calib.telescope",
            O="ImagePlotter.output_path",
        ))
    flags = Dict(
        dict(D=(
            {
                "ImagePlotter": {
                    "display": True
                }
            },
            "Display the photo-electron images on-screen as they are produced.",
        )))
    classes = List([EventSource, ImagePlotter] +
                   traits.classes_with_traits(ImageExtractor))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config.EventSource.input_url = get_dataset_path(
            "gamma_test_large.simtel.gz")
        self.eventsource = None
        self.calibrator = None
        self.plotter = None

    def setup(self):
        self.eventsource = EventSource.from_config(parent=self)
        subarray = self.eventsource.subarray

        self.calibrator = CameraCalibrator(parent=self, subarray=subarray)
        self.plotter = ImagePlotter(parent=self, subarray=subarray)

    def start(self):
        for event in self.eventsource:
            self.calibrator(event)

            tel_list = event.r0.tel.keys()

            if self.telescope:
                if self.telescope not in tel_list:
                    continue
                tel_list = [self.telescope]

            for telid in tel_list:
                self.plotter.plot(event, telid)

    def finish(self):
        self.plotter.finish()
Example #2
0
def test_classes_with_traits():
    from ctapipe.core import Tool

    class CompA(Component):
        a = Int().tag(config=True)

    class CompB(Component):
        classes = List([CompA])
        b = Int().tag(config=True)

    class CompC(Component):
        c = Int().tag(config=True)

    class MyTool(Tool):
        classes = [CompB, CompC]

    with_traits = classes_with_traits(MyTool)
    assert len(with_traits) == 4
    assert MyTool in with_traits
    assert CompA in with_traits
    assert CompB in with_traits
    assert CompC in with_traits
class CalibrationHDF5Writer(Tool):
    """
     Tool that generates a HDF5 file with camera calibration coefficients.
     This is just an example on how the monitoring containers can be filled using
     the calibration Components in calib/camera.
     This example is based on an input file with pedestal and flat-field events

     For getting help run:
     python calc_camera_calibration.py --help

     """

    name = "CalibrationHDF5Writer"
    description = "Generate a HDF5 file with camera calibration coefficients"

    one_event = Bool(
        False, help='Stop after first calibration event').tag(config=True)

    output_file = Unicode('calibration.hdf5',
                          help='Name of the output file').tag(config=True)

    calibration_product = traits.create_class_enum_trait(
        CalibrationCalculator, default_value='LSTCalibrationCalculator')

    events_to_skip = Int(
        1000,
        help='Number of first events to skip due to bad DRS4 pedestal correction'
    ).tag(config=True)

    mc_min_flatfield_adc = Float(
        2000,
        help=
        'Minimum high-gain camera median charge per pixel (ADC) for flatfield MC events'
    ).tag(config=True)

    mc_max_pedestal_adc = Float(
        300,
        help=
        'Maximum high-gain camera median charge per pixel (ADC) for pedestal MC events'
    ).tag(config=True)

    aliases = {
        ("i", "input_file"): 'EventSource.input_url',
        ("m", "max_events"): 'EventSource.max_events',
        ("o", "output_file"): 'CalibrationHDF5Writer.output_file',
        ("p", "pedestal_file"):
        "LSTEventSource.LSTR0Corrections.drs4_pedestal_path",
        ("s", "systematics_file"):
        "LSTCalibrationCalculator.systematic_correction_path",
        ("r", "run_summary_file"):
        "LSTEventSource.EventTimeCalculator.run_summary_path",
        ("t", "time_calibration_file"):
        "LSTEventSource.LSTR0Corrections.drs4_time_calibration_path",
        "events_to_skip": 'CalibrationHDF5Writer.events_to_skip',
    }

    flags = {
        **traits.flag(
            "flatfield-heuristic",
            "LSTEventSource.use_flatfield_heuristic",
            "Use flatfield heuristic",
            "Do not use flatfield heuristic",
        )
    }

    classes = ([EventSource, CalibrationCalculator] +
               traits.classes_with_traits(CalibrationCalculator) +
               traits.classes_with_traits(EventSource))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        """
        Tool that generates a HDF5 file with camera calibration coefficients.
        Input file must contain interleaved pedestal and flat-field events

        For getting help run:
            lstchain_create_calibration_file --help
        """
        self.eventsource = None
        self.processor = None
        self.writer = None
        self.tot_events = 0
        self.simulation = False

    def setup(self):

        self.log.debug("Opening file")
        self.eventsource = EventSource.from_config(parent=self)

        self.processor = CalibrationCalculator.from_name(
            self.calibration_product,
            parent=self,
            subarray=self.eventsource.subarray)

        tel_id = self.processor.tel_id

        # if real data
        if isinstance(self.eventsource, LSTEventSource):
            if tel_id != self.eventsource.lst_service.telescope_id:
                raise ValueError(
                    f"Events telescope_id {self.eventsource.lst_service.telescope_id} "
                    f"different than CalibrationCalculator telescope_id {tel_id}"
                )

            if self.eventsource.r0_r1_calibrator.drs4_pedestal_path.tel[
                    tel_id] is None:
                raise IOError(
                    "Missing (mandatory) drs4 pedestal file in trailets")

            # remember how many events in the files
            self.tot_events = len(self.eventsource.multi_file)
            self.log.debug(f"Input file has file {self.tot_events} events")
        else:
            self.tot_events = self.eventsource.max_events
            self.simulation = True

        group_name = 'tel_' + str(tel_id)

        self.log.debug(f"Open output file {self.output_file}")

        self.writer = HDF5TableWriter(filename=self.output_file,
                                      group_name=group_name,
                                      overwrite=True)

    def start(self):
        '''Calibration coefficient calculator'''

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

        tel_id = self.processor.tel_id
        new_ped = False
        new_ff = False
        end_of_file = False

        try:
            self.log.debug("Start loop")
            self.log.debug(
                f"If not simulation, skip first {self.events_to_skip} events")
            for count, event in enumerate(self.eventsource):

                # if simulation use not calibrated and not gain selected R0 waveform
                if self.simulation:
                    # estimate offset of each channel from the camera median pedestal value
                    offset = np.median(
                        event.mon.tel[tel_id].calibration.pedestal_per_sample,
                        axis=1).round()
                    event.r1.tel[tel_id].waveform = event.r0.tel[
                        tel_id].waveform.astype(
                            np.float32) - offset[:, np.newaxis, np.newaxis]

                if count % 1000 == 0 and count > self.events_to_skip:
                    self.log.debug(f"Event {count}")

                # if last event write results
                max_events_reached = (self.eventsource.max_events is not None
                                      and count
                                      == self.eventsource.max_events - 1)
                if count == self.tot_events - 1 or max_events_reached:
                    self.log.debug(f"Last event, count = {count}")
                    end_of_file = True

                # save the config, to be retrieved as data.meta['config']
                if count == 0:

                    if self.simulation:
                        initialize_pixel_status(
                            event.mon.tel[tel_id],
                            event.r1.tel[tel_id].waveform.shape)

                    ped_data = event.mon.tel[tel_id].pedestal
                    add_config_metadata(ped_data, self.config)
                    add_global_metadata(ped_data, metadata)

                    ff_data = event.mon.tel[tel_id].flatfield
                    add_config_metadata(ff_data, self.config)
                    add_global_metadata(ff_data, metadata)

                    status_data = event.mon.tel[tel_id].pixel_status
                    add_config_metadata(status_data, self.config)
                    add_global_metadata(status_data, metadata)

                    calib_data = event.mon.tel[tel_id].calibration
                    add_config_metadata(calib_data, self.config)
                    add_global_metadata(calib_data, metadata)

                # skip first events which are badly drs4 corrected
                if not self.simulation and count < self.events_to_skip:
                    continue

                # if pedestal event
                # use a cut on the charge for MC events if trigger not defined
                if event.trigger.event_type == EventType.SKY_PEDESTAL or (
                        self.simulation and np.median(
                            np.sum(event.r1.tel[tel_id].waveform[0],
                                   axis=1)) < self.mc_max_pedestal_adc):

                    new_ped = self.processor.pedestal.calculate_pedestals(
                        event)

                # if flat-field event
                # use a cut on the charge for MC events if trigger not defined
                elif event.trigger.event_type == EventType.FLATFIELD or (
                        self.simulation and np.median(
                            np.sum(event.r1.tel[tel_id].waveform[0],
                                   axis=1)) > self.mc_min_flatfield_adc):

                    new_ff = self.processor.flatfield.calculate_relative_gain(
                        event)

                # write pedestal results when enough statistics or end of file
                if new_ped or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.processor.pedestal.store_results(event)

                    # write the event
                    self.log.debug(
                        f"Write pedestal data at event n. {count+1}, id {event.index.event_id} "
                        f"stat = {ped_data.n_events} events")

                    # write on file
                    self.writer.write('pedestal', ped_data)

                    new_ped = False

                # write flatfield results when enough statistics (also for pedestals) or end of file
                if (new_ff and ped_data.n_events > 0) or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.processor.flatfield.store_results(event)

                    self.log.debug(
                        f"Write flatfield data at event n. {count+1}, id {event.index.event_id} "
                        f"stat = {ff_data.n_events} events")

                    # write on file
                    self.writer.write('flatfield', ff_data)

                    new_ff = False

                    # Then, calculate calibration coefficients
                    self.processor.calculate_calibration_coefficients(event)

                    # write calib and pixel status
                    self.log.debug("Write pixel_status data")
                    self.writer.write('pixel_status', status_data)

                    self.log.debug("Write calibration data")
                    self.writer.write('calibration', calib_data)
                    if self.one_event:
                        break

        except ValueError as e:
            self.log.error(e)

    def finish(self):
        Provenance().add_output_file(self.output_file,
                                     role='mon.tel.calibration')
        self.writer.close()
class CalibrationCalculator(Component):
    """
    Parent class for the camera calibration calculators.
    Fills the MonitoringCameraContainer on the base of calibration events

    Parameters
    ----------

    flatfield_calculator: lstchain.calib.camera.flatfield
         The flatfield to use. If None, then FlatFieldCalculator
            will be used by default.

    pedestal_calculator: lstchain.calib.camera.pedestal
         The pedestal to use. If None, then
           PedestalCalculator will be used by default.

    kwargs

    """
    squared_excess_noise_factor = Float(
        1.222,
        help='Excess noise factor squared: 1+ Var(gain)/Mean(Gain)**2').tag(
            config=True)

    pedestal_product = traits.create_class_enum_trait(
        PedestalCalculator, default_value='PedestalIntegrator')

    flatfield_product = traits.create_class_enum_trait(
        FlatFieldCalculator, default_value='FlasherFlatFieldCalculator')

    classes = List([FlatFieldCalculator, PedestalCalculator] +
                   traits.classes_with_traits(FlatFieldCalculator) +
                   traits.classes_with_traits(PedestalCalculator))

    def __init__(self, subarray, parent=None, config=None, **kwargs):
        """
        Parent class for the camera calibration calculators.
        Fills the MonitoringCameraContainer on the base of calibration events

        Parameters
        ----------

        flatfield_calculator: lstchain.calib.camera.flatfield
             The flatfield to use. If None, then FlatFieldCalculator
                will be used by default.

        pedestal_calculator: lstchain.calib.camera.pedestal
             The pedestal to use. If None, then
               PedestalCalculator will be used by default.

        """

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

        self.flatfield = FlatFieldCalculator.from_name(self.flatfield_product,
                                                       parent=self,
                                                       subarray=subarray)
        self.pedestal = PedestalCalculator.from_name(self.pedestal_product,
                                                     parent=self,
                                                     subarray=subarray)

        msg = "tel_id not the same for all calibration components"
        if self.pedestal.tel_id != self.flatfield.tel_id:
            raise ValueError(msg)

        self.tel_id = self.flatfield.tel_id

        self.log.debug(f"{self.pedestal}")
        self.log.debug(f"{self.flatfield}")
Example #5
0
class BokehFileViewer(Tool):
    name = "BokehFileViewer"
    description = ("Interactively explore an event file using the bokeh "
                   "visualisation package")

    port = Int(5006, help="Port to open bokeh server onto").tag(config=True)
    disable_server = Bool(False,
                          help="Do not start the bokeh server "
                          "(useful for testing)").tag(config=True)

    default_url = get_dataset_path("gamma_test_large.simtel.gz")
    EventSource.input_url.default_value = default_url

    extractor_product = traits.enum_trait(ImageExtractor,
                                          default='NeighborPeakWindowSum')

    aliases = Dict(
        dict(
            port='BokehFileViewer.port',
            disable_server='BokehFileViewer.disable_server',
            f='EventSource.input_url',
            max_events='EventSource.max_events',
            extractor='BokehFileViewer.extractor_product',
        ))

    classes = List([
        EventSource,
    ] + traits.classes_with_traits(ImageExtractor))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._event = None
        self._event_index = None
        self._event_id = None
        self._telid = None
        self._channel = None

        self.w_next_event = None
        self.w_previous_event = None
        self.w_event_index = None
        self.w_event_id = None
        self.w_goto_event_index = None
        self.w_goto_event_id = None
        self.w_telid = None
        self.w_channel = None
        self.w_dl1_dict = None
        self.wb_extractor = None
        self.layout = None

        self.reader = None
        self.seeker = None
        self.extractor = None
        self.calibrator = None
        self.viewer = None

        self._updating_dl1 = False
        # make sure, gzip files are seekable
        self.config.SimTelEventSource.back_seekable = True

    def setup(self):
        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"

        self.reader = EventSource.from_config(parent=self)
        self.seeker = EventSeeker(self.reader, parent=self)

        self.extractor = ImageExtractor.from_name(
            self.extractor_product,
            parent=self,
            subarray=self.reader.subarray,
        )
        self.calibrator = CameraCalibrator(
            subarray=self.reader.subarray,
            parent=self,
            image_extractor=self.extractor,
        )

        self.viewer = BokehEventViewer(parent=self)

        # Setup widgets
        self.viewer.create()
        self.viewer.enable_automatic_index_increment()
        self.create_previous_event_widget()
        self.create_next_event_widget()
        self.create_event_index_widget()
        self.create_goto_event_index_widget()
        self.create_event_id_widget()
        self.create_goto_event_id_widget()
        self.create_telid_widget()
        self.create_channel_widget()
        self.create_dl1_widgets()
        self.update_dl1_widget_values()

        # Setup layout
        self.layout = layout([[self.viewer.layout],
                              [
                                  self.w_previous_event, self.w_next_event,
                                  self.w_goto_event_index, self.w_goto_event_id
                              ], [self.w_event_index, self.w_event_id],
                              [self.w_telid, self.w_channel],
                              [self.wb_extractor]])

    def start(self):
        self.event_index = 0

    def finish(self):
        if not self.disable_server:

            def modify_doc(doc):
                doc.add_root(self.layout)
                doc.title = self.name

                directory = os.path.abspath(os.path.dirname(__file__))
                theme_path = os.path.join(directory, "theme.yaml")
                template_path = os.path.join(directory, "templates")
                doc.theme = Theme(filename=theme_path)
                env = jinja2.Environment(
                    loader=jinja2.FileSystemLoader(template_path))
                doc.template = env.get_template('index.html')

            self.log.info('Opening Bokeh application on '
                          'http://localhost:{}/'.format(self.port))
            server = Server({'/': modify_doc}, num_procs=1, port=self.port)
            server.start()
            server.io_loop.add_callback(server.show, "/")
            server.io_loop.start()

    @property
    def event_index(self):
        return self._event_index

    @event_index.setter
    def event_index(self, val):
        try:
            self.event = self.seeker[val]
        except IndexError:
            self.log.warning(f"Event Index {val} does not exist")

    @property
    def event_id(self):
        return self._event_id

    @event_id.setter
    def event_id(self, val):
        try:
            self.event = self.seeker[str(val)]
        except IndexError:
            self.log.warning(f"Event ID {val} does not exist")

    @property
    def telid(self):
        return self._telid

    @telid.setter
    def telid(self, val):
        self.channel = 0
        tels = list(self.event.r0.tels_with_data)
        if val not in tels:
            val = tels[0]
        self._telid = val
        self.viewer.telid = val
        self.update_telid_widget()

    @property
    def channel(self):
        return self._channel

    @channel.setter
    def channel(self, val):
        self._channel = val
        self.viewer.channel = val
        self.update_channel_widget()

    @property
    def event(self):
        return self._event

    @event.setter
    def event(self, val):
        self.calibrator(val)

        self._event = val

        self.viewer.event = val

        self._event_index = val.count
        self._event_id = val.r0.event_id
        self.update_event_index_widget()
        self.update_event_id_widget()

        self._telid = self.viewer.telid
        self.update_telid_widget()

        self._channel = self.viewer.channel
        self.update_channel_widget()

    def update_dl1_calibrator(self, extractor=None):
        """
        Recreate the dl1 calibrator with the specified extractor and cleaner

        Parameters
        ----------
        extractor : ctapipe.image.extractor.ImageExtractor
        """
        if extractor is None:
            extractor = self.calibrator.image_extractor

        self.extractor = extractor

        self.calibrator = CameraCalibrator(
            subarray=self.reader.subarray,
            parent=self,
            image_extractor=self.extractor,
        )
        self.viewer.refresh()

    def create_next_event_widget(self):
        self.w_next_event = Button(label=">", button_type="default", width=50)
        self.w_next_event.on_click(self.on_next_event_widget_click)

    def on_next_event_widget_click(self):
        self.event_index += 1

    def create_previous_event_widget(self):
        self.w_previous_event = Button(label="<",
                                       button_type="default",
                                       width=50)
        self.w_previous_event.on_click(self.on_previous_event_widget_click)

    def on_previous_event_widget_click(self):
        self.event_index -= 1

    def create_event_index_widget(self):
        self.w_event_index = TextInput(title="Event Index:", value='')

    def update_event_index_widget(self):
        if self.w_event_index:
            self.w_event_index.value = str(self.event_index)

    def create_event_id_widget(self):
        self.w_event_id = TextInput(title="Event ID:", value='')

    def update_event_id_widget(self):
        if self.w_event_id:
            self.w_event_id.value = str(self.event_id)

    def create_goto_event_index_widget(self):
        self.w_goto_event_index = Button(label="GOTO Index",
                                         button_type="default",
                                         width=100)
        self.w_goto_event_index.on_click(self.on_goto_event_index_widget_click)

    def on_goto_event_index_widget_click(self):
        self.event_index = int(self.w_event_index.value)

    def create_goto_event_id_widget(self):
        self.w_goto_event_id = Button(label="GOTO ID",
                                      button_type="default",
                                      width=70)
        self.w_goto_event_id.on_click(self.on_goto_event_id_widget_click)

    def on_goto_event_id_widget_click(self):
        self.event_id = int(self.w_event_id.value)

    def create_telid_widget(self):
        self.w_telid = Select(title="Telescope:", value="", options=[])
        self.w_telid.on_change('value', self.on_telid_widget_change)

    def update_telid_widget(self):
        if self.w_telid:
            tels = [str(t) for t in self.event.r0.tels_with_data]
            self.w_telid.options = tels
            self.w_telid.value = str(self.telid)

    def on_telid_widget_change(self, _, __, ___):
        if self.telid != int(self.w_telid.value):
            self.telid = int(self.w_telid.value)

    def create_channel_widget(self):
        self.w_channel = Select(title="Channel:", value="", options=[])
        self.w_channel.on_change('value', self.on_channel_widget_change)

    def update_channel_widget(self):
        if self.w_channel:
            try:
                n_chan = self.event.r0.tel[self.telid].waveform.shape[0]
            except AttributeError:
                n_chan = 1
            channels = [str(c) for c in range(n_chan)]
            self.w_channel.options = channels
            self.w_channel.value = str(self.channel)

    def on_channel_widget_change(self, _, __, ___):
        if self.channel != int(self.w_channel.value):
            self.channel = int(self.w_channel.value)

    def create_dl1_widgets(self):
        self.w_dl1_dict = dict(
            extractor=Select(title="Extractor:",
                             value='',
                             width=5,
                             options=BokehFileViewer.extractor_product.values),
            extractor_window_start=TextInput(title="Window Start:", value=''),
            extractor_window_width=TextInput(title="Window Width:", value=''),
            extractor_window_shift=TextInput(title="Window Shift:", value=''),
            extractor_lwt=TextInput(title="Local Pixel Weight:", value=''))

        for val in self.w_dl1_dict.values():
            val.on_change('value', self.on_dl1_widget_change)

        self.wb_extractor = widgetbox(
            PreText(text="Charge Extractor Configuration"),
            self.w_dl1_dict['extractor'],
            self.w_dl1_dict['extractor_window_start'],
            self.w_dl1_dict['extractor_window_width'],
            self.w_dl1_dict['extractor_window_shift'],
            self.w_dl1_dict['extractor_lwt'])

    def update_dl1_widget_values(self):
        if self.w_dl1_dict:
            for key, val in self.w_dl1_dict.items():
                if 'extractor' in key:
                    if key == 'extractor':
                        val.value = self.extractor.__class__.__name__
                    else:
                        key = key.replace("extractor_", "")
                        try:
                            val.value = str(getattr(self.extractor, key))
                        except AttributeError:
                            val.value = ''

    def on_dl1_widget_change(self, _, __, ___):
        if self.event:
            if not self._updating_dl1:
                self._updating_dl1 = True
                cmdline = []
                for key, val in self.w_dl1_dict.items():
                    k = key.replace("extractor_", "ImageExtractor.")
                    if val.value:
                        cmdline.append(f'--{k}={val.value}')
                self.parse_command_line(cmdline)
                extractor = ImageExtractor.from_name(self.extractor_product,
                                                     parent=self)
                self.update_dl1_calibrator(extractor)
                self.update_dl1_widget_values()
                self._updating_dl1 = False
Example #6
0
class CalibrationCalculator(Component):
    """
    Parent class for the camera calibration calculators.
    Fills the MonitoringCameraContainer on the base of calibration events

    Parameters
    ----------

    flatfield_calculator: lstchain.calib.camera.flatfield
         The flatfield to use. If None, then FlatFieldCalculator
            will be used by default.

    pedestal_calculator: lstchain.calib.camera.pedestal
         The pedestal to use. If None, then
           PedestalCalculator will be used by default.

    kwargs

    """

    systematic_correction_path = Path(
        default_value=None,
        allow_none=True,
        exists=True,
        directory_ok=False,
        help='Path to systematic correction file ',
    ).tag(config=True)

    squared_excess_noise_factor = Float(
        1.222,
        help='Excess noise factor squared: 1+ Var(gain)/Mean(Gain)**2').tag(
            config=True)

    pedestal_product = traits.create_class_enum_trait(
        PedestalCalculator, default_value='PedestalIntegrator')

    flatfield_product = traits.create_class_enum_trait(
        FlatFieldCalculator, default_value='FlasherFlatFieldCalculator')

    classes = ([FlatFieldCalculator, PedestalCalculator] +
               traits.classes_with_traits(FlatFieldCalculator) +
               traits.classes_with_traits(PedestalCalculator))

    def __init__(self, subarray, parent=None, config=None, **kwargs):
        """
        Parent class for the camera calibration calculators.
        Fills the MonitoringCameraContainer on the base of calibration events

        Parameters
        ----------

        flatfield_calculator: lstchain.calib.camera.flatfield
             The flatfield to use. If None, then FlatFieldCalculator
                will be used by default.

        pedestal_calculator: lstchain.calib.camera.pedestal
             The pedestal to use. If None, then
               PedestalCalculator will be used by default.

        """

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

        if self.squared_excess_noise_factor <= 0:
            msg = "Argument squared_excess_noise_factor must have a positive value"
            raise ValueError(msg)

        self.flatfield = FlatFieldCalculator.from_name(self.flatfield_product,
                                                       parent=self,
                                                       subarray=subarray)
        self.pedestal = PedestalCalculator.from_name(self.pedestal_product,
                                                     parent=self,
                                                     subarray=subarray)

        msg = "tel_id not the same for all calibration components"
        if self.pedestal.tel_id != self.flatfield.tel_id:
            raise ValueError(msg)

        self.tel_id = self.flatfield.tel_id

        # load systematic correction term B
        self.quadratic_term = 0
        if self.systematic_correction_path is not None:
            try:
                with h5py.File(self.systematic_correction_path, 'r') as hf:
                    self.quadratic_term = np.array(hf['B_term'])

            except:
                raise IOError(
                    f"Problem in reading quadratic term file {self.systematic_correction_path}"
                )
        self.log.debug(f"{self.pedestal}")
        self.log.debug(f"{self.flatfield}")
Example #7
0
class PedestalHDF5Writer(Tool):
    '''
    Example of tool that extract the pedestal value per pixel and write the pedestal
    container to disk in a hdf5 file
    '''

    name = "PedestalHDF5Writer"
    description = "Generate a HDF5 file with pedestal values"

    output_file = Unicode(
        'pedestal.hdf5',
        help='Name of the output file'
    ).tag(config=True)

    calculator_product = traits.enum_trait(
        PedestalCalculator,
        default='PedestalIntegrator'
    )
    r0calibrator_product = traits.enum_trait(
        CameraR0Calibrator,
        default='NullR0Calibrator'
    )

    aliases = Dict(dict(
        input_file='EventSource.input_url',
        max_events='EventSource.max_events',
        tel_id='PedestalCalculator.tel_id',
        sample_duration='PedestalCalculator.sample_duration',
        sample_size='PedestalCalculator.sample_size',
        n_channels='PedestalCalculator.n_channels',
        charge_product = 'PedestalCalculator.charge_product'
    ))

    classes = List([EventSource,
                    PedestalCalculator,
                    PedestalContainer,
                    CameraR0Calibrator,
                    HDF5TableWriter
                    ] + traits.classes_with_traits(PedestalCalculator)
                      + traits.classes_with_traits(CameraR0Calibrator))

    def __init__(self, **kwargs):
        '''
        Example of tool that extract the pedestal value per pixel and write the pedestal
        container to disk
        '''

        super().__init__(**kwargs)
        self.eventsource = None
        self.pedestal = None
        self.container = None
        self.writer = None
        self.group_name = None
        self.r0calibrator = None

    def setup(self):

        kwargs = dict(parent=self)
        self.eventsource = EventSource.from_config(**kwargs)
        self.pedestal = PedestalCalculator.from_name(
            self.calculator_product,
            **kwargs
        )
        self.r0calibrator = CameraR0Calibrator.from_name(
            self.r0calibrator_product,
            **kwargs
        )
        self.group_name = 'tel_' + str(self.pedestal.tel_id)

        self.writer = HDF5TableWriter(
            filename=self.output_file, group_name=self.group_name, overwrite=True
        )

    def start(self):
        '''
        Example of tool that extract the pedestal value per pixel and write the pedestal
        container to disk
        '''


        write_config = True

        # loop on events
        for count, event in enumerate(self.eventsource):
            # select only pedestal events

            if event.r0.tel[self.pedestal.tel_id].trigger_type != 32:
                continue

            # perform R0->R1
            self.r0calibrator.calibrate(event)

            # fill pedestal monitoring container
            if self.pedestal.calculate_pedestals(event):

                ped_data = event.mon.tel[self.pedestal.tel_id].pedestal
                self.log.debug(f" r0 {event.r0.tel[0].waveform.shape}")
                self.log.debug(f" r1 {event.r1.tel[0].waveform.shape}")
                if write_config:
                    ped_data.meta['config']= self.config
                    write_config = False

                self.log.debug(f"write event in table: /{self.group_name}/pedestal")

                # write data to file
                self.writer.write('pedestal', ped_data)

    def finish(self):
        Provenance().add_output_file(
            self.output_file,
            role='mon.tel.pedestal'
        )
        self.writer.close()
class CalibrationHDF5Writer(Tool):
    """
     Tool that generates a HDF5 file with camera calibration coefficients.
     This is just an example on how the monitoring containers can be filled using
     the calibration Components in calib/camera.
     This example is based on an input file with interleaved pedestal and flat-field events

     For getting help run:
     python calc_camera_calibration.py --help

     """

    name = "CalibrationHDF5Writer"
    description = "Generate a HDF5 file with camera calibration coefficients"

    output_file = Unicode('calibration.hdf5',
                          help='Name of the output file').tag(config=True)

    minimum_charge = Float(
        800,
        help='Temporary cut on charge to eliminate events without led signal'
    ).tag(config=True)

    pedestal_product = traits.enum_trait(PedestalCalculator,
                                         default='PedestalIntegrator')

    flatfield_product = traits.enum_trait(FlatFieldCalculator,
                                          default='FlasherFlatFieldCalculator')

    aliases = Dict(
        dict(
            input_file='EventSource.input_url',
            max_events='EventSource.max_events',
            flatfield_product='CalibrationHDF5Writer.flatfield_product',
            pedestal_product='CalibrationHDF5Writer.pedestal_product',
        ))

    classes = List([
        EventSource, FlatFieldCalculator, FlatFieldContainer,
        PedestalCalculator, PedestalContainer, WaveformCalibrationContainer
    ] + traits.classes_with_traits(ImageExtractor) +
                   traits.classes_with_traits(FlatFieldCalculator) +
                   traits.classes_with_traits(PedestalCalculator))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        """
         Tool that generates a HDF5 file with camera calibration coefficients.
         Input file must contain interleaved pedestal and flat-field events  

         For getting help run:
         python calc_camera_calibration.py --help
         
        """
        self.eventsource = None
        self.flatfield = None
        self.pedestal = None
        self.container = None
        self.writer = None
        self.tel_id = None

    def setup(self):
        kwargs = dict(parent=self)
        self.eventsource = EventSource.from_config(**kwargs)

        self.flatfield = FlatFieldCalculator.from_name(self.flatfield_product,
                                                       **kwargs)
        self.pedestal = PedestalCalculator.from_name(self.pedestal_product,
                                                     **kwargs)

        msg = "tel_id not the same for all calibration components"
        assert self.pedestal.tel_id == self.flatfield.tel_id, msg

        self.tel_id = self.flatfield.tel_id

        group_name = 'tel_' + str(self.tel_id)

        self.writer = HDF5TableWriter(filename=self.output_file,
                                      group_name=group_name,
                                      overwrite=True)

    def start(self):
        '''Calibration coefficient calculator'''

        ped_initialized = False
        ff_initialized = False

        for count, event in enumerate(self.eventsource):

            # get link to monitoring containers
            if count == 0:
                ped_data = event.mon.tel[self.tel_id].pedestal
                ff_data = event.mon.tel[self.tel_id].flatfield
                status_data = event.mon.tel[self.tel_id].pixel_status
                calib_data = event.mon.tel[self.tel_id].calibration
                ids = event.nectarcam.tel[self.tel_id].svc.pixel_ids

            # if pedestal
            if event.r1.tel[self.tel_id].trigger_type == 32:
                if self.pedestal.calculate_pedestals(event):

                    self.log.debug(
                        f"new pedestal at event n. {count+1}, id {event.r0.event_id}"
                    )

                    # update pedestal mask
                    status_data.pedestal_failing_pixels = np.logical_or(
                        ped_data.charge_median_outliers,
                        ped_data.charge_std_outliers)

                    if not ped_initialized:
                        # save the config, to be retrieved as data.meta['config']
                        ped_data.meta['config'] = self.config
                        ped_initialized = True
                    else:
                        self.log.debug(f"write pedestal data")
                        # write only after a first event (used to initialize the mask)
                        self.writer.write('pedestal', ped_data)

            # consider flat field events only after first pedestal event
            elif (event.r1.tel[self.tel_id].trigger_type == 5
                  or event.r1.tel[self.tel_id].trigger_type
                  == 4) and (ped_initialized and np.median(
                      np.sum(event.r1.tel[self.tel_id].waveform[0, ids],
                             axis=1)) > self.minimum_charge):

                if self.flatfield.calculate_relative_gain(event):

                    self.log.debug(
                        f"new flatfield at event n. {count+1}, id {event.r0.event_id}"
                    )

                    # update the flatfield mask
                    status_data.flatfield_failing_pixels = np.logical_or(
                        ff_data.charge_median_outliers,
                        ff_data.time_median_outliers)

                    # mask from pedestal and flat-fleid data
                    monitoring_unusable_pixels = np.logical_or(
                        status_data.pedestal_failing_pixels,
                        status_data.flatfield_failing_pixels)

                    # calibration unusable pixels are an OR of all maskes
                    calib_data.unusable_pixels = np.logical_or(
                        monitoring_unusable_pixels,
                        status_data.hardware_failing_pixels)

                    # Extract calibration coefficients with F-factor method
                    # Assume fix F2 factor, F2=1+Var(gain)/Mean(Gain)**2 must be known from elsewhere
                    F2 = 1.1

                    # calculate photon-electrons
                    n_pe = F2 * (ff_data.charge_median - ped_data.charge_median
                                 )**2 / (ff_data.charge_std**2 -
                                         ped_data.charge_std**2)

                    # fill WaveformCalibrationContainer (this is an example)
                    calib_data.time = ff_data.sample_time
                    calib_data.time_range = ff_data.sample_time_range
                    calib_data.n_pe = n_pe
                    calib_data.dc_to_pe = n_pe / ff_data.charge_median
                    calib_data.time_correction = -ff_data.relative_time_median

                    ped_extractor_name = self.config.get(
                        "PedestalCalculator").get("charge_product")
                    ped_width = self.config.get(ped_extractor_name).get(
                        "window_width")
                    calib_data.pedestal_per_sample = ped_data.charge_median / ped_width

                    # save the config, to be retrieved as data.meta['config']
                    if not ff_initialized:
                        calib_data.meta['config'] = self.config
                        ff_initialized = True
                    else:
                        # write only after a first event (used to initialize the mask)
                        self.log.debug(f"write flatfield data")
                        self.writer.write('flatfield', ff_data)
                        self.log.debug(f"write pixel_status data")
                        self.writer.write('pixel_status', status_data)
                        self.log.debug(f"write calibration data")
                        self.writer.write('calibration', calib_data)

    def finish(self):
        Provenance().add_output_file(self.output_file,
                                     role='mon.tel.calibration')
        self.writer.close()
Example #9
0
def test_enum_classes_with_traits():
    """ test that we can get a list of classes that have traits """
    list_of_classes = classes_with_traits(ImageExtractor)
    assert list_of_classes  # should not be empty
class ChargeResolutionGenerator(Tool):
    name = "ChargeResolutionGenerator"
    description = ("Calculate the Charge Resolution from a sim_telarray "
                   "simulation and store within a HDF5 file.")

    telescopes = List(
        Int(),
        None,
        allow_none=True,
        help=
        "Telescopes to include from the event file. Default = All telescopes",
    ).tag(config=True)
    output_path = Unicode(
        "charge_resolution.h5",
        help="Path to store the output HDF5 file").tag(config=True)
    extractor_product = traits.enum_trait(ImageExtractor,
                                          default="NeighborPeakWindowSum")

    aliases = Dict(
        dict(
            f="SimTelEventSource.input_url",
            max_events="SimTelEventSource.max_events",
            T="SimTelEventSource.allowed_tels",
            extractor="ChargeResolutionGenerator.extractor_product",
            O="ChargeResolutionGenerator.output_path",
        ))

    classes = List([SimTelEventSource] +
                   traits.classes_with_traits(ImageExtractor))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.eventsource = None
        self.calibrator = None
        self.calculator = None

    def setup(self):
        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"

        self.eventsource = self.add_component(SimTelEventSource(parent=self))

        extractor = self.add_component(
            ImageExtractor.from_name(
                self.extractor_product,
                parent=self,
                subarray=self.eventsource.subarray,
            ))

        self.calibrator = self.add_component(
            CameraCalibrator(
                parent=self,
                image_extractor=extractor,
                subarray=self.eventsource.subarray,
            ))
        self.calculator = ChargeResolutionCalculator()

    def start(self):
        desc = "Extracting Charge Resolution"
        for event in tqdm(self.eventsource, desc=desc):
            self.calibrator(event)

            # Check events have true charge included
            if event.count == 0:
                try:
                    pe = list(event.mc.tel.values())[0].photo_electron_image
                    if np.all(pe == 0):
                        raise KeyError
                except KeyError:
                    self.log.exception("Source does not contain true charge!")
                    raise

            for mc, dl1 in zip(event.mc.tel.values(), event.dl1.tel.values()):
                true_charge = mc.photo_electron_image
                measured_charge = dl1.image
                pixels = np.arange(measured_charge.size)
                self.calculator.add(pixels, true_charge, measured_charge)

    def finish(self):
        df_p, df_c = self.calculator.finish()

        output_directory = os.path.dirname(self.output_path)
        if not os.path.exists(output_directory):
            self.log.info(f"Creating directory: {output_directory}")
            os.makedirs(output_directory)

        with pd.HDFStore(self.output_path, "w") as store:
            store["charge_resolution_pixel"] = df_p
            store["charge_resolution_camera"] = df_c

        self.log.info("Created charge resolution file: {}".format(
            self.output_path))
        Provenance().add_output_file(self.output_path)
Example #11
0
class DisplayIntegrator(Tool):
    name = "ctapipe-display-integration"
    description = __doc__

    event_index = Int(0, help="Event index to view.").tag(config=True)
    use_event_id = Bool(
        False,
        help=
        "event_index will obtain an event using event_id instead of index.",
    ).tag(config=True)
    telescope = Int(
        None,
        allow_none=True,
        help="Telescope to view. Set to None to display the first"
        "telescope with data.",
    ).tag(config=True)
    channel = Enum([0, 1], 0, help="Channel to view").tag(config=True)

    extractor_product = traits.enum_trait(ImageExtractor,
                                          default="NeighborPeakWindowSum")

    aliases = Dict(
        dict(
            f="EventSource.input_url",
            max_events="EventSource.max_events",
            extractor="DisplayIntegrator.extractor_product",
            E="DisplayIntegrator.event_index",
            T="DisplayIntegrator.telescope",
            C="DisplayIntegrator.channel",
        ))
    flags = Dict(
        dict(id=(
            {
                "DisplayDL1Calib": {
                    "use_event_index": True
                }
            },
            "event_index will obtain an event using event_id instead of index.",
        )))
    classes = List([EventSource] + traits.classes_with_traits(ImageExtractor))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # make sure gzip files are seekable
        self.config.SimTelEventSource.back_seekable = True
        self.eventseeker = None
        self.extractor = None
        self.calibrator = None

    def setup(self):
        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"

        event_source = self.add_component(EventSource.from_config(parent=self))
        self.eventseeker = self.add_component(
            EventSeeker(event_source, parent=self))
        self.extractor = self.add_component(
            ImageExtractor.from_name(self.extractor_product, parent=self))
        self.calibrate = self.add_component(
            CameraCalibrator(parent=self, image_extractor=self.extractor))

    def start(self):
        event_num = self.event_index
        if self.use_event_id:
            event_num = str(event_num)
        event = self.eventseeker[event_num]

        # Calibrate
        self.calibrate(event)

        # Select telescope
        tels = list(event.r0.tels_with_data)
        telid = self.telescope
        if telid is None:
            telid = tels[0]
        if telid not in tels:
            self.log.error("[event] please specify one of the following "
                           "telescopes for this event: {}".format(tels))
            exit()

        extractor_name = self.extractor.__class__.__name__

        plot(event, telid, self.channel, extractor_name)

    def finish(self):
        pass
Example #12
0
class DisplayIntegrator(Tool):
    name = "ctapipe-display-integration"
    description = __doc__

    event_index = Int(0, help="Event index to view.").tag(config=True)
    use_event_id = Bool(
        False, help="event_index will obtain an event using event_id instead of index."
    ).tag(config=True)
    telescope = Int(
        None,
        allow_none=True,
        help="Telescope to view. Set to None to display the first"
        "telescope with data.",
    ).tag(config=True)
    channel = Enum([0, 1], 0, help="Channel to view").tag(config=True)

    aliases = Dict(
        dict(
            f="EventSource.input_url",
            max_events="EventSource.max_events",
            E="DisplayIntegrator.event_index",
            T="DisplayIntegrator.telescope",
            C="DisplayIntegrator.channel",
        )
    )
    flags = Dict(
        dict(
            id=(
                {"DisplayDL1Calib": {"use_event_index": True}},
                "event_index will obtain an event using event_id instead of index.",
            )
        )
    )
    classes = [EventSource] + traits.classes_with_traits(ImageExtractor)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # make sure gzip files are seekable
        self.config.SimTelEventSource.back_seekable = True
        self.eventseeker = None
        self.calibrator = None

    def setup(self):
        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"

        event_source = EventSource(parent=self)
        self.subarray = event_source.subarray
        self.eventseeker = EventSeeker(event_source, parent=self)
        self.calibrate = CameraCalibrator(parent=self, subarray=self.subarray)

    def start(self):
        if self.use_event_id:
            event = self.eventseeker.get_event_id(self.event_index)
        else:
            event = self.eventseeker.get_event_index(self.event_index)

        # Calibrate
        self.calibrate(event)

        # Select telescope
        tels = list(event.r0.tel.keys())
        telid = self.telescope
        if telid is None:
            telid = tels[0]
        if telid not in tels:
            self.log.error(
                "[event] please specify one of the following "
                "telescopes for this event: {}".format(tels)
            )
            exit()

        extractor_name = self.calibrate.image_extractor_type.tel[telid]

        plot(self.subarray, event, telid, self.channel, extractor_name)

    def finish(self):
        pass
class CalibrationHDF5Writer(Tool):
    """
     Tool that generates a HDF5 file with camera calibration coefficients.
     This is just an example on how the monitoring containers can be filled using
     the calibration Components in calib/camera.
     This example is based on an input file with pedestal and flat-field events

     For getting help run:
     python calc_camera_calibration.py --help

     """

    name = "CalibrationHDF5Writer"
    description = "Generate a HDF5 file with camera calibration coefficients"

    one_event = Bool(
        False, help='Stop after first calibration event').tag(config=True)

    output_file = Unicode('calibration.hdf5',
                          help='Name of the output file').tag(config=True)

    log_file = Unicode('None', help='Name of the log file').tag(config=True)

    calibration_product = traits.create_class_enum_trait(
        CalibrationCalculator, default_value='LSTCalibrationCalculator')

    r0calibrator_product = traits.create_class_enum_trait(
        CameraR0Calibrator, default_value='NullR0Calibrator')

    aliases = Dict(
        dict(
            input_file='EventSource.input_url',
            output_file='CalibrationHDF5Writer.output_file',
            log_file='CalibrationHDF5Writer.log_file',
            max_events='EventSource.max_events',
            pedestal_file='LSTR0Corrections.pedestal_path',
            calibration_product='CalibrationHDF5Writer.calibration_product',
            r0calibrator_product='CalibrationHDF5Writer.r0calibrator_product',
        ))

    classes = List([EventSource, CalibrationCalculator] +
                   traits.classes_with_traits(CameraR0Calibrator) +
                   traits.classes_with_traits(CalibrationCalculator))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        """
         Tool that generates a HDF5 file with camera calibration coefficients.
         Input file must contain interleaved pedestal and flat-field events  

         For getting help run:
         python calc_camera_calibration.py --help
         
        """
        self.eventsource = None
        self.processor = None
        self.container = None
        self.writer = None
        self.r0calibrator = None
        self.tot_events = 0
        self.simulation = False

    def setup(self):

        self.log.debug(f"Open  file")
        self.eventsource = EventSource.from_config(parent=self)

        # if data remember how many event in the files
        if "LSTEventSource" in str(type(self.eventsource)):
            self.tot_events = len(self.eventsource.multi_file)
            self.log.debug(f"Input file has file {self.tot_events} events")
        else:
            self.tot_events = self.eventsource.max_events
            self.simulation = True

        self.processor = CalibrationCalculator.from_name(
            self.calibration_product,
            parent=self,
            subarray=self.eventsource.subarray)

        if self.r0calibrator_product:
            self.r0calibrator = CameraR0Calibrator.from_name(
                self.r0calibrator_product, parent=self)

        group_name = 'tel_' + str(self.processor.tel_id)

        self.log.debug(f"Open output file {self.output_file}")

        self.writer = HDF5TableWriter(filename=self.output_file,
                                      group_name=group_name,
                                      overwrite=True)

    def start(self):
        '''Calibration coefficient calculator'''

        tel_id = self.processor.tel_id
        new_ped = False
        new_ff = False
        end_of_file = False

        # skip the first events which are badly drs4 corrected
        events_to_skip = 1000

        try:
            self.log.debug(f"Start loop")
            for count, event in enumerate(self.eventsource):

                if count % 100 == 0:
                    self.log.debug(f"Event {count}")

                # if last event write results
                max_events_reached = (self.eventsource.max_events is not None
                                      and count
                                      == self.eventsource.max_events - 1)
                if count == self.tot_events - 1 or max_events_reached:
                    self.log.debug(f"Last event, count = {count}")
                    end_of_file = True

                # save the config, to be retrieved as data.meta['config']
                if count == 0:

                    if self.simulation:
                        initialize_pixel_status(
                            event.mon.tel[tel_id],
                            event.r1.tel[tel_id].waveform.shape)

                    ped_data = event.mon.tel[tel_id].pedestal
                    ped_data.meta['config'] = self.config

                    ff_data = event.mon.tel[tel_id].flatfield
                    ff_data.meta['config'] = self.config

                    status_data = event.mon.tel[tel_id].pixel_status
                    status_data.meta['config'] = self.config

                    calib_data = event.mon.tel[tel_id].calibration
                    calib_data.meta['config'] = self.config

                # correct for low level calibration
                self.r0calibrator.calibrate(event)

                # skip first events which are badly drs4 corrected
                if not self.simulation and count < events_to_skip:
                    continue

                # reject event without trigger type
                if LSTEventType.is_unknown(event.r1.tel[tel_id].trigger_type):
                    continue

                # if pedestal event
                if LSTEventType.is_pedestal(
                        event.r1.tel[tel_id].trigger_type
                ) or (self.simulation and np.median(
                        np.sum(event.r1.tel[tel_id].waveform[0], axis=1)) <
                      self.processor.minimum_hg_charge_median):
                    new_ped = self.processor.pedestal.calculate_pedestals(
                        event)

                # if flat-field event: no calibration  TIB for the moment,
                # use a cut on the charge for ff events and on std for rejecting Magic Lidar events
                elif LSTEventType.is_calibration(
                        event.r1.tel[tel_id].trigger_type
                ) or (np.median(
                        np.sum(event.r1.tel[tel_id].waveform[0], axis=1)) >
                      self.processor.minimum_hg_charge_median and
                      np.std(np.sum(event.r1.tel[tel_id].waveform[1], axis=1))
                      < self.processor.maximum_lg_charge_std):

                    new_ff = self.processor.flatfield.calculate_relative_gain(
                        event)
                # write pedestal results when enough statistics or end of file
                if new_ped or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.processor.pedestal.store_results(event)

                    # write the event
                    self.log.debug(
                        f"Write pedestal data at event n. {count+1}, id {event.index.event_id} "
                        f"stat = {ped_data.n_events} events")

                    # write on file
                    self.writer.write('pedestal', ped_data)

                    new_ped = False

                # write flatfield results when enough statistics (also for pedestals) or end of file
                if (new_ff and ped_data.n_events > 0) or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.processor.flatfield.store_results(event)

                    self.log.debug(
                        f"Write flatfield data at event n. {count+1}, id {event.index.event_id} "
                        f"stat = {ff_data.n_events} events")

                    # write on file
                    self.writer.write('flatfield', ff_data)

                    new_ff = False

                    # Then, calculate calibration coefficients
                    self.processor.calculate_calibration_coefficients(event)

                    # write calib and pixel status
                    self.log.debug(f"Write pixel_status data")
                    self.writer.write('pixel_status', status_data)

                    self.log.debug(f"Write calibration data")
                    self.writer.write('calibration', calib_data)
                    if self.one_event:
                        break

                    #self.writer.write('mon', event.mon.tel[tel_id])
        except ValueError as e:
            self.log.error(e)

    def finish(self):
        Provenance().add_output_file(self.output_file,
                                     role='mon.tel.calibration')
        self.writer.close()
class CalibrationHDF5Writer(Tool):
    """
     Tool that generates a HDF5 file with camera calibration coefficients.
     This is just an example on how the monitoring containers can be filled using
     the calibration Components in calib/camera.
     This example is based on an input file with interleaved pedestal and flat-field events

     For getting help run:
     python calc_camera_calibration.py --help

     """

    name = "CalibrationHDF5Writer"
    description = "Generate a HDF5 file with camera calibration coefficients"

    minimum_charge = Float(
        800,
        help='Temporary cut on charge till the calibox TIB do not work').tag(
            config=True)

    one_event = Bool(
        False, help='Stop after first calibration event').tag(config=True)

    output_file = Unicode('calibration.hdf5',
                          help='Name of the output file').tag(config=True)

    log_file = Unicode('None', help='Name of the log file').tag(config=True)

    pedestal_product = traits.enum_trait(PedestalCalculator,
                                         default='PedestalIntegrator')

    flatfield_product = traits.enum_trait(FlatFieldCalculator,
                                          default='FlasherFlatFieldCalculator')

    r0calibrator_product = traits.enum_trait(CameraR0Calibrator,
                                             default='NullR0Calibrator')

    aliases = Dict(
        dict(
            input_file='EventSource.input_url',
            output_file='CalibrationHDF5Writer.output_file',
            log_file='CalibrationHDF5Writer.log_file',
            max_events='EventSource.max_events',
            pedestal_file='LSTR0Corrections.pedestal_path',
            flatfield_product='CalibrationHDF5Writer.flatfield_product',
            pedestal_product='CalibrationHDF5Writer.pedestal_product',
            r0calibrator_product='CalibrationHDF5Writer.r0calibrator_product',
        ))

    classes = List([EventSource, FlatFieldCalculator, PedestalCalculator] +
                   traits.classes_with_traits(CameraR0Calibrator) +
                   traits.classes_with_traits(FlatFieldCalculator) +
                   traits.classes_with_traits(PedestalCalculator))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        """
         Tool that generates a HDF5 file with camera calibration coefficients.
         Input file must contain interleaved pedestal and flat-field events  

         For getting help run:
         python calc_camera_calibration.py --help
         
        """
        self.eventsource = None
        self.flatfield = None
        self.pedestal = None
        self.container = None
        self.writer = None
        self.r0calibrator = None
        self.tel_id = None
        self.tot_events = 0

    def setup(self):
        kwargs = dict(parent=self)

        self.eventsource = EventSource.from_config(**kwargs)

        # remember how many event in the files
        self.tot_events = len(self.eventsource.multi_file)
        self.log.debug(f"Input file has file {self.tot_events} events")

        self.flatfield = FlatFieldCalculator.from_name(self.flatfield_product,
                                                       **kwargs)
        self.pedestal = PedestalCalculator.from_name(self.pedestal_product,
                                                     **kwargs)

        if self.r0calibrator_product:
            self.r0calibrator = CameraR0Calibrator.from_name(
                self.r0calibrator_product, **kwargs)

        msg = "tel_id not the same for all calibration components"
        assert self.r0calibrator.tel_id == self.pedestal.tel_id == self.flatfield.tel_id, msg

        self.tel_id = self.flatfield.tel_id

        group_name = 'tel_' + str(self.tel_id)

        self.log.debug(f"Open output file {self.output_file}")

        self.writer = HDF5TableWriter(filename=self.output_file,
                                      group_name=group_name,
                                      overwrite=True)

    def start(self):
        '''Calibration coefficient calculator'''

        new_ped = False
        new_ff = False
        end_of_file = False
        try:

            self.log.debug(f"Start loop")
            for count, event in enumerate(self.eventsource):

                if count % 1000 == 0:
                    self.log.debug(f"Event {count}")

                # if last event write results
                if count == self.tot_events - 1 or count == self.eventsource.max_events - 1:
                    self.log.debug(f"Last event, count = {count}")
                    end_of_file = True

                # save the config, to be retrieved as data.meta['config']
                if count == 0:
                    ped_data = event.mon.tel[self.tel_id].pedestal
                    ped_data.meta['config'] = self.config

                    ff_data = event.mon.tel[self.tel_id].flatfield
                    ff_data.meta['config'] = self.config

                    status_data = event.mon.tel[self.tel_id].pixel_status
                    status_data.meta['config'] = self.config

                    calib_data = event.mon.tel[self.tel_id].calibration
                    calib_data.meta['config'] = self.config

                # correct for low level calibration
                self.r0calibrator.calibrate(event)

                # if pedestal event
                if event.r1.tel[self.tel_id].trigger_type == 32:

                    new_ped = self.pedestal.calculate_pedestals(event)

                # if flat-field event: no calibration  TIB for the moment, use a cut on the charge for ff events
                elif event.r1.tel[self.tel_id].trigger_type == 4 or np.median(
                        np.sum(event.r1.tel[self.tel_id].waveform[0],
                               axis=1)) > self.minimum_charge:

                    new_ff = self.flatfield.calculate_relative_gain(event)

                # write pedestal results when enough statistics or end of file
                if new_ped or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.pedestal.store_results(event)

                    # write the event
                    self.log.debug(
                        f"Write pedestal data at event n. {count+1}, id {event.r0.event_id} "
                        f"stat = {ped_data.n_events} events")

                    # write on file
                    self.writer.write('pedestal', ped_data)

                    new_ped = False

                # write flatfield results when enough statistics (also for pedestals) or end of file
                if (new_ff and ped_data.n_events > 0) or end_of_file:

                    # update the monitoring container with the last statistics
                    if end_of_file:
                        self.flatfield.store_results(event)

                    self.log.debug(
                        f"Write flatfield data at event n. {count+1}, id {event.r0.event_id} "
                        f"stat = {ff_data.n_events} events")

                    # write on file
                    self.writer.write('flatfield', ff_data)

                    new_ff = False

                    # Then, calculate calibration coefficients

                    # mask from pedestal and flat-field data
                    monitoring_unusable_pixels = np.logical_or(
                        status_data.pedestal_failing_pixels,
                        status_data.flatfield_failing_pixels)

                    # calibration unusable pixels are an OR of all masks
                    calib_data.unusable_pixels = np.logical_or(
                        monitoring_unusable_pixels,
                        status_data.hardware_failing_pixels)

                    # Extract calibration coefficients with F-factor method
                    # Assume fix F2 factor, F2=1+Var(gain)/Mean(Gain)**2 must be known from elsewhere
                    F2 = 1.2

                    # calculate photon-electrons
                    n_pe = F2 * (ff_data.charge_median - ped_data.charge_median
                                 )**2 / (ff_data.charge_std**2 -
                                         ped_data.charge_std**2)

                    # fill WaveformCalibrationContainer (this is an example)
                    calib_data.time = ff_data.sample_time
                    calib_data.time_range = ff_data.sample_time_range
                    calib_data.n_pe = n_pe
                    calib_data.dc_to_pe = n_pe / (ff_data.charge_median -
                                                  ped_data.charge_median)
                    calib_data.time_correction = -ff_data.relative_time_median

                    ped_extractor_name = self.config.get(
                        "PedestalCalculator").get("charge_product")
                    ped_width = self.config.get(ped_extractor_name).get(
                        "window_width")
                    calib_data.pedestal_per_sample = ped_data.charge_median / ped_width

                    # write calib and pixel status

                    self.log.debug(f"Write pixel_status data")
                    self.writer.write('pixel_status', status_data)

                    self.log.debug(f"Write calibration data")
                    self.writer.write('calibration', calib_data)
                    if self.one_event:
                        break

                    #self.writer.write('mon', event.mon.tel[self.tel_id])
        except ValueError as e:
            self.log.error(e)

    def finish(self):
        Provenance().add_output_file(self.output_file,
                                     role='mon.tel.calibration')
        self.writer.close()
Example #15
0
class MuonAnalysis(Tool):
    """
    Detect and extract muon ring parameters, and write the muon ring and
    intensity parameters to an output table.

    The resulting output can be read e.g. using for example
    `pandas.read_hdf(filename, 'dl1/event/telescope/parameters/muon')`
    """
    name = 'ctapipe-reconstruct-muons'
    description = traits.Unicode(__doc__)

    output = traits.Path(directory_ok=False,
                         help='HDF5 output file name').tag(config=True)

    completeness_threshold = traits.FloatTelescopeParameter(
        default_value=30.0,
        help='Threshold for calculating the ``ring_completeness``',
    ).tag(config=True)

    ratio_width = traits.FloatTelescopeParameter(
        default_value=1.5,
        help=('Ring width for intensity ratio'
              ' computation as multiple of pixel diameter')).tag(config=True)

    overwrite = traits.Bool(
        default_value=False,
        help='If true, overwrite outputfile without asking').tag(config=True)

    min_pixels = traits.IntTelescopeParameter(
        help=('Minimum number of pixels after cleaning and ring finding'
              'required to process an event'),
        default_value=100,
    ).tag(config=True)

    pedestal = traits.FloatTelescopeParameter(
        help='Pedestal noise rms',
        default_value=1.1,
    ).tag(config=True)

    extractor_name = traits.create_class_enum_trait(
        ImageExtractor,
        default_value='GlobalPeakWindowSum',
    ).tag(config=True)

    classes = [
        CameraCalibrator,
        TailcutsImageCleaner,
        EventSource,
        MuonRingFitter,
        MuonIntensityFitter,
    ] + traits.classes_with_traits(ImageExtractor)

    aliases = {
        'i': 'EventSource.input_url',
        'input': 'EventSource.input_url',
        'o': 'MuonAnalysis.output',
        'output': 'MuonAnalysis.output',
        'max-events': 'EventSource.max_events',
        'allowed-tels': 'EventSource.allowed_tels',
    }

    flags = {
        'overwrite': ({
            'MuonAnalysis': {
                'overwrite': True
            }
        }, 'overwrite output file')
    }

    def setup(self):
        if self.output is None:
            raise ToolConfigurationError(
                'You need to provide an --output file')

        if self.output.exists() and not self.overwrite:
            raise ToolConfigurationError(
                'Outputfile {self.output} already exists, use `--overwrite` to overwrite'
            )

        self.source = self.add_component(EventSource.from_config(parent=self))
        self.extractor = self.add_component(
            ImageExtractor.from_name(self.extractor_name,
                                     parent=self,
                                     subarray=self.source.subarray))
        self.calib = self.add_component(
            CameraCalibrator(
                subarray=self.source.subarray,
                parent=self,
                image_extractor=self.extractor,
            ))
        self.ring_fitter = self.add_component(MuonRingFitter(parent=self, ))
        self.intensity_fitter = self.add_component(
            MuonIntensityFitter(
                subarray=self.source.subarray,
                parent=self,
            ))
        self.cleaning = self.add_component(
            TailcutsImageCleaner(
                parent=self,
                subarray=self.source.subarray,
            ))
        self.writer = self.add_component(
            HDF5TableWriter(
                self.output,
                "",
                add_prefix=True,
                parent=self,
                mode='w',
            ))
        self.pixels_in_tel_frame = {}
        self.field_of_view = {}
        self.pixel_widths = {}

        for p in [
                'min_pixels', 'pedestal', 'ratio_width',
                'completeness_threshold'
        ]:
            getattr(self, p).attach_subarray(self.source.subarray)

    def start(self):
        for event in tqdm(self.source, desc='Processing events: '):
            self.process_array_event(event)

    def process_array_event(self, event):
        self.calib(event)

        for tel_id, dl1 in event.dl1.tel.items():
            self.process_telescope_event(event.index, tel_id, dl1)

        self.writer.write('sim/event/subarray/shower', [event.index, event.mc])

    def process_telescope_event(self, event_index, tel_id, dl1):
        event_id = event_index.event_id

        if self.source.subarray.tel[tel_id].optics.num_mirrors != 1:
            self.log.warn(f'Skipping non-single mirror telescope {tel_id}'
                          ' set --allowed_tels to get rid of this warning')
            return

        self.log.debug(f'Processing event {event_id}, telescope {tel_id}')
        image = dl1.image
        clean_mask = self.cleaning(tel_id, image)

        if np.count_nonzero(clean_mask) <= self.min_pixels.tel[tel_id]:
            self.log.debug(
                f'Skipping event {event_id}-{tel_id}:'
                f' has less then {self.min_pixels.tel[tel_id]} pixels after cleaning'
            )
            return

        x, y = self.get_pixel_coords(tel_id)

        # iterative ring fit.
        # First use cleaning pixels, then only pixels close to the ring
        # three iterations seems to be enough for most rings
        mask = clean_mask
        for i in range(3):
            ring = self.ring_fitter(x, y, image, mask)
            dist = np.sqrt((x - ring.center_x)**2 + (y - ring.center_y)**2)
            mask = np.abs(dist - ring.radius) / ring.radius < 0.4

        if np.count_nonzero(mask) <= self.min_pixels.tel[tel_id]:
            self.log.debug(
                f'Skipping event {event_id}-{tel_id}:'
                f' Less then {self.min_pixels.tel[tel_id]} pixels on ring')
            return

        if np.isnan(
            [ring.radius.value, ring.center_x.value,
             ring.center_y.value]).any():
            self.log.debug(
                f'Skipping event {event_id}-{tel_id}: Ring fit did not succeed'
            )
            return

        parameters = self.calculate_muon_parameters(tel_id, image, clean_mask,
                                                    ring)

        # intensity_fitter does not support a mask yet, set ignored pixels to 0
        image[~mask] = 0

        result = self.intensity_fitter(
            tel_id,
            ring.center_x,
            ring.center_y,
            ring.radius,
            image,
            pedestal=self.pedestal.tel[tel_id],
        )

        self.log.info(
            f'Muon fit: r={ring.radius:.2f}'
            f', width={result.width:.4f}'
            f', efficiency={result.optical_efficiency:.2%}', )

        tel_event_index = TelEventIndexContainer(
            **event_index,
            tel_id=tel_id,
        )

        self.writer.write('dl1/event/telescope/parameters/muons',
                          [tel_event_index, ring, parameters, result])

    def calculate_muon_parameters(self, tel_id, image, clean_mask, ring):
        fov_radius = self.get_fov(tel_id)
        x, y = self.get_pixel_coords(tel_id)

        # add ring containment, not filled in fit
        containment = ring_containment(
            ring.radius,
            ring.center_x,
            ring.center_y,
            fov_radius,
        )

        completeness = ring_completeness(
            x,
            y,
            image,
            ring.radius,
            ring.center_x,
            ring.center_y,
            threshold=self.completeness_threshold.tel[tel_id],
        )

        pixel_width = self.get_pixel_width(tel_id)
        intensity_ratio = intensity_ratio_inside_ring(
            x[clean_mask],
            y[clean_mask],
            image[clean_mask],
            ring.radius,
            ring.center_x,
            ring.center_y,
            width=self.ratio_width.tel[tel_id] * pixel_width,
        )

        mse = mean_squared_error(x[clean_mask], y[clean_mask],
                                 image[clean_mask], ring.radius, ring.center_x,
                                 ring.center_y)

        return MuonParametersContainer(
            containment=containment,
            completeness=completeness,
            intensity_ratio=intensity_ratio,
            mean_squared_error=mse,
        )

    def get_fov(self, tel_id):
        '''Guesstimate fov radius for telescope with id `tel_id`'''
        # memoize fov calculation
        if tel_id not in self.field_of_view:
            cam = self.source.subarray.tel[tel_id].camera.geometry
            border = cam.get_border_pixel_mask()

            x, y = self.get_pixel_coords(tel_id)
            self.field_of_view[tel_id] = np.sqrt(x[border]**2 +
                                                 y[border]**2).mean()

        return self.field_of_view[tel_id]

    def get_pixel_width(self, tel_id):
        '''Guesstimate fov radius for telescope with id `tel_id`'''
        # memoize fov calculation
        if tel_id not in self.pixel_widths:
            x, y = self.get_pixel_coords(tel_id)
            self.pixel_widths[tel_id] = CameraGeometry.guess_pixel_width(x, y)

        return self.pixel_widths[tel_id]

    def get_pixel_coords(self, tel_id):
        '''Get pixel coords in telescope frame for telescope with id `tel_id`'''
        # memoize transformation
        if tel_id not in self.pixels_in_tel_frame:
            telescope = self.source.subarray.tel[tel_id]
            cam = telescope.camera.geometry
            camera_frame = CameraFrame(
                focal_length=telescope.optics.equivalent_focal_length,
                rotation=cam.cam_rotation,
            )
            cam_coords = SkyCoord(x=cam.pix_x, y=cam.pix_y, frame=camera_frame)
            tel_coord = cam_coords.transform_to(TelescopeFrame())
            self.pixels_in_tel_frame[tel_id] = tel_coord

        coords = self.pixels_in_tel_frame[tel_id]
        return coords.fov_lon, coords.fov_lat

    def finish(self):
        Provenance().add_output_file(
            self.output,
            role='muon_efficiency_parameters',
        )
        self.writer.close()