def predict(self, hillas_parameters, tel_x, tel_y, array_direction): """ Parameters ---------- hillas_parameters: dict Dictionary containing Hillas parameters for all telescopes in reconstruction tel_x: dict Dictionary containing telescope position on ground for all telescopes in reconstruction tel_y: dict Dictionary containing telescope position on ground for all telescopes in reconstruction array_direction: AltAz Pointing direction of the array Returns ------- ReconstructedShowerContainer: """ src_x, src_y, err_x, err_y = self.reconstruct_nominal( hillas_parameters) core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted( hillas_parameters, tel_x, tel_y) err_x *= u.rad err_y *= u.rad nom = SkyCoord(x=src_x * u.rad, y=src_y * u.rad, frame=NominalFrame(array_direction=array_direction)) horiz = nom.transform_to(AltAz()) result = ReconstructedShowerContainer() result.alt, result.az = horiz.alt, horiz.az tilt = SkyCoord( x=core_x * u.m, y=core_y * u.m, frame=TiltedGroundFrame(pointing_direction=array_direction), ) grd = project_to_ground(tilt) result.core_x = grd.x result.core_y = grd.y x_max = self.reconstruct_xmax( nom.x, nom.y, tilt.x, tilt.y, hillas_parameters, tel_x, tel_y, 90 * u.deg - array_direction.alt, ) result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m result.tel_ids = [h for h in hillas_parameters.keys()] result.average_intensity = np.mean( [h.intensity for h in hillas_parameters.values()]) result.is_valid = True src_error = np.sqrt(err_x**2 + err_y**2) result.alt_uncert = src_error.to(u.deg) result.az_uncert = src_error.to(u.deg) result.h_max = x_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result
def predict(self, hillas_dict, inst, array_pointing, telescopes_pointings=None): """ Parameters ---------- hillas_dict: dict Dictionary containing Hillas parameters for all telescopes in reconstruction inst : ctapipe.io.InstrumentContainer instrumental description array_pointing: SkyCoord[AltAz] pointing direction of the array telescopes_pointings: dict[SkyCoord[AltAz]] dictionary of pointing direction per each telescope Returns ------- ReconstructedShowerContainer: """ # filter warnings for missing obs time. this is needed because MC data has no obs time warnings.filterwarnings(action='ignore', category=MissingFrameAttributeWarning) # stereoscopy needs at least two telescopes if len(hillas_dict) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}".format( len(hillas_dict))) # check for np.nan or 0 width's as these screw up weights if any( [np.isnan(hillas_dict[tel]['width'].value) for tel in hillas_dict]): raise InvalidWidthException( "A HillasContainer contains an ellipse of width==np.nan") if any([hillas_dict[tel]['width'].value == 0 for tel in hillas_dict]): raise InvalidWidthException( "A HillasContainer contains an ellipse of width==0") if telescopes_pointings is None: telescopes_pointings = { tel_id: array_pointing for tel_id in hillas_dict.keys() } tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing) ground_positions = inst.subarray.tel_coords grd_coord = GroundFrame(x=ground_positions.x, y=ground_positions.y, z=ground_positions.z) tilt_coord = grd_coord.transform_to(tilted_frame) tel_x = { tel_id: tilt_coord.x[tel_id - 1] for tel_id in list(hillas_dict.keys()) } tel_y = { tel_id: tilt_coord.y[tel_id - 1] for tel_id in list(hillas_dict.keys()) } nom_frame = NominalFrame(origin=array_pointing) hillas_dict_mod = copy.deepcopy(hillas_dict) for tel_id, hillas in hillas_dict_mod.items(): # prevent from using rads instead of meters as inputs assert hillas.x.to(u.m).unit == u.Unit('m') focal_length = inst.subarray.tel[ tel_id].optics.equivalent_focal_length camera_frame = CameraFrame( telescope_pointing=telescopes_pointings[tel_id], focal_length=focal_length, ) cog_coords = SkyCoord(x=hillas.x, y=hillas.y, frame=camera_frame) cog_coords_nom = cog_coords.transform_to(nom_frame) hillas.x = cog_coords_nom.delta_alt hillas.y = cog_coords_nom.delta_az src_x, src_y, err_x, err_y = self.reconstruct_nominal(hillas_dict_mod) core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted( hillas_dict_mod, tel_x, tel_y) err_x *= u.rad err_y *= u.rad nom = SkyCoord(delta_az=src_x * u.rad, delta_alt=src_y * u.rad, frame=nom_frame) # nom = sky_pos.transform_to(nom_frame) sky_pos = nom.transform_to(array_pointing.frame) result = ReconstructedShowerContainer() result.alt = sky_pos.altaz.alt.to(u.rad) result.az = sky_pos.altaz.az.to(u.rad) tilt = SkyCoord( x=core_x * u.m, y=core_y * u.m, frame=tilted_frame, ) grd = project_to_ground(tilt) result.core_x = grd.x result.core_y = grd.y x_max = self.reconstruct_xmax( nom.delta_az, nom.delta_alt, tilt.x, tilt.y, hillas_dict_mod, tel_x, tel_y, 90 * u.deg - array_pointing.alt, ) result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m result.tel_ids = [h for h in hillas_dict_mod.keys()] result.average_intensity = np.mean( [h.intensity for h in hillas_dict_mod.values()]) result.is_valid = True src_error = np.sqrt(err_x**2 + err_y**2) result.alt_uncert = src_error.to(u.rad) result.az_uncert = src_error.to(u.rad) result.h_max = x_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result
def process_event(event, calibrator, config): '''Processes one event. Calls calculate_image_features and performs a stereo hillas reconstruction. ReconstructedShowerContainer will be filled with nans (+units) if hillas failed. Returns: -------- ArrayEventContainer list(telescope_event_containers.values()) ''' logging.info(f'processing event {event.dl0.event_id}') calibrator(event) telescope_types = [] hillas_reconstructor = HillasReconstructor() telescope_event_containers = {} horizon_frame = AltAz() telescope_pointings = {} array_pointing = SkyCoord( az=event.mcheader.run_array_direction[0], alt=event.mcheader.run_array_direction[1], frame=horizon_frame, ) for telescope_id, dl1 in event.dl1.tel.items(): cam_type = event.inst.subarray.tels[telescope_id].camera.cam_id if cam_type not in config.cleaning_level.keys(): logging.info(f"No cleaning levels for camera {cam_type}. Skipping event") continue telescope_types.append(str(event.inst.subarray.tels[telescope_id].optics)) try: telescope_event_containers[telescope_id] = calculate_image_features( telescope_id, event, dl1, config ) except HillasParameterizationError: logging.info( f'Error calculating hillas features for event {event.dl0.event_id, telescope_id}', exc_info=True, ) continue except Exception: logging.info( f'Error calculating image features for event {event.dl0.event_id, telescope_id}', exc_info=True, ) continue telescope_pointings[telescope_id] = SkyCoord( alt=telescope_event_containers[telescope_id].pointing.altitude, az=telescope_event_containers[telescope_id].pointing.azimuth, frame=horizon_frame, ) if len(telescope_event_containers) < 1: raise Exception('None of the allowed telescopes triggered for event %s', event.dl0.event_id) parameters = {tel_id: telescope_event_containers[tel_id].hillas for tel_id in telescope_event_containers} try: reconstruction_container = hillas_reconstructor.predict( parameters, event.inst, array_pointing, telescopes_pointings=telescope_pointings ) reconstruction_container.prefix = '' except Exception: logging.info( 'Not enough telescopes for which Hillas parameters could be reconstructed', exc_info=True ) reconstruction_container = ReconstructedShowerContainer() reconstruction_container.alt = u.Quantity(np.nan, u.rad) reconstruction_container.alt_uncert = u.Quantity(np.nan, u.rad) reconstruction_container.az = u.Quantity(np.nan, u.rad) reconstruction_container.az_uncert = np.nan reconstruction_container.core_x = u.Quantity(np.nan, u.m) reconstruction_container.core_y = u.Quantity(np.nan, u.m) reconstruction_container.core_uncert = np.nan reconstruction_container.h_max = u.Quantity(np.nan, u.m) reconstruction_container.h_max_uncert = np.nan reconstruction_container.is_valid = False reconstruction_container.tel_ids = [] reconstruction_container.average_intensity = np.nan reconstruction_container.goodness_of_fit = np.nan reconstruction_container.prefix = '' calculate_distance_to_core(telescope_event_containers, event, reconstruction_container) mc_container = copy.deepcopy(event.mc) mc_container.tel = None mc_container.prefix = 'mc' counter = Counter(telescope_types) array_event = ArrayEventContainer( array_event_id=event.dl0.event_id, run_id=event.r0.obs_id, reco=reconstruction_container, total_intensity=sum([t.hillas.intensity for t in telescope_event_containers.values()]), num_triggered_lst=counter['LST'], num_triggered_mst=counter['MST'], num_triggered_sst=counter['SST'], num_triggered_telescopes=len(telescope_types), mc=mc_container, ) return array_event, list(telescope_event_containers.values())
def predict(self, hillas_dict, inst, pointing_alt, pointing_az): ''' The function you want to call for the reconstruction of the event. It takes care of setting up the event and consecutively calls the functions for the direction and core position reconstruction. Shower parameters not reconstructed by this class are set to np.nan Parameters ----------- hillas_dict: dict dictionary with telescope IDs as key and HillasParametersContainer instances as values inst : ctapipe.io.InstrumentContainer instrumental description pointing_alt: dict[astropy.coordinates.Angle] dict mapping telescope ids to pointing altitude pointing_az: dict[astropy.coordinates.Angle] dict mapping telescope ids to pointing azimuth Raises ------ TooFewTelescopesException if len(hillas_dict) < 2 ''' # filter warnings for missing obs time. this is needed because MC data has no obs time warnings.filterwarnings(action='ignore', category=MissingFrameAttributeWarning) # stereoscopy needs at least two telescopes if len(hillas_dict) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}".format( len(hillas_dict))) self.initialize_hillas_planes(hillas_dict, inst.subarray, pointing_alt, pointing_az) # algebraic direction estimate direction, err_est_dir = self.estimate_direction() alt = u.Quantity(list(pointing_alt.values())) az = u.Quantity(list(pointing_az.values())) if np.any(alt != alt[0]) or np.any(az != az[0]): warnings.warn('Divergent pointing not supported') telescope_pointing = SkyCoord(alt=alt[0], az=az[0], frame=AltAz()) # core position estimate using a geometric approach core_pos = self.estimate_core_position(hillas_dict, telescope_pointing) # container class for reconstructed showers result = ReconstructedShowerContainer() _, lat, lon = cartesian_to_spherical(*direction) # estimate max height of shower h_max = self.estimate_h_max() # astropy's coordinates system rotates counter-clockwise. # Apparently we assume it to be clockwise. result.alt, result.az = lat, -lon result.core_x = core_pos[0] result.core_y = core_pos[1] result.core_uncert = np.nan result.tel_ids = [h for h in hillas_dict.keys()] result.average_intensity = np.mean( [h.intensity for h in hillas_dict.values()]) result.is_valid = True result.alt_uncert = err_est_dir result.az_uncert = np.nan result.h_max = h_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result
def predict(self, hillas_parameters, tel_x, tel_y, array_direction): """ Parameters ---------- hillas_parameters: dict Dictionary containing Hillas parameters for all telescopes in reconstruction tel_x: dict Dictionary containing telescope position on ground for all telescopes in reconstruction tel_y: dict Dictionary containing telescope position on ground for all telescopes in reconstruction array_direction: HorizonFrame Pointing direction of the array Returns ------- ReconstructedShowerContainer: """ src_x, src_y, err_x, err_y = self.reconstruct_nominal(hillas_parameters) core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted( hillas_parameters, tel_x, tel_y) err_x *= u.rad err_y *= u.rad nom = SkyCoord( x=src_x * u.rad, y=src_y * u.rad, frame=NominalFrame(array_direction=array_direction) ) horiz = nom.transform_to(HorizonFrame()) result = ReconstructedShowerContainer() result.alt, result.az = horiz.alt, horiz.az tilt = SkyCoord( x=core_x * u.m, y=core_y * u.m, frame=TiltedGroundFrame(pointing_direction=array_direction), ) grd = project_to_ground(tilt) result.core_x = grd.x result.core_y = grd.y x_max = self.reconstruct_xmax( nom.x, nom.y, tilt.x, tilt.y, hillas_parameters, tel_x, tel_y, 90 * u.deg - array_direction.alt, ) result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m result.tel_ids = [h for h in hillas_parameters.keys()] result.average_intensity = np.mean([h.intensity for h in hillas_parameters.values()]) result.is_valid = True src_error = np.sqrt(err_x**2 + err_y**2) result.alt_uncert = src_error.to(u.deg) result.az_uncert = src_error.to(u.deg) result.h_max = x_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result
def predict(self, hillas_dict, inst, pointing_alt, pointing_az): ''' The function you want to call for the reconstruction of the event. It takes care of setting up the event and consecutively calls the functions for the direction and core position reconstruction. Shower parameters not reconstructed by this class are set to np.nan Parameters ----------- hillas_dict: dict dictionary with telescope IDs as key and HillasParametersContainer instances as values inst : ctapipe.io.InstrumentContainer instrumental description pointing_alt: dict[astropy.coordinates.Angle] dict mapping telescope ids to pointing altitude pointing_az: dict[astropy.coordinates.Angle] dict mapping telescope ids to pointing azimuth Raises ------ TooFewTelescopesException if len(hillas_dict) < 2 ''' # filter warnings for missing obs time. this is needed because MC data has no obs time warnings.filterwarnings(action='ignore', category=MissingFrameAttributeWarning) # stereoscopy needs at least two telescopes if len(hillas_dict) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}" .format(len(hillas_dict))) self.initialize_hillas_planes( hillas_dict, inst.subarray, pointing_alt, pointing_az ) # algebraic direction estimate direction, err_est_dir = self.estimate_direction() alt = u.Quantity(list(pointing_alt.values())) az = u.Quantity(list(pointing_az.values())) if np.any(alt != alt[0]) or np.any(az != az[0]): warnings.warn('Divergent pointing not supported') telescope_pointing = SkyCoord(alt=alt[0], az=az[0], frame=HorizonFrame()) # core position estimate using a geometric approach core_pos = self.estimate_core_position(hillas_dict, telescope_pointing) # container class for reconstructed showers result = ReconstructedShowerContainer() _, lat, lon = cartesian_to_spherical(*direction) # estimate max height of shower h_max = self.estimate_h_max() # astropy's coordinates system rotates counter-clockwise. # Apparently we assume it to be clockwise. result.alt, result.az = lat, -lon result.core_x = core_pos[0] result.core_y = core_pos[1] result.core_uncert = np.nan result.tel_ids = [h for h in hillas_dict.keys()] result.average_intensity = np.mean([h.intensity for h in hillas_dict.values()]) result.is_valid = True result.alt_uncert = err_est_dir result.az_uncert = np.nan result.h_max = h_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result
def predict(self, hillas_dict, inst, array_pointing, telescopes_pointings=None): """ The function you want to call for the reconstruction of the event. It takes care of setting up the event and consecutively calls the functions for the direction and core position reconstruction. Shower parameters not reconstructed by this class are set to np.nan Parameters ----------- hillas_dict: dict dictionary with telescope IDs as key and HillasParametersContainer instances as values inst : ctapipe.io.InstrumentContainer instrumental description array_pointing: SkyCoord[AltAz] pointing direction of the array telescopes_pointings: dict[SkyCoord[AltAz]] dictionary of pointing direction per each telescope Raises ------ TooFewTelescopesException if len(hillas_dict) < 2 InvalidWidthException if any width is np.nan or 0 """ # filter warnings for missing obs time. this is needed because MC data has no obs time warnings.filterwarnings(action='ignore', category=MissingFrameAttributeWarning) # stereoscopy needs at least two telescopes if len(hillas_dict) < 2: raise TooFewTelescopesException( "need at least two telescopes, have {}".format( len(hillas_dict))) # check for np.nan or 0 width's as these screw up weights if any( [np.isnan(hillas_dict[tel]['width'].value) for tel in hillas_dict]): raise InvalidWidthException( "A HillasContainer contains an ellipse of width==np.nan") if any([hillas_dict[tel]['width'].value == 0 for tel in hillas_dict]): raise InvalidWidthException( "A HillasContainer contains an ellipse of width==0") # use the single telescope pointing also for parallel pointing: code is more general if telescopes_pointings is None: telescopes_pointings = { tel_id: array_pointing for tel_id in hillas_dict.keys() } else: self.divergent_mode = True self.corrected_angle_dict = {} self.initialize_hillas_planes(hillas_dict, inst.subarray, telescopes_pointings, array_pointing) # algebraic direction estimate direction, err_est_dir = self.estimate_direction() # array pointing is needed to define the tilted frame core_pos = self.estimate_core_position(hillas_dict, array_pointing) # container class for reconstructed showers result = ReconstructedShowerContainer() _, lat, lon = cartesian_to_spherical(*direction) # estimate max height of shower h_max = self.estimate_h_max() # astropy's coordinates system rotates counter-clockwise. # Apparently we assume it to be clockwise. result.alt, result.az = lat, -lon result.core_x = core_pos[0] result.core_y = core_pos[1] result.core_uncert = np.nan result.tel_ids = [h for h in hillas_dict.keys()] result.average_intensity = np.mean( [h.intensity for h in hillas_dict.values()]) result.is_valid = True result.alt_uncert = err_est_dir result.az_uncert = np.nan result.h_max = h_max result.h_max_uncert = np.nan result.goodness_of_fit = np.nan return result