def test_enum_trait_default_is_right(): """ check default value of enum trait """ from ctapipe.core.traits import create_class_enum_trait with pytest.raises(ValueError): create_class_enum_trait(ImageExtractor, default_value="name_of_default_choice")
def test_enum_trait(): """ check that enum traits are constructable from a complex class """ from ctapipe.core.traits import create_class_enum_trait trait = create_class_enum_trait(ImageExtractor, default_value="NeighborPeakWindowSum") assert isinstance(trait, CaselessStrEnum)
class ShowerProcessor(Component): """ Run the stereo event reconstruction on the input events. This is mainly needed, so that the type of reconstructor can be chosen via the configuration system. Input events must already contain dl1 parameters. """ reconstructor_type = create_class_enum_trait( Reconstructor, default_value="HillasReconstructor", help="The stereo geometry reconstructor to be used", ) def __init__(self, subarray: SubarrayDescription, config=None, parent=None, **kwargs): """ Parameters ---------- subarray: SubarrayDescription Description of the subarray. Provides information about the camera which are useful in calibration. Also required for configuring the TelescopeParameter traitlets. config: traitlets.loader.Config Configuration specified by config file or cmdline arguments. Used to set traitlet values. This is mutually exclusive with passing a ``parent``. parent: ctapipe.core.Component or ctapipe.core.Tool Parent of this component in the configuration hierarchy, this is mutually exclusive with passing ``config`` """ super().__init__(config=config, parent=parent, **kwargs) self.subarray = subarray self.reconstructor = Reconstructor.from_name( self.reconstructor_type, subarray=self.subarray, parent=self, ) def __call__(self, event: ArrayEventContainer): """ Perform the full shower geometry reconstruction on the input event. Afterwards, optionally perform energy estimation and/or particle classification (currently these two operations are not yet supported). Parameters ---------- event : ctapipe.containers.ArrayEventContainer Top-level container for all event information. """ k = self.reconstructor_type event.dl2.stereo.geometry[k] = self.reconstructor(event)
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}")
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}")
class TailCutsDataVolumeReducer(DataVolumeReducer): """ Reduce the time integrated shower image in 3 Steps: 1) Select pixels with tailcuts_clean. 2) Add iteratively all pixels with Signal S >= boundary_thresh with ctapipe module dilate until no new pixels were added. 3) Adding new pixels with dilate to get more conservative. Attributes ---------- image_extractor_type: String Name of the image_extractor to be used. n_end_dilates: IntTelescopeParameter Number of how many times to dilate at the end. do_boundary_dilation: BoolTelescopeParameter If set to 'False', the iteration steps in 2) are skipped and normal TailcutCleaning is used. """ image_extractor_type = TelescopeParameter( trait=create_class_enum_trait(ImageExtractor, default_value="NeighborPeakWindowSum"), default_value="NeighborPeakWindowSum", help="Name of the ImageExtractor subclass to be used.", ).tag(config=True) n_end_dilates = IntTelescopeParameter( default_value=1, help="Number of how many times to dilate at the end.").tag(config=True) do_boundary_dilation = BoolTelescopeParameter( default_value=True, help="If set to 'False', the iteration steps in 2) are skipped and" "normal TailcutCleaning is used.", ).tag(config=True) def __init__( self, subarray, config=None, parent=None, cleaner=None, image_extractor=None, **kwargs, ): """ Parameters ---------- subarray: ctapipe.instrument.SubarrayDescription Description of the subarray 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__(config=config, parent=parent, subarray=subarray, **kwargs) if cleaner is None: self.cleaner = TailcutsImageCleaner(parent=self, subarray=self.subarray) else: self.cleaner = cleaner self.image_extractors = {} if image_extractor is None: for (_, _, name) in self.image_extractor_type: self.image_extractors[name] = ImageExtractor.from_name( name, subarray=self.subarray, parent=self) else: name = image_extractor.__class__.__name__ self.image_extractor_type = [("type", "*", name)] self.image_extractors[name] = image_extractor def select_pixels(self, waveforms, telid=None, selected_gain_channel=None): camera_geom = self.subarray.tel[telid].camera.geometry # Pulse-integrate waveforms extractor = self.image_extractors[self.image_extractor_type.tel[telid]] charge, _ = extractor(waveforms, telid=telid, selected_gain_channel=selected_gain_channel) # 1) Step: TailcutCleaning at first mask = self.cleaner(telid, charge) pixels_above_boundary_thresh = ( charge >= self.cleaner.boundary_threshold_pe.tel[telid]) mask_in_loop = np.array([]) # 2) Step: Add iteratively all pixels with Signal # S > boundary_thresh with ctapipe module # 'dilate' until no new pixels were added. while (not np.array_equal(mask, mask_in_loop) and self.do_boundary_dilation.tel[telid]): mask_in_loop = mask mask = dilate(camera_geom, mask) & pixels_above_boundary_thresh # 3) Step: Adding Pixels with 'dilate' to get more conservative. for _ in range(self.n_end_dilates.tel[telid]): mask = dilate(camera_geom, mask) return mask
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.create_class_enum_trait( ImageExtractor, default_value="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 = [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(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, subarray=self.reader.subarray) # 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.get_event_index(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.get_event_id(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.tel.keys()) 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.index.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.tel.keys()] 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
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 CameraCalibrator(TelescopeComponent): """ Calibrator to handle the full camera calibration chain, in order to fill the DL1 data level in the event container. Attributes ---------- data_volume_reducer_type: str The name of the DataVolumeReducer subclass to be used for data volume reduction image_extractor_type: str The name of the ImageExtractor subclass to be used for image extraction """ data_volume_reducer_type = create_class_enum_trait( DataVolumeReducer, default_value="NullDataVolumeReducer").tag(config=True) image_extractor_type = TelescopeParameter( trait=create_class_enum_trait(ImageExtractor, default_value="NeighborPeakWindowSum"), default_value="NeighborPeakWindowSum", help="Name of the ImageExtractor subclass to be used.", ).tag(config=True) apply_waveform_time_shift = BoolTelescopeParameter( default_value=False, help=("Apply waveform time shift corrections." " The minimal integer shift to synchronize waveforms is applied" " before peak extraction if this option is True"), ).tag(config=True) apply_peak_time_shift = BoolTelescopeParameter( default_value=True, help= ("Apply peak time shift corrections." " Apply the remaining absolute and fractional time shift corrections" " to the peak time after pulse extraction." " If `apply_waveform_time_shift` is False, this will apply the full time shift" ), ).tag(config=True) def __init__( self, subarray, config=None, parent=None, image_extractor=None, data_volume_reducer=None, **kwargs, ): """ Parameters ---------- subarray: ctapipe.instrument.SubarrayDescription Description of the subarray. Provides information about the camera which are useful in calibration. Also required for configuring the TelescopeParameter traitlets. config: traitlets.loader.Config Configuration specified by config file or cmdline arguments. Used to set traitlet values. This is mutually exclusive with passing a ``parent``. parent: ctapipe.core.Component or ctapipe.core.Tool Parent of this component in the configuration hierarchy, this is mutually exclusive with passing ``config`` data_volume_reducer: ctapipe.image.reducer.DataVolumeReducer The DataVolumeReducer to use. This is used to override the options from the config system and to enable passing a preconfigured reducer. image_extractor: ctapipe.image.extractor.ImageExtractor The ImageExtractor to use. If None, the default via the configuration system will be constructed. """ super().__init__(subarray=subarray, config=config, parent=parent, **kwargs) self.subarray = subarray self._r1_empty_warn = False self._dl0_empty_warn = False self.image_extractors = {} if image_extractor is None: for (_, _, name) in self.image_extractor_type: self.image_extractors[name] = ImageExtractor.from_name( name, subarray=self.subarray, parent=self) else: name = image_extractor.__class__.__name__ self.image_extractor_type = [("type", "*", name)] self.image_extractors[name] = image_extractor if data_volume_reducer is None: self.data_volume_reducer = DataVolumeReducer.from_name( self.data_volume_reducer_type, subarray=self.subarray, parent=self) else: self.data_volume_reducer = data_volume_reducer def _check_r1_empty(self, waveforms): if waveforms is None: if not self._r1_empty_warn: warnings.warn("Encountered an event with no R1 data. " "DL0 is unchanged in this circumstance.") self._r1_empty_warn = True return True else: return False def _check_dl0_empty(self, waveforms): if waveforms is None: if not self._dl0_empty_warn: warnings.warn("Encountered an event with no DL0 data. " "DL1 is unchanged in this circumstance.") self._dl0_empty_warn = True return True else: return False def _calibrate_dl0(self, event, telid): waveforms = event.r1.tel[telid].waveform selected_gain_channel = event.r1.tel[telid].selected_gain_channel if self._check_r1_empty(waveforms): return reduced_waveforms_mask = self.data_volume_reducer( waveforms, telid=telid, selected_gain_channel=selected_gain_channel) waveforms_copy = waveforms.copy() waveforms_copy[~reduced_waveforms_mask] = 0 event.dl0.tel[telid].waveform = waveforms_copy event.dl0.tel[telid].selected_gain_channel = selected_gain_channel def _calibrate_dl1(self, event, telid): waveforms = event.dl0.tel[telid].waveform selected_gain_channel = event.dl0.tel[telid].selected_gain_channel dl1_calib = event.calibration.tel[telid].dl1 if self._check_dl0_empty(waveforms): return selected_gain_channel = event.r1.tel[telid].selected_gain_channel time_shift = event.calibration.tel[telid].dl1.time_shift readout = self.subarray.tel[telid].camera.readout n_pixels, n_samples = waveforms.shape # subtract any remaining pedestal before extraction if dl1_calib.pedestal_offset is not None: # this copies intentionally, we don't want to modify the dl0 data # waveforms have shape (n_pixel, n_samples), pedestals (n_pixels, ) waveforms = waveforms - dl1_calib.pedestal_offset[:, np.newaxis] if n_samples == 1: # To handle ASTRI and dst # TODO: Improved handling of ASTRI and dst # - dst with custom EventSource? # - Read into dl1 container directly? # - Don't do anything if dl1 container already filled # - Update on SST review decision charge = waveforms[..., 0].astype(np.float32) peak_time = np.zeros(n_pixels, dtype=np.float32) else: # shift waveforms if time_shift calibration is available if time_shift is not None: if self.apply_waveform_time_shift.tel[telid]: sampling_rate = readout.sampling_rate.to_value(u.GHz) time_shift_samples = time_shift * sampling_rate waveforms, remaining_shift = shift_waveforms( waveforms, time_shift_samples) remaining_shift /= sampling_rate else: remaining_shift = time_shift extractor = self.image_extractors[ self.image_extractor_type.tel[telid]] charge, peak_time = extractor( waveforms, telid=telid, selected_gain_channel=selected_gain_channel) # correct non-integer remainder of the shift if given if self.apply_peak_time_shift.tel[telid] and time_shift is not None: peak_time -= remaining_shift # Calibrate extracted charge charge *= dl1_calib.relative_factor / dl1_calib.absolute_factor event.dl1.tel[telid].image = charge event.dl1.tel[telid].peak_time = peak_time def __call__(self, event): """ Perform the full camera calibration from R1 to DL1. Any calibration relating to data levels before the data level the file is read into will be skipped. Parameters ---------- event : container A `~ctapipe.containers.ArrayEventContainer` event container """ # TODO: How to handle different calibrations depending on telid? tel = event.r1.tel or event.dl0.tel or event.dl1.tel for telid in tel.keys(): self._calibrate_dl0(event, telid) self._calibrate_dl1(event, telid)
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 = Path( default_value="charge_resolution.h5", directory_ok=False, help="Path to store the output HDF5 file", ).tag(config=True) extractor_product = traits.create_class_enum_trait( ImageExtractor, default_value="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].true_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.true_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)
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.create_class_enum_trait( ImageExtractor, default_value="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.subarray = event_source.subarray self.eventseeker = self.add_component( EventSeeker(event_source, parent=self)) self.extractor = self.add_component( ImageExtractor.from_name(self.extractor_product, parent=self, subarray=self.subarray)) self.calibrate = self.add_component( CameraCalibrator(parent=self, image_extractor=self.extractor, subarray=self.subarray)) 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(self.subarray, event, telid, self.channel, extractor_name) def finish(self): pass
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()
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) extractor_product = traits.create_class_enum_trait( ImageExtractor, default_value="NeighborPeakWindowSum") aliases = Dict( dict( input="EventSource.input_url", max_events="EventSource.max_events", extractor="DisplayDL1Calib.extractor_product", 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 = self.add_component( EventSource.from_config(parent=self)) self.calibrator = self.add_component( CameraCalibrator(parent=self, subarray=self.eventsource.subarray)) self.plotter = self.add_component( ImagePlotter(subarray=self.eventsource.subarray, parent=self)) def start(self): for event in self.eventsource: self.calibrator(event) tel_list = event.r0.tels_with_data 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()