def test_cam_to_tel(): from ctapipe.coordinates import CameraFrame, TelescopeFrame # Coordinates in any fram can be given as a numpy array of the xyz positions # e.g. in this case the position on pixels in the camera pix_x = [1] * u.m pix_y = [1] * u.m focal_length = 15 * u.m # first define the camera frame camera_coord = CameraFrame(pix_x, pix_y, focal_length=focal_length) # then use transform to function to convert to a new system # making sure to give the required values for the conversion # (these are not checked yet) telescope_coord = camera_coord.transform_to(TelescopeFrame()) assert telescope_coord.x[0] == (1 / 15) * u.rad # check rotation camera_coord = CameraFrame(pix_x, pix_y, focal_length=focal_length) telescope_coord_rot = camera_coord.transform_to(TelescopeFrame()) assert telescope_coord_rot.y[0] - (1 / 15) * u.rad < 1e-6 * u.rad # The Transform back camera_coord2 = telescope_coord.transform_to( CameraFrame(focal_length=focal_length) ) # Check separation assert camera_coord.separation_3d(camera_coord2)[0] == 0 * u.m
def cam_to_nom(): pix = [np.ones(2048),np.ones(2048),np.zeros(2048)] * u.m camera_coord = CameraFrame(pix,focal_length = 15*u.m) # In this case we bypass the telescope system nom_coord = camera_coord.transform_to(NominalFrame(pointing_direction=[70*u.deg,180*u.deg],array_direction=[75*u.deg,180*u.deg])) alt_az = camera_coord.transform_to(HorizonFrame(pointing_direction=[70*u.deg,180*u.deg],array_direction=[75*u.deg,180*u.deg])) print("Nominal Coordinate",nom_coord)
def test_camera_telescope_transform(): camera_coord = CameraFrame(x=1*u.m, y=2*u.m, z=0*u.m) print(camera_coord) telescope_coord = camera_coord.transform_to(TelescopeFrame) print(telescope_coord) camera_coord2 = telescope_coord.transform_to(CameraFrame) print(camera_coord2)
def test_array_draw(): filename = get_dataset("gamma_test.simtel.gz") cam_geom = {} source = hessio_event_source(filename, max_events=2) r1 = HessioR1Calibrator(None, None) dl0 = CameraDL0Reducer(None, None) calibrator = CameraDL1Calibrator(None, None) for event in source: array_pointing = SkyCoord(event.mcheader.run_array_direction[1] * u.rad, event.mcheader.run_array_direction[0] * u.rad, frame=AltAz) # array_view = ArrayPlotter(instrument=event.inst, # system=TiltedGroundFrame( # pointing_direction=array_pointing)) hillas_dict = {} r1.calibrate(event) dl0.reduce(event) calibrator.calibrate(event) # calibrate the events # store MC pointing direction for the array for tel_id in event.dl0.tels_with_data: pmt_signal = event.dl1.tel[tel_id].image[0] geom = event.inst.subarray.tel[tel_id].camera fl = event.inst.subarray.tel[tel_id].optics.effective_focal_length # Transform the pixels positions into nominal coordinates camera_coord = CameraFrame(x=geom.pix_x, y=geom.pix_y, z=np.zeros(geom.pix_x.shape) * u.m, focal_length=fl, rotation=90 * u.deg - geom.cam_rotation) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=array_pointing, pointing_direction=array_pointing)) mask = tailcuts_clean(geom, pmt_signal, picture_thresh=10., boundary_thresh=5.) try: moments = hillas_parameters(nom_coord.x, nom_coord.y, pmt_signal * mask) hillas_dict[tel_id] = moments nom_coord = NominalPlotter(hillas_parameters=hillas_dict, draw_axes=True) nom_coord.draw_array() except HillasParameterizationError as e: print(e) continue
def cam_to_tel(): # Coordinates in any fram can be given as a numpy array of the xyz positions # e.g. in this case the position on pixels in the camera pix = [np.ones(2048),np.ones(2048),np.zeros(2048)] * u.m # first define the camera frame camera_coord = CameraFrame(pix,focal_length=15*u.m,rotation=0*u.deg) # then use transform to function to convert to a new system # making sure to give the required values for the conversion (these are not checked yet) telescope_coord = camera_coord.transform_to(TelescopeFrame()) # Print coordinates in the new frame print("Telescope Coordinate",telescope_coord) # Transforming back is then easy camera_coord2 = telescope_coord.transform_to(CameraFrame(focal_length=15*u.m,rotation=0*u.deg)) # We can easily check the distance between 2 coordinates in the same frame # In this case they should be the same print("Separation",np.sum(camera_coord.separation_3d(camera_coord2)))
def inititialize_hillas_planes(self, hillas_dict, subarray, pointing_alt, pointing_az): """ creates a dictionary of :class:`.HillasPlane` from a dictionary of hillas parameters Parameters ---------- hillas_dict : dictionary dictionary of hillas moments subarray : ctapipe.instrument.SubarrayDescription subarray information tel_phi, tel_theta : dictionaries dictionaries of the orientation angles of the telescopes needs to contain at least the same keys as in `hillas_dict` """ self.hillas_planes = {} for tel_id, moments in hillas_dict.items(): p2_x = moments.x + 0.1 * u.m * np.cos(moments.psi) p2_y = moments.y + 0.1 * u.m * np.sin(moments.psi) focal_length = subarray.tel[tel_id].optics.equivalent_focal_length pointing = SkyCoord(alt=pointing_alt[tel_id], az=pointing_az[tel_id], frame='altaz') hf = HorizonFrame(array_direction=pointing, pointing_direction=pointing) cf = CameraFrame(focal_length=focal_length, array_direction=pointing, pointing_direction=pointing) cog_coord = SkyCoord(x=moments.x, y=moments.y, frame=cf) cog_coord = cog_coord.transform_to(hf) p2_coord = SkyCoord(x=p2_x, y=p2_y, frame=cf) p2_coord = p2_coord.transform_to(hf) circle = HillasPlane( p1=cog_coord, p2=p2_coord, telescope_position=subarray.positions[tel_id], weight=moments.intensity * (moments.length / moments.width), ) self.hillas_planes[tel_id] = circle
def transform_to_telescope_xy_altaz(self, alt, az, pointing_alt, pointing_az): """ Returns alt, az """ # change azimuth if negative az2 = az.to_value(u.deg) while az2 > 360: az2 -= 360 while az2 < 0: az2 += 360 az = az2*u.deg pointing_coord = SkyCoord(alt=0*u.deg+pointing_alt, az=pointing_az, frame=self.altaz, unit='deg') #pointing = pointing_coord_m.transform_to(self.altaz) camera_frame = CameraFrame( telescope_pointing=pointing_coord, focal_length=self.focal_length, obstime=self.obstime, location=self.magic_location, rotation=(-90+70.9)*u.deg ) #print("A1: ",0*u.deg+alt, " V: ", az, " D: ",self.altaz) event_location = SkyCoord(alt=0*u.deg+alt, az=az, frame=self.altaz, unit='deg') event_loc_cam = event_location.transform_to(camera_frame) x1 = event_loc_cam.x y1 = event_loc_cam.y tf1 = TelescopeFrame(telescope_pointing = pointing_coord, obstime = self.obstime, location = self.magic_location) loc1 = event_location.transform_to(tf1) loc1.delta_az.to_value(u.deg) return x1,y1, loc1.delta_alt, loc1.delta_az
def cam_to_nom(): pix_x = np.ones(2048) * u.m pix_y = np.ones(2048) * u.m pointing_direction = SkyCoord(alt=70 * u.deg, az=180 * u.deg, frame=AltAz()) camera_frame = CameraFrame(focal_length=15 * u.m, telescope_pointing=pointing_direction) camera_coord = SkyCoord(pix_x, pix_y, frame=camera_frame) # In this case we bypass the telescope system nominal_frame = NominalFrame(origin=AltAz(alt=75 * u.deg, az=180 * u.deg)) nom_coord = camera_coord.transform_to(nominal_frame) horizon = camera_coord.transform_to(AltAz()) print("Nominal Coordinate", nom_coord) print("Horizon coordinate", horizon)
def estimate_h_max(self, hillas_dict, subarray, pointing_alt, pointing_az): weights = [] tels = [] dirs = [] for tel_id, moments in hillas_dict.items(): focal_length = subarray.tel[tel_id].optics.equivalent_focal_length pointing = SkyCoord( alt=pointing_alt[tel_id], az=pointing_az[tel_id], frame='altaz' ) hf = HorizonFrame( array_direction=pointing, pointing_direction=pointing ) cf = CameraFrame( focal_length=focal_length, array_direction=pointing, pointing_direction=pointing ) cog_coord = SkyCoord(x=moments.cen_x, y=moments.cen_y, frame=cf) cog_coord = cog_coord.transform_to(hf) cog_direction = spherical_to_cartesian(1, cog_coord.alt, cog_coord.az) cog_direction = np.array(cog_direction).ravel() weights.append(self.hillas_planes[tel_id].weight) tels.append(self.hillas_planes[tel_id].pos) dirs.append(cog_direction) # minimising the test function pos_max = minimize(dist_to_line3d, np.array([0, 0, 10000]), args=(np.array(tels), np.array(dirs), np.array(weights)), method='BFGS', options={'disp': False} ).x return pos_max[2] * u.m
def transform_azel_to_xy(stars_az, stars_alt, az_obs, el_obs): """ function to transform azimuth and elevation coodinates to XY coordinates in the fild of view. :param stars_az: 2D array of azimuth coordinates to be transformed. 1st dimension is for different objects and 2nd dimension is for different observation time. :param stars_alt: 2D array of elevation coordinates to be transformed. 1st dimension is for different objects and 2nd dimension is for different observation time. :param az_obs: 1D array of telescope azimuth pointing direction (1 element per observation time). :param el_obs: 1D array of telescope elevation pointing direction (1 element per observation time). :return: stars_x, stars_y : 2D array giving for each object (1st dimension) and each observation time (2nd dimension) the XY coordinates in the field of view. """ n_stars = stars_az.shape[0] n_event = az_obs.shape[0] assert stars_az.shape == (n_stars, n_event) assert stars_alt.shape == (n_stars, n_event) assert el_obs.shape[0] == n_event stars_x = np.zeros([n_stars, n_event]) * u.mm stars_y = np.zeros([n_stars, n_event]) * u.mm for event in range(n_event): pd = SkyCoord(alt=el_obs[event], az=az_obs[event], frame=HorizonFrame()) cam_frame = CameraFrame( focal_length=5.6 * u.m, rotation=90 * u.deg, pointing_direction=pd, array_direction=pd, ) stars_sky = SkyCoord(alt=stars_alt[:, event], az=stars_az[:, event], frame=HorizonFrame()) stars_cam = stars_sky.transform_to(cam_frame) stars_x[:, event] = -stars_cam.x stars_y[:, event] = stars_cam.y return stars_x, stars_y
def pixel_coords_to_telescope(geom, equivalent_focal_length): """ Get the x, y coordinates of the pixels in the telescope frame Paramenters --------- geom: CameraGeometry equivalent_focal_length: Focal length of the telescope Returns --------- fov_lon, fov_lat: `floats` coordinates in the TelescopeFrame """ camera_coord = SkyCoord( geom.pix_x, geom.pix_y, CameraFrame(focal_length=equivalent_focal_length, rotation=geom.cam_rotation)) tel_coord = camera_coord.transform_to(TelescopeFrame()) return tel_coord.fov_lon, tel_coord.fov_lat
def test_roundtrip_camera_horizon(): from ctapipe.coordinates import CameraFrame, TelescopeFrame telescope_pointing = SkyCoord(alt=70 * u.deg, az=0 * u.deg, frame=AltAz()) camera_frame = CameraFrame(focal_length=28 * u.m, telescope_pointing=telescope_pointing) cam_coord = SkyCoord(x=0.5 * u.m, y=0.1 * u.m, frame=camera_frame) telescope_coord = cam_coord.transform_to(TelescopeFrame()) horizon_coord = telescope_coord.transform_to(AltAz()) back_telescope_coord = horizon_coord.transform_to(TelescopeFrame()) back_cam_coord = back_telescope_coord.transform_to(camera_frame) fov_lon = back_telescope_coord.fov_lon.to_value(u.deg) fov_lat = back_telescope_coord.fov_lat.to_value(u.deg) assert fov_lon == approx(telescope_coord.fov_lon.to_value(u.deg)) assert fov_lat == approx(telescope_coord.fov_lat.to_value(u.deg)) assert back_cam_coord.x.to_value(u.m) == approx(cam_coord.x.to_value(u.m)) assert back_cam_coord.y.to_value(u.m) == approx(cam_coord.y.to_value(u.m))
def calc_dist(df, coord, n_offs, OFF=False): if coord is not None: crab = SkyCoord.from_name(coord) altaz = AltAz( location = EarthLocation.of_site('Roque de los Muchachos'), obstime = Time(df.dragon_time, format='unix') ) telescope_pointing = SkyCoord( alt = u.Quantity(df.alt_tel.to_numpy(), u.rad, copy=False), az = u.Quantity(df.az_tel.to_numpy(), u.rad, copy=False), frame = altaz ) camera_frame = CameraFrame( focal_length = u.Quantity(df.focal_length.to_numpy(), u.m, copy=False), telescope_pointing = telescope_pointing, location = EarthLocation.of_site('Roque de los Muchachos'), obstime = Time(df.dragon_time, format='unix') ) crab_cf = crab.transform_to(camera_frame) if OFF == False: dist = (df.source_x_prediction - crab_cf.x.to_value(u.m))**2 + (df.source_y_prediction - crab_cf.y.to_value(u.m))**2 elif OFF == True and n_offs != 1: r = np.sqrt(crab_cf.x.to_value(u.m)**2 + crab_cf.y.to_value(u.m)**2) phi = np.arctan2(crab_cf.y.to_value(u.m), crab_cf.x.to_value(u.m)) dist = pd.Series() for i in range(1, n_offs + 1): x_off = r * np.cos(phi + i * 2 * np.pi / (n_offs + 1)) y_off = r * np.sin(phi + i * 2 * np.pi / (n_offs + 1)) dist = dist.append( (df.source_x_prediction - x_off)**2 + (df.source_y_prediction - y_off)**2 ) else: dist = df.source_x_prediction**2 + df.source_y_prediction**2 else: dist = df.source_x_prediction**2 + df.source_y_prediction**2 return dist
def camera_to_sky(pos_x, pos_y, focal, pointing_alt, pointing_az): """ Parameters ---------- pos_x: X coordinate in camera (distance) pos_y: Y coordinate in camera (distance) focal: telescope focal (distance) pointing_alt: pointing altitude in angle unit pointing_az: pointing altitude in angle unit Returns ------- sky frame: `astropy.coordinates.sky_coordinate.SkyCoord` Example: -------- import astropy.units as u import numpy as np pos_x = np.array([0, 0]) * u.m pos_y = np.array([0, 0]) * u.m focal = 28*u.m pointing_alt = np.array([1.0, 1.0]) * u.rad pointing_az = np.array([0.2, 0.5]) * u.rad sky_coords = utils.camera_to_sky(pos_x, pos_y, focal, pointing_alt, pointing_az) """ pointing_direction = SkyCoord(alt=clip_alt(pointing_alt), az=pointing_az, frame=horizon_frame) camera_frame = CameraFrame(focal_length=focal, telescope_pointing=pointing_direction) camera_coord = SkyCoord(pos_x, pos_y, frame=camera_frame) horizon = camera_coord.transform_to(horizon_frame) return horizon
def transform_to(self, frame): ''' Transform the pixel coordinates stored in this geometry and the pixel and camera rotations to another camera coordinate frame. Parameters ---------- frame: ctapipe.coordinates.CameraFrame The coordinate frame to transform to. ''' if self.frame is None: self.frame = CameraFrame() coord = SkyCoord(x=self.pix_x, y=self.pix_y, frame=self.frame) trans = coord.transform_to(frame) # also transform the unit vectors, to get rotation / mirroring uv = SkyCoord(x=[1, 0], y=[0, 1], unit=u.m, frame=self.frame) uv_trans = uv.transform_to(frame) rot = np.arctan2(uv_trans[0].y, uv_trans[1].y) det = np.linalg.det([uv_trans.x.value, uv_trans.y.value]) cam_rotation = rot + det * self.cam_rotation pix_rotation = rot + det * self.pix_rotation return CameraGeometry( cam_id=self.cam_id, pix_id=self.pix_id, pix_x=trans.x, pix_y=trans.y, pix_area=self.pix_area, pix_type=self.pix_type, sampling_rate=self.sampling_rate, pix_rotation=pix_rotation, cam_rotation=cam_rotation, neighbors=None, apply_derotation=False, frame=frame, )
def configure(self, config): config[self.cout_pix_pos] = self.pix_pos config[self.cout_fov] = 0.8 # around a pixel # precomputing transformations # Will need to do this smarter (in chunks) when the number of frames reaches 10k-100k self.obstimes = config["time_steps"] self.precomp_hf = HorizonFrame(location=self.location, obstime=self.obstimes) target = SkyCoord(ra=self.par_pointingra.val, dec=self.par_pointingdec.val, unit="deg") self.precomp_point = target.transform_to(self.precomp_hf) config["start_pointing"] = self.precomp_point[0] config[self.cout_altazframes] = self.precomp_hf config["cam_frame0"] = CameraFrame( telescope_pointing=config["start_pointing"], focal_length=u.Quantity(self.focal_length, u.m), # 28 for LST obstime=self.obstimes[0], location=self.location, )
def horizontal_to_camera(alt, az, alt_pointing, az_pointing, focal_length): with warnings.catch_warnings(): warnings.simplefilter("ignore", MissingFrameAttributeWarning) altaz = AltAz() source_altaz = SkyCoord( alt=u.Quantity(alt, u.deg, copy=False), az=u.Quantity(az, u.deg, copy=False), frame=altaz, ) tel_pointing = SkyCoord( alt=u.Quantity(alt_pointing, u.deg, copy=False), az=u.Quantity(az_pointing, u.deg, copy=False), frame=altaz, ) camera_frame = CameraFrame( focal_length=u.Quantity(focal_length, u.m, copy=False), telescope_pointing=tel_pointing, ) cam_coordinates = source_altaz.transform_to(camera_frame) return cam_coordinates.x.to_value(u.m), cam_coordinates.y.to_value(u.m)
def plot2D_runs(runs, names, source, threshold, ax=None): for i, df in enumerate(runs): ax = ax or plt.gca() df_selected = df.query(f'gammaness > {threshold}') crab = SkyCoord.from_name(source) altaz = AltAz( location = EarthLocation.of_site('Roque de los Muchachos'), obstime = Time(df_selected.dragon_time, format='unix') ) telescope_pointing = SkyCoord( alt = u.Quantity(df_selected.alt_tel.to_numpy(), u.rad, copy=False), az = u.Quantity(df_selected.az_tel.to_numpy(), u.rad, copy=False), frame = altaz ) camera_frame = CameraFrame( focal_length = u.Quantity(df_selected.focal_length.to_numpy(), u.m, copy=False), telescope_pointing = telescope_pointing, location = EarthLocation.of_site('Roque de los Muchachos'), obstime = Time(df_selected.dragon_time, format='unix') ) crab_cf = crab.transform_to(camera_frame) crab_tf = crab_cf.transform_to(TelescopeFrame()) ax.scatter( crab_tf.fov_lon.to_value(u.deg), crab_tf.fov_lat.to_value(u.deg), color = f'C{i}', marker = ',', label = names[i] ) ax.legend() ax.set_xlabel(r'fov_lon$ \,/\, \mathrm{deg}$') ax.set_ylabel(r'fov_lat$ \,/\, \mathrm{deg}$') return ax
def transform_to_telescope_camera(self, alt, az, pointing_alt, pointing_az): pointing_coord = SkyCoord(alt=0*u.deg+pointing_alt, az=pointing_az, frame=altaz, unit='deg') #pointing = pointing_coord_m.transform_to(self.altaz) camera_frame_m = CameraFrame( telescope_pointing=pointing_coord, focal_length=self.focal_length, obstime=self.obstime, location=self.magic_location, rotation=0*u.deg ) event_location_m = SkyCoord(alt=0*u.deg+alt, az=az, frame=altaz, unit='deg') event_loc_cam = event_location_m.transform_to(camera_frame_m) return event_loc_cam.x, event_loc_cam.y
def sky_to_camera(alt, az, focal, pointing_alt, pointing_az): """ Coordinate transform from aky position (alt, az) (in angles) to camera coordinates (x, y) in distance Parameters ---------- alt: astropy Quantity az: astropy Quantity focal: astropy Quantity pointing_alt: pointing altitude in angle unit pointing_az: pointing altitude in angle unit Returns ------- camera frame: `astropy.coordinates.sky_coordinate.SkyCoord` """ pointing_direction = SkyCoord(alt=clip_alt(pointing_alt), az=pointing_az, frame=horizon_frame) camera_frame = CameraFrame(focal_length=focal, telescope_pointing=pointing_direction) event_direction = SkyCoord(alt=clip_alt(alt), az=az, frame=horizon_frame) camera_pos = event_direction.transform_to(camera_frame) return camera_pos
def test_cam_to_hor(): from ctapipe.coordinates import CameraFrame # Coordinates in any frame can be given as a numpy array of the xyz positions # e.g. in this case the position on pixels in the camera pix_x = [1] * u.m pix_y = [1] * u.m focal_length = 15000 * u.mm # first define the camera frame pointing = SkyCoord(alt=70 * u.deg, az=0 * u.deg, frame=AltAz()) camera_frame = CameraFrame(focal_length=focal_length, telescope_pointing=pointing) # transform camera_coord = SkyCoord(pix_x, pix_y, frame=camera_frame) altaz_coord = camera_coord.transform_to(AltAz()) # transform back altaz_coord2 = SkyCoord(az=altaz_coord.az, alt=altaz_coord.alt, frame=AltAz()) camera_coord2 = altaz_coord2.transform_to(camera_frame) # check transform assert np.isclose(camera_coord.x.to_value(u.m), camera_coord2.y.to_value(u.m))
def analyze_muon_event(event): """ Generic muon event analyzer. Parameters ---------- event : ctapipe dl1 event container Returns ------- muonringparam, muonintensityparam : MuonRingParameter and MuonIntensityParameter container event """ names = [ 'LST_LST_LSTCam', 'MST_MST_NectarCam', 'MST_MST_FlashCam', 'MST_SCT_SCTCam', 'SST_1M_DigiCam', 'SST_GCT_CHEC', 'SST_ASTRI_ASTRICam', 'SST_ASTRI_CHEC' ] tail_cuts = [(5, 7), (5, 7), (10, 12), (5, 7), (5, 7), (5, 7), (5, 7), (5, 7)] # 10, 12? impact = [(0.2, 0.9), (0.1, 0.95), (0.2, 0.9), (0.2, 0.9), (0.1, 0.95), (0.1, 0.95), (0.1, 0.95), (0.1, 0.95)] * u.m ringwidth = [(0.04, 0.08), (0.02, 0.1), (0.01, 0.1), (0.02, 0.1), (0.01, 0.5), (0.02, 0.2), (0.02, 0.2), (0.02, 0.2)] * u.deg total_pix = [1855., 1855., 1764., 11328., 1296., 2048., 2368., 2048] # 8% (or 6%) as limit min_pix = [148., 148., 141., 680., 104., 164., 142., 164] # Need to either convert from the pixel area in m^2 or check the camera specs ang_pixel_width = [0.1, 0.2, 0.18, 0.067, 0.24, 0.2, 0.17, 0.2, 0.163 ] * u.deg # Found from TDRs (or the pixel area) hole_rad = [ 0.308 * u.m, 0.244 * u.m, 0.244 * u.m, 4.3866 * u.m, 0.160 * u.m, 0.130 * u.m, 0.171 * u.m, 0.171 * u.m ] # Assuming approximately spherical hole cam_rad = [2.26, 3.96, 3.87, 4., 4.45, 2.86, 5.25, 2.86] * u.deg # Above found from the field of view calculation sec_rad = [ 0. * u.m, 0. * u.m, 0. * u.m, 2.7 * u.m, 0. * u.m, 1. * u.m, 1.8 * u.m, 1.8 * u.m ] sct = [False, False, False, True, False, True, True, True] # Added cleaning here. All these options should go to an input card cleaning = True muon_cuts = { 'Name': names, 'tail_cuts': tail_cuts, 'Impact': impact, 'RingWidth': ringwidth, 'total_pix': total_pix, 'min_pix': min_pix, 'CamRad': cam_rad, 'SecRad': sec_rad, 'SCT': sct, 'AngPixW': ang_pixel_width, 'HoleRad': hole_rad } logger.debug(muon_cuts) muonringlist = [] # [None] * len(event.dl0.tels_with_data) muonintensitylist = [] # [None] * len(event.dl0.tels_with_data) tellist = [] muon_event_param = { 'TelIds': tellist, 'MuonRingParams': muonringlist, 'MuonIntensityParams': muonintensitylist } for telid in event.dl0.tels_with_data: logger.debug("Analysing muon event for tel %d", telid) image = event.dl1.tel[telid].image # Get geometry teldes = event.inst.subarray.tel[telid] geom = teldes.camera x, y = geom.pix_x, geom.pix_y dict_index = muon_cuts['Name'].index(str(teldes)) logger.debug('found an index of %d for camera %d', dict_index, geom.cam_id) tailcuts = muon_cuts['tail_cuts'][dict_index] logger.debug("Tailcuts are %s", tailcuts) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) # TODO: correct this hack for values over 90 altval = event.mcheader.run_array_direction[1] if altval > Angle(90, unit=u.deg): warnings.warn('Altitude over 90 degrees') altval = Angle(90, unit=u.deg) telescope_pointing = SkyCoord(alt=altval, az=event.mcheader.run_array_direction[0], frame=AltAz()) camera_coord = SkyCoord( x=x, y=y, frame=CameraFrame( focal_length=teldes.optics.equivalent_focal_length, rotation=geom.pix_rotation, telescope_pointing=telescope_pointing, )) nom_coord = camera_coord.transform_to( NominalFrame(origin=telescope_pointing)) x = nom_coord.delta_az.to(u.deg) y = nom_coord.delta_alt.to(u.deg) if (cleaning): img = image * clean_mask else: img = image muonring = ChaudhuriKunduRingFitter(None) logger.debug("img: %s mask: %s, x=%s y= %s", np.sum(image), np.sum(clean_mask), x, y) if not sum(img): # Nothing left after tail cuts continue muonringparam = muonring.fit(x, y, image * clean_mask) dist = np.sqrt( np.power(x - muonringparam.ring_center_x, 2) + np.power(y - muonringparam.ring_center_y, 2)) ring_dist = np.abs(dist - muonringparam.ring_radius) muonringparam = muonring.fit( x, y, img * (ring_dist < muonringparam.ring_radius * 0.4)) dist = np.sqrt( np.power(x - muonringparam.ring_center_x, 2) + np.power(y - muonringparam.ring_center_y, 2)) ring_dist = np.abs(dist - muonringparam.ring_radius) muonringparam = muonring.fit( x, y, img * (ring_dist < muonringparam.ring_radius * 0.4)) muonringparam.tel_id = telid muonringparam.obs_id = event.dl0.obs_id muonringparam.event_id = event.dl0.event_id dist_mask = np.abs( dist - muonringparam.ring_radius) < muonringparam.ring_radius * 0.4 pix_im = image * dist_mask nom_dist = np.sqrt( np.power(muonringparam.ring_center_x, 2) + np.power(muonringparam.ring_center_y, 2)) minpix = muon_cuts['min_pix'][dict_index] # 0.06*numpix #or 8% mir_rad = np.sqrt(teldes.optics.mirror_area.to("m2") / np.pi) # Camera containment radius - better than nothing - guess pixel # diameter of 0.11, all cameras are perfectly circular cam_rad = # np.sqrt(numpix*0.11/(2.*np.pi)) if (npix_above_threshold(pix_im, tailcuts[0]) > 0.1 * minpix and npix_composing_ring(pix_im) > minpix and nom_dist < muon_cuts['CamRad'][dict_index] and muonringparam.ring_radius < 1.5 * u.deg and muonringparam.ring_radius > 1. * u.deg): muonringparam.ring_containment = ring_containment( muonringparam.ring_radius, muon_cuts['CamRad'][dict_index], muonringparam.ring_center_x, muonringparam.ring_center_y) # Guess HESS is 0.16 # sec_rad = 0.*u.m # sct = False # if numpix == 2048 and mir_rad > 2.*u.m and mir_rad < 2.1*u.m: # sec_rad = 1.*u.m # sct = True # # Store muon ring parameters (passing cuts stage 1) # muonringlist[idx] = muonringparam tellist.append(telid) muonringlist.append(muonringparam) muonintensitylist.append(None) ctel = MuonLineIntegrate( mir_rad, hole_radius=muon_cuts['HoleRad'][dict_index], pixel_width=muon_cuts['AngPixW'][dict_index], sct_flag=muon_cuts['SCT'][dict_index], secondary_radius=muon_cuts['SecRad'][dict_index]) if image.shape[0] == muon_cuts['total_pix'][dict_index]: muonintensityoutput = ctel.fit_muon( muonringparam.ring_center_x, muonringparam.ring_center_y, muonringparam.ring_radius, x[dist_mask], y[dist_mask], image[dist_mask]) muonintensityoutput.tel_id = telid muonintensityoutput.obs_id = event.dl0.obs_id muonintensityoutput.event_id = event.dl0.event_id muonintensityoutput.mask = dist_mask idx_ring = np.nonzero(pix_im) muonintensityoutput.ring_completeness = ring_completeness( x[idx_ring], y[idx_ring], pix_im[idx_ring], muonringparam.ring_radius, muonringparam.ring_center_x, muonringparam.ring_center_y, threshold=30, bins=30) muonintensityoutput.ring_size = np.sum(pix_im) dist_ringwidth_mask = np.abs( dist - muonringparam.ring_radius) < ( muonintensityoutput.ring_width) pix_ringwidth_im = image * dist_ringwidth_mask idx_ringwidth = np.nonzero(pix_ringwidth_im) muonintensityoutput.ring_pix_completeness = npix_above_threshold( pix_ringwidth_im[idx_ringwidth], tailcuts[0]) / len( pix_im[idx_ringwidth]) logger.debug( "Tel %d Impact parameter = %s mir_rad=%s " "ring_width=%s", telid, muonintensityoutput.impact_parameter, mir_rad, muonintensityoutput.ring_width) conditions = [ muonintensityoutput.impact_parameter * u.m < muon_cuts['Impact'][dict_index][1] * mir_rad, muonintensityoutput.impact_parameter > muon_cuts['Impact'][dict_index][0], muonintensityoutput.ring_width < muon_cuts['RingWidth'][dict_index][1], muonintensityoutput.ring_width > muon_cuts['RingWidth'][dict_index][0] ] if all(conditions): muonintensityparam = muonintensityoutput idx = tellist.index(telid) muonintensitylist[idx] = muonintensityparam logger.debug("Muon found in tel %d, tels in event=%d", telid, len(event.dl0.tels_with_data)) else: continue return muon_event_param
def plot_muon_event(event, muonparams, args=None): if muonparams['MuonRingParams'] is not None: # Plot the muon event and overlay muon parameters fig = plt.figure(figsize=(16, 7)) colorbar = None colorbar2 = None #for tel_id in event.dl0.tels_with_data: for tel_id in muonparams['TelIds']: idx = muonparams['TelIds'].index(tel_id) if not muonparams['MuonRingParams'][idx]: continue #otherwise... npads = 2 # Only create two pads if there is timing information extracted # from the calibration ax1 = fig.add_subplot(1, npads, 1) plotter = CameraPlotter(event) image = event.dl1.tel[tel_id].image[0] geom = event.inst.subarray.tel[tel_id].camera tailcuts = (5., 7.) # Try a higher threshold for if geom.cam_id == 'FlashCam': tailcuts = (10., 12.) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) signals = image * clean_mask #print("Ring Centre in Nominal Coords:",muonparams[0].ring_center_x,muonparams[0].ring_center_y) muon_incl = np.sqrt( muonparams['MuonRingParams'][idx].ring_center_x**2. + muonparams['MuonRingParams'][idx].ring_center_y**2.) muon_phi = np.arctan( muonparams['MuonRingParams'][idx].ring_center_y / muonparams['MuonRingParams'][idx].ring_center_x) rotr_angle = geom.pix_rotation # if event.inst.optical_foclen[tel_id] > 10.*u.m and # event.dl0.tel[tel_id].num_pixels != 1764: if geom.cam_id == 'LSTCam' or geom.cam_id == 'NectarCam': #print("Resetting the rotation angle") rotr_angle = 0. * u.deg # Convert to camera frame (centre & radius) altaz = HorizonFrame(alt=event.mc.alt, az=event.mc.az) ring_nominal = NominalFrame( x=muonparams['MuonRingParams'][idx].ring_center_x, y=muonparams['MuonRingParams'][idx].ring_center_y, array_direction=altaz, pointing_direction=altaz) # embed() ring_camcoord = ring_nominal.transform_to( CameraFrame(pointing_direction=altaz, focal_length=event.inst.optical_foclen[tel_id], rotation=rotr_angle)) centroid_rad = np.sqrt(ring_camcoord.y**2 + ring_camcoord.x**2) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) ringrad_camcoord = muonparams['MuonRingParams'][idx].ring_radius.to(u.rad) \ * event.inst.optical_foclen[tel_id] * 2. # But not FC? px, py = event.inst.pixel_pos[tel_id] flen = event.inst.optical_foclen[tel_id] camera_coord = CameraFrame(x=px, y=py, focal_length=flen, rotation=geom.pix_rotation) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=altaz, pointing_direction=altaz)) px = nom_coord.x.to(u.deg) py = nom_coord.y.to(u.deg) dist = np.sqrt( np.power(px - muonparams['MuonRingParams'][idx].ring_center_x, 2) + np.power(py - muonparams['MuonRingParams'][idx].ring_center_y, 2)) ring_dist = np.abs(dist - muonparams['MuonRingParams'][idx].ring_radius) pixRmask = ring_dist < muonparams['MuonRingParams'][ idx].ring_radius * 0.4 #if muonparams[1] is not None: if muonparams['MuonIntensityParams'][idx] is not None: signals *= muonparams['MuonIntensityParams'][idx].mask camera1 = plotter.draw_camera(tel_id, signals, ax1) cmaxmin = (max(signals) - min(signals)) cmin = min(signals) if not cmin: cmin = 1. if not cmaxmin: cmaxmin = 1. cmap_charge = colors.LinearSegmentedColormap.from_list( 'cmap_c', [(0 / cmaxmin, 'darkblue'), (np.abs(cmin) / cmaxmin, 'black'), (2.0 * np.abs(cmin) / cmaxmin, 'blue'), (2.5 * np.abs(cmin) / cmaxmin, 'green'), (1, 'yellow')]) camera1.pixels.set_cmap(cmap_charge) if not colorbar: camera1.add_colorbar(ax=ax1, label=" [photo-electrons]") colorbar = camera1.colorbar else: camera1.colorbar = colorbar camera1.update(True) camera1.add_ellipse(centroid, ringrad_camcoord.value, ringrad_camcoord.value, 0., 0., color="red") if muonparams['MuonIntensityParams'][idx] is not None: # continue #Comment this...(should ringwidthfrac also be *0.5?) ringwidthfrac = muonparams['MuonIntensityParams'][ idx].ring_width / muonparams['MuonRingParams'][ idx].ring_radius ringrad_inner = ringrad_camcoord * (1. - ringwidthfrac) ringrad_outer = ringrad_camcoord * (1. + ringwidthfrac) camera1.add_ellipse(centroid, ringrad_inner.value, ringrad_inner.value, 0., 0., color="magenta") camera1.add_ellipse(centroid, ringrad_outer.value, ringrad_outer.value, 0., 0., color="magenta") npads = 2 ax2 = fig.add_subplot(1, npads, npads) pred = muonparams['MuonIntensityParams'][idx].prediction if len(pred) != np.sum( muonparams['MuonIntensityParams'][idx].mask): print("Warning! Lengths do not match...len(pred)=", len(pred), "len(mask)=", np.sum(muonparams['MuonIntensityParams'][idx].mask)) # Numpy broadcasting - fill in the shape plotpred = np.zeros(image.shape) plotpred[muonparams['MuonIntensityParams'][idx].mask == True] = pred camera2 = plotter.draw_camera(tel_id, plotpred, ax2) if np.isnan(max(plotpred)) or np.isnan(min(plotpred)): print("nan prediction, skipping...") continue c2maxmin = (max(plotpred) - min(plotpred)) if not c2maxmin: c2maxmin = 1. c2map_charge = colors.LinearSegmentedColormap.from_list( 'c2map_c', [(0 / c2maxmin, 'darkblue'), (np.abs(min(plotpred)) / c2maxmin, 'black'), (2.0 * np.abs(min(plotpred)) / c2maxmin, 'blue'), (2.5 * np.abs(min(plotpred)) / c2maxmin, 'green'), (1, 'yellow')]) camera2.pixels.set_cmap(c2map_charge) if not colorbar2: camera2.add_colorbar(ax=ax2, label=" [photo-electrons]") colorbar2 = camera2.colorbar else: camera2.colorbar = colorbar2 camera2.update(True) plt.pause(1.) # make shorter # plt.pause(0.1) # if pp is not None: # pp.savefig(fig) #fig.savefig(str(args.output_path) + "_" + # str(event.dl0.event_id) + '.png') plt.close()
# boundary_thresh=7) clean_mask = tailcuts_clean(geom, image, 1, picture_thresh=1, boundary_thresh=2) pixwidth = np.sqrt(geom.pix_area) angpixwidth = pixwidth / ( teloptconfigdict['focallengths'][dictindex]) * ( 180. / np.pi) * (u.deg / u.m) # This is not correct... # print('Area of the pixel is:',geom.pix_area,'approx width as', # pixwidth,angpixwidth) # 1/0 camera_coord = CameraFrame(x=x, y=y, z=np.zeros(x.shape) * u.m) optics = event.inst.subarray.tel[tel_id].optics foclen = optics.equivalent_focal_length frame = NominalFrame( array_direction=[container.mc.alt, container.mc.az], pointing_direction=[container.mc.alt, container.mc.az], focal_length=foclen) nom_coord = camera_coord.transform_to(frame) x = nom_coord.x.to(u.deg) y = nom_coord.y.to(u.deg) img = image * clean_mask noise = 5 weight = img / (img + noise)
# CUTS also need to change for each telescope type.... # clean_mask = tailcuts_clean(geom,image,1,picture_thresh=5, # boundary_thresh=7) clean_mask = tailcuts_clean(geom, image, 1, picture_thresh=1, boundary_thresh=2) pixwidth = np.sqrt(geom.pix_area) angpixwidth = pixwidth / (teloptconfigdict['focallengths'][ dictindex]) * (180. / np.pi) * ( u.deg / u.m) # This is not correct... # print('Area of the pixel is:',geom.pix_area,'approx width as', # pixwidth,angpixwidth) # 1/0 camera_coord = CameraFrame(x=x, y=y, z=np.zeros(x.shape) * u.m) optics = event.inst.subarray.tel[tel_id].optics foclen = optics.effective_focal_length frame = NominalFrame(array_direction=[container.mc.alt, container.mc.az], pointing_direction=[container.mc.alt, container.mc.az], focal_length=foclen) nom_coord = camera_coord.transform_to(frame) x = nom_coord.x.to(u.deg) y = nom_coord.y.to(u.deg) img = image * clean_mask noise = 5
def plot_muon_event(event, muonparams, geom_dict=None, args=None): if muonparams[0] is not None: # Plot the muon event and overlay muon parameters fig = plt.figure(figsize=(16, 7)) # if args.display: # plt.show(block=False) #pp = PdfPages(args.output_path) if args.output_path is not None else None # pp = None #For now, need to correct this colorbar = None colorbar2 = None for tel_id in event.dl0.tels_with_data: npads = 2 # Only create two pads if there is timing information extracted # from the calibration ax1 = fig.add_subplot(1, npads, 1) plotter = CameraPlotter(event, geom_dict) #image = event.dl1.tel[tel_id].calibrated_image image = event.dl1.tel[tel_id].image[0] # Get geometry geom = None if geom_dict is not None and tel_id in geom_dict: geom = geom_dict[tel_id] else: #log.debug("[calib] Guessing camera geometry") geom = CameraGeometry.guess(*event.inst.pixel_pos[tel_id], event.inst.optical_foclen[tel_id]) #log.debug("[calib] Camera geometry found") if geom_dict is not None: geom_dict[tel_id] = geom tailcuts = (5., 7.) # Try a higher threshold for if geom.cam_id == 'FlashCam': tailcuts = (10., 12.) #print("Using Tail Cuts:",tailcuts) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) signals = image * clean_mask #print("Ring Centre in Nominal Coords:",muonparams[0].ring_center_x,muonparams[0].ring_center_y) muon_incl = np.sqrt(muonparams[0].ring_center_x**2. + muonparams[0].ring_center_y**2.) muon_phi = np.arctan(muonparams[0].ring_center_y / muonparams[0].ring_center_x) rotr_angle = geom.pix_rotation # if event.inst.optical_foclen[tel_id] > 10.*u.m and # event.dl0.tel[tel_id].num_pixels != 1764: if geom.cam_id == 'LSTCam' or geom.cam_id == 'NectarCam': #print("Resetting the rotation angle") rotr_angle = 0. * u.deg # Convert to camera frame (centre & radius) altaz = HorizonFrame(alt=event.mc.alt, az=event.mc.az) ring_nominal = NominalFrame(x=muonparams[0].ring_center_x, y=muonparams[0].ring_center_y, array_direction=altaz, pointing_direction=altaz) # embed() ring_camcoord = ring_nominal.transform_to(CameraFrame( pointing_direction=altaz, focal_length=event.inst.optical_foclen[tel_id], rotation=rotr_angle)) centroid_rad = np.sqrt(ring_camcoord.y**2 + ring_camcoord.x**2) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) ringrad_camcoord = muonparams[0].ring_radius.to(u.rad) \ * event.inst.optical_foclen[tel_id] * 2. # But not FC? #rot_angle = 0.*u.deg # if event.inst.optical_foclen[tel_id] > 10.*u.m and event.dl0.tel[tel_id].num_pixels != 1764: #rot_angle = -100.14*u.deg px, py = event.inst.pixel_pos[tel_id] flen = event.inst.optical_foclen[tel_id] camera_coord = CameraFrame(x=px, y=py, z=np.zeros(px.shape) * u.m, focal_length=flen, rotation=geom.pix_rotation) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=altaz, pointing_direction=altaz) ) #,focal_length = event.inst.optical_foclen[tel_id])) # tel['TelescopeTable_VersionFeb2016'][tel['TelescopeTable_VersionFeb2016']['TelID']==telid]['FL'][0]*u.m)) px = nom_coord.x.to(u.deg) py = nom_coord.y.to(u.deg) dist = np.sqrt(np.power( px - muonparams[0].ring_center_x, 2) + np.power(py - muonparams[0].ring_center_y, 2)) ring_dist = np.abs(dist - muonparams[0].ring_radius) pixRmask = ring_dist < muonparams[0].ring_radius * 0.4 if muonparams[1] is not None: signals *= muonparams[1].mask camera1 = plotter.draw_camera(tel_id, signals, ax1) cmaxmin = (max(signals) - min(signals)) if not cmaxmin: cmaxmin = 1. cmap_charge = colors.LinearSegmentedColormap.from_list( 'cmap_c', [(0 / cmaxmin, 'darkblue'), (np.abs(min(signals)) / cmaxmin, 'black'), (2.0 * np.abs(min(signals)) / cmaxmin, 'blue'), (2.5 * np.abs(min(signals)) / cmaxmin, 'green'), (1, 'yellow')] ) camera1.pixels.set_cmap(cmap_charge) if not colorbar: camera1.add_colorbar(ax=ax1, label=" [photo-electrons]") colorbar = camera1.colorbar else: camera1.colorbar = colorbar camera1.update(True) camera1.add_ellipse(centroid, ringrad_camcoord.value, ringrad_camcoord.value, 0., 0., color="red") # ax1.set_title("CT {} ({}) - Mean pixel charge" # .format(tel_id, geom_dict[tel_id].cam_id)) if muonparams[1] is not None: # continue #Comment this...(should ringwidthfrac also be *0.5?) ringwidthfrac = muonparams[ 1].ring_width / muonparams[0].ring_radius ringrad_inner = ringrad_camcoord * (1. - ringwidthfrac) ringrad_outer = ringrad_camcoord * (1. + ringwidthfrac) camera1.add_ellipse(centroid, ringrad_inner.value, ringrad_inner.value, 0., 0., color="magenta") camera1.add_ellipse(centroid, ringrad_outer.value, ringrad_outer.value, 0., 0., color="magenta") npads = 2 ax2 = fig.add_subplot(1, npads, npads) pred = muonparams[1].prediction if len(pred) != np.sum(muonparams[1].mask): print("Warning! Lengths do not match...len(pred)=", len(pred), "len(mask)=", np.sum(muonparams[1].mask)) # Numpy broadcasting - fill in the shape plotpred = np.zeros(image.shape) plotpred[muonparams[1].mask == True] = pred camera2 = plotter.draw_camera(tel_id, plotpred, ax2) c2maxmin = (max(plotpred) - min(plotpred)) if not c2maxmin: c2maxmin = 1. c2map_charge = colors.LinearSegmentedColormap.from_list( 'c2map_c', [(0 / c2maxmin, 'darkblue'), (np.abs(min(plotpred)) / c2maxmin, 'black'), (2.0 * np.abs(min(plotpred)) / c2maxmin, 'blue'), (2.5 * np.abs(min(plotpred)) / c2maxmin, 'green'), (1, 'yellow')] ) camera2.pixels.set_cmap(c2map_charge) if not colorbar2: camera2.add_colorbar(ax=ax2, label=" [photo-electrons]") colorbar2 = camera2.colorbar else: camera2.colorbar = colorbar2 camera2.update(True) plt.pause(1.) # make shorter # plt.pause(0.1) # if pp is not None: # pp.savefig(fig) fig.savefig(str(args.output_path) + "_" + str(event.dl0.event_id) + '.png') plt.close()
def analyze_muon_event(event, params=None, geom_dict=None): """ Generic muon event analyzer. Parameters ---------- event : ctapipe dl1 event container Returns ------- muonringparam, muonintensityparam : MuonRingParameter and MuonIntensityParameter container event """ # Declare a dict to define the muon cuts (ASTRI and SCT missing) muon_cuts = {} names = ['LST:LSTCam','MST:NectarCam','MST:FlashCam','MST-SCT:SCTCam','SST-1M:DigiCam','SST-GCT:CHEC','SST-ASTRI:ASTRICam'] TailCuts = [(5,7),(5,7),(10,12),(5,7),(5,7),(5,7),(5,7)] #10,12? impact = [(0.2,0.9),(0.1,0.95),(0.2,0.9),(0.2,0.9),(0.1,0.95),(0.1,0.95),(0.1,0.95)] ringwidth = [(0.04,0.08),(0.02,0.1),(0.01,0.1),(0.02,0.1),(0.01,0.5),(0.02,0.2),(0.02,0.2)] TotalPix = [1855.,1855.,1764.,11328.,1296.,2048.,2368.]#8% (or 6%) as limit MinPix = [148.,148.,141.,680.,104.,164.,142.] #Need to either convert from the pixel area in m^2 or check the camera specs AngPixelWidth = [0.1,0.2,0.18,0.067,0.24,0.2,0.17] #Found from TDRs (or the pixel area) hole_rad = []#Need to check and implement cam_rad = [2.26,3.96,3.87,4.,4.45,2.86,5.25]#Found from the field of view calculation sec_rad = [0.*u.m,0.*u.m,0.*u.m,2.7*u.m,0.*u.m,1.*u.m,1.8*u.m] sct = [False,False,False,True,False,True,True] muon_cuts = {'Name':names,'TailCuts':TailCuts,'Impact':impact,'RingWidth':ringwidth,'TotalPix':TotalPix,'MinPix':MinPix,'CamRad':cam_rad,'SecRad':sec_rad,'SCT':sct,'AngPixW':AngPixelWidth} #print(muon_cuts) muonringlist = []#[None] * len(event.dl0.tels_with_data) muonintensitylist = []#[None] * len(event.dl0.tels_with_data) tellist = [] #for tid in event.dl0.tels_with_data: # tellist.append(tid) muon_event_param = {'TelIds':tellist,'MuonRingParams':muonringlist,'MuonIntensityParams':muonintensitylist} #muonringparam = None #muonintensityparam = None for telid in event.dl0.tels_with_data: #print("Analysing muon event for tel",telid) muonringparam = None muonintensityparam = None #idx = muon_event_param['TelIds'].index(telid) x, y = event.inst.pixel_pos[telid] #image = event.dl1.tel[telid].calibrated_image image = event.dl1.tel[telid].image[0] # Get geometry geom = None if geom_dict is not None and telid in geom_dict: geom = geom_dict[telid] else: log.debug("[calib] Guessing camera geometry") geom = CameraGeometry.guess(*event.inst.pixel_pos[telid], event.inst.optical_foclen[telid]) log.debug("[calib] Camera geometry found") if geom_dict is not None: geom_dict[telid] = geom teldes = event.inst.subarray.tel[telid] dict_index = muon_cuts['Name'].index(str(teldes)) #print('found an index of',dict_index,'for camera',geom.cam_id) #tailcuts = (5.,7.) tailcuts = muon_cuts['TailCuts'][dict_index] #print("Tailcuts are",tailcuts[0],tailcuts[1]) #rot_angle = 0.*u.deg #if event.inst.optical_foclen[telid] > 10.*u.m and event.dl0.tel[telid].num_pixels != 1764: #rot_angle = -100.14*u.deg clean_mask = tailcuts_clean(geom,image,picture_thresh=tailcuts[0],boundary_thresh=tailcuts[1]) camera_coord = CameraFrame(x=x,y=y,z=np.zeros(x.shape)*u.m, focal_length = event.inst.optical_foclen[telid], rotation=geom.pix_rotation) #print("Camera",geom.cam_id,"focal length",event.inst.optical_foclen[telid],"rotation",geom.pix_rotation) #TODO: correct this hack for values over 90 altval = event.mcheader.run_array_direction[1] if (altval > np.pi/2.): altval = np.pi/2. altaz = HorizonFrame(alt=altval*u.rad,az=event.mcheader.run_array_direction[0]*u.rad) nom_coord = camera_coord.transform_to(NominalFrame(array_direction=altaz,pointing_direction=altaz)) x = nom_coord.x.to(u.deg) y = nom_coord.y.to(u.deg) img = image*clean_mask noise = 5. weight = img / (img+noise) muonring = ChaudhuriKunduRingFitter(None) #print("img:",np.sum(image),"mask:",np.sum(clean_mask), "x=",x,"y=",y) if not sum(img):#Nothing left after tail cuts continue muonringparam = muonring.fit(x,y,image*clean_mask) #muonringparam = muonring.fit(x,y,weight) dist = np.sqrt(np.power(x-muonringparam.ring_center_x,2) + np.power(y-muonringparam.ring_center_y,2)) ring_dist = np.abs(dist-muonringparam.ring_radius) muonringparam = muonring.fit(x,y,img*(ring_dist<muonringparam.ring_radius*0.4)) dist = np.sqrt(np.power(x-muonringparam.ring_center_x,2) + np.power(y-muonringparam.ring_center_y,2)) ring_dist = np.abs(dist-muonringparam.ring_radius) #print("1: x",muonringparam.ring_center_x,"y",muonringparam.ring_center_y,"radius",muonringparam.ring_radius) muonringparam = muonring.fit(x,y,img*(ring_dist<muonringparam.ring_radius*0.4)) #print("2: x",muonringparam.ring_center_x,"y",muonringparam.ring_center_y,"radius",muonringparam.ring_radius) muonringparam.tel_id = telid muonringparam.run_id = event.dl0.run_id muonringparam.event_id = event.dl0.event_id dist_mask = np.abs(dist-muonringparam.ring_radius)<muonringparam.ring_radius*0.4 rad = list() cx = list() cy = list() mc_x = event.mc.core_x mc_y = event.mc.core_y pix_im = image*dist_mask nom_dist = np.sqrt(np.power(muonringparam.ring_center_x,2)+np.power(muonringparam.ring_center_y,2)) #numpix = event.dl0.tel[telid].num_pixels minpix = muon_cuts['MinPix'][dict_index]#0.06*numpix #or 8% mir_rad = np.sqrt(event.inst.mirror_dish_area[telid]/(np.pi))#need to consider units? (what about hole? Area is then less...) #Camera containment radius - better than nothing - guess pixel diameter of 0.11, all cameras are perfectly circular cam_rad = np.sqrt(numpix*0.11/(2.*np.pi)) if(np.sum(pix_im>tailcuts[0])>0.1*minpix and np.sum(pix_im)>minpix and nom_dist < muon_cuts['CamRad'][dict_index]*u.deg and muonringparam.ring_radius<1.5*u.deg and muonringparam.ring_radius>1.*u.deg): #Guess HESS is 0.16 #sec_rad = 0.*u.m #sct = False #if numpix == 2048 and mir_rad > 2.*u.m and mir_rad < 2.1*u.m: # sec_rad = 1.*u.m # sct = True #Store muon ring parameters (passing cuts stage 1) #muonringlist[idx] = muonringparam tellist.append(telid) muonringlist.append(muonringparam) muonintensitylist.append(None) #embed() ctel = MuonLineIntegrate(mir_rad,0.2*u.m,pixel_width=muon_cuts['AngPixW'][dict_index]*u.deg,sct_flag=muon_cuts['SCT'][dict_index], secondary_radius=muon_cuts['SecRad'][dict_index]) if (image.shape[0] == muon_cuts['TotalPix'][dict_index]): muonintensityoutput = ctel.fit_muon(muonringparam.ring_center_x,muonringparam.ring_center_y,muonringparam.ring_radius,x[dist_mask],y[dist_mask],image[dist_mask]) muonintensityoutput.tel_id = telid muonintensityoutput.run_id = event.dl0.run_id muonintensityoutput.event_id = event.dl0.event_id muonintensityoutput.mask = dist_mask print("Tel",telid,"Impact parameter = ",muonintensityoutput.impact_parameter,"mir_rad",mir_rad,"ring_width=",muonintensityoutput.ring_width) #if(muonintensityoutput.impact_parameter > muon_cuts['Impact'][dict_index][1]*mir_rad or muonintensityoutput.impact_parameter < muon_cuts['Impact'][dict_index][0]*mir_rad): # print("Failed on impact parameter low cut",muon_cuts['Impact'][dict_index][0]*mir_rad,"high cut",muon_cuts['Impact'][dict_index][1]*mir_rad,"for",geom.cam_id) #if(muonintensityoutput.ring_width > muon_cuts['RingWidth'][dict_index][1]*u.deg or muonintensityoutput.ring_width < muon_cuts['RingWidth'][dict_index][0]*u.deg): # print("Failed on ring width low cut",muon_cuts['RingWidth'][dict_index][0]*u.deg,"high cut",muon_cuts['RingWidth'][dict_index][1]*u.deg,"for ",geom.cam_id) #print("Cuts <",muon_cuts["Impact"][dict_index][1]*mir_rad,"cuts >",muon_cuts['Impact'][dict_index][0]*u.m,'ring_width < ',muon_cuts['RingWidth'][dict_index][1]*u.deg,'ring_width >',muon_cuts['RingWidth'][dict_index][0]*u.deg) if( muonintensityoutput.impact_parameter < muon_cuts['Impact'][dict_index][1]*mir_rad and muonintensityoutput.impact_parameter > muon_cuts['Impact'][dict_index][0]*u.m and muonintensityoutput.ring_width < muon_cuts['RingWidth'][dict_index][1]*u.deg and muonintensityoutput.ring_width > muon_cuts['RingWidth'][dict_index][0]*u.deg ): muonintensityparam = muonintensityoutput idx = tellist.index(telid) muonintensitylist[idx] = muonintensityparam print("Muon in tel",telid,"# tels in event=",len(event.dl0.tels_with_data)) else: continue #print("Fitted ring centre (2):",muonringparam.ring_center_x,muonringparam.ring_center_y) #return muonringparam, muonintensityparam return muon_event_param
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 analyze_muon_event(event, params=None, geom_dict=None): """ Generic muon event analyzer. Parameters ---------- event : ctapipe dl1 event container Returns ------- muonringparam, muonintensityparam : MuonRingParameter and MuonIntensityParameter container event """ # Declare a dict to define the muon cuts (ASTRI and SCT missing) muon_cuts = {} names = [ 'LST:LSTCam', 'MST:NectarCam', 'MST:FlashCam', 'MST-SCT:SCTCam', 'SST-1M:DigiCam', 'SST-GCT:CHEC', 'SST-ASTRI:ASTRICam' ] TailCuts = [(5, 7), (5, 7), (10, 12), (5, 7), (5, 7), (5, 7), (5, 7)] #10,12? impact = [(0.2, 0.9), (0.1, 0.95), (0.2, 0.9), (0.2, 0.9), (0.1, 0.95), (0.1, 0.95), (0.1, 0.95)] ringwidth = [(0.04, 0.08), (0.02, 0.1), (0.01, 0.1), (0.02, 0.1), (0.01, 0.5), (0.02, 0.2), (0.02, 0.2)] TotalPix = [1855., 1855., 1764., 11328., 1296., 2048., 2368.] #8% (or 6%) as limit MinPix = [148., 148., 141., 680., 104., 164., 142.] #Need to either convert from the pixel area in m^2 or check the camera specs AngPixelWidth = [0.1, 0.2, 0.18, 0.067, 0.24, 0.2, 0.17] #Found from TDRs (or the pixel area) hole_rad = [] #Need to check and implement cam_rad = [2.26, 3.96, 3.87, 4., 4.45, 2.86, 5.25] #Found from the field of view calculation sec_rad = [ 0. * u.m, 0. * u.m, 0. * u.m, 2.7 * u.m, 0. * u.m, 1. * u.m, 1.8 * u.m ] sct = [False, False, False, True, False, True, True] muon_cuts = { 'Name': names, 'TailCuts': TailCuts, 'Impact': impact, 'RingWidth': ringwidth, 'TotalPix': TotalPix, 'MinPix': MinPix, 'CamRad': cam_rad, 'SecRad': sec_rad, 'SCT': sct, 'AngPixW': AngPixelWidth } #print(muon_cuts) muonringlist = [] #[None] * len(event.dl0.tels_with_data) muonintensitylist = [] #[None] * len(event.dl0.tels_with_data) tellist = [] #for tid in event.dl0.tels_with_data: # tellist.append(tid) muon_event_param = { 'TelIds': tellist, 'MuonRingParams': muonringlist, 'MuonIntensityParams': muonintensitylist } #muonringparam = None #muonintensityparam = None for telid in event.dl0.tels_with_data: #print("Analysing muon event for tel",telid) muonringparam = None muonintensityparam = None #idx = muon_event_param['TelIds'].index(telid) x, y = event.inst.pixel_pos[telid] #image = event.dl1.tel[telid].calibrated_image image = event.dl1.tel[telid].image[0] # Get geometry geom = None if geom_dict is not None and telid in geom_dict: geom = geom_dict[telid] else: log.debug("[calib] Guessing camera geometry") geom = CameraGeometry.guess(*event.inst.pixel_pos[telid], event.inst.optical_foclen[telid]) log.debug("[calib] Camera geometry found") if geom_dict is not None: geom_dict[telid] = geom teldes = event.inst.subarray.tel[telid] dict_index = muon_cuts['Name'].index(str(teldes)) #print('found an index of',dict_index,'for camera',geom.cam_id) #tailcuts = (5.,7.) tailcuts = muon_cuts['TailCuts'][dict_index] #print("Tailcuts are",tailcuts[0],tailcuts[1]) #rot_angle = 0.*u.deg #if event.inst.optical_foclen[telid] > 10.*u.m and event.dl0.tel[telid].num_pixels != 1764: #rot_angle = -100.14*u.deg clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) camera_coord = CameraFrame( x=x, y=y, z=np.zeros(x.shape) * u.m, focal_length=event.inst.optical_foclen[telid], rotation=geom.pix_rotation) #print("Camera",geom.cam_id,"focal length",event.inst.optical_foclen[telid],"rotation",geom.pix_rotation) #TODO: correct this hack for values over 90 altval = event.mcheader.run_array_direction[1] if (altval > np.pi / 2.): altval = np.pi / 2. altaz = HorizonFrame(alt=altval * u.rad, az=event.mcheader.run_array_direction[0] * u.rad) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=altaz, pointing_direction=altaz)) x = nom_coord.x.to(u.deg) y = nom_coord.y.to(u.deg) img = image * clean_mask noise = 5. weight = img / (img + noise) muonring = ChaudhuriKunduRingFitter(None) #print("img:",np.sum(image),"mask:",np.sum(clean_mask), "x=",x,"y=",y) if not sum(img): #Nothing left after tail cuts continue muonringparam = muonring.fit(x, y, image * clean_mask) #muonringparam = muonring.fit(x,y,weight) dist = np.sqrt( np.power(x - muonringparam.ring_center_x, 2) + np.power(y - muonringparam.ring_center_y, 2)) ring_dist = np.abs(dist - muonringparam.ring_radius) muonringparam = muonring.fit( x, y, img * (ring_dist < muonringparam.ring_radius * 0.4)) dist = np.sqrt( np.power(x - muonringparam.ring_center_x, 2) + np.power(y - muonringparam.ring_center_y, 2)) ring_dist = np.abs(dist - muonringparam.ring_radius) #print("1: x",muonringparam.ring_center_x,"y",muonringparam.ring_center_y,"radius",muonringparam.ring_radius) muonringparam = muonring.fit( x, y, img * (ring_dist < muonringparam.ring_radius * 0.4)) #print("2: x",muonringparam.ring_center_x,"y",muonringparam.ring_center_y,"radius",muonringparam.ring_radius) muonringparam.tel_id = telid muonringparam.run_id = event.dl0.run_id muonringparam.event_id = event.dl0.event_id dist_mask = np.abs( dist - muonringparam.ring_radius) < muonringparam.ring_radius * 0.4 rad = list() cx = list() cy = list() mc_x = event.mc.core_x mc_y = event.mc.core_y pix_im = image * dist_mask nom_dist = np.sqrt( np.power(muonringparam.ring_center_x, 2) + np.power(muonringparam.ring_center_y, 2)) #numpix = event.dl0.tel[telid].num_pixels minpix = muon_cuts['MinPix'][dict_index] #0.06*numpix #or 8% mir_rad = np.sqrt( event.inst.mirror_dish_area[telid] / (np.pi) ) #need to consider units? (what about hole? Area is then less...) #Camera containment radius - better than nothing - guess pixel diameter of 0.11, all cameras are perfectly circular cam_rad = np.sqrt(numpix*0.11/(2.*np.pi)) if (np.sum(pix_im > tailcuts[0]) > 0.1 * minpix and np.sum(pix_im) > minpix and nom_dist < muon_cuts['CamRad'][dict_index] * u.deg and muonringparam.ring_radius < 1.5 * u.deg and muonringparam.ring_radius > 1. * u.deg): #Guess HESS is 0.16 #sec_rad = 0.*u.m #sct = False #if numpix == 2048 and mir_rad > 2.*u.m and mir_rad < 2.1*u.m: # sec_rad = 1.*u.m # sct = True #Store muon ring parameters (passing cuts stage 1) #muonringlist[idx] = muonringparam tellist.append(telid) muonringlist.append(muonringparam) muonintensitylist.append(None) #embed() ctel = MuonLineIntegrate( mir_rad, 0.2 * u.m, pixel_width=muon_cuts['AngPixW'][dict_index] * u.deg, sct_flag=muon_cuts['SCT'][dict_index], secondary_radius=muon_cuts['SecRad'][dict_index]) if (image.shape[0] == muon_cuts['TotalPix'][dict_index]): muonintensityoutput = ctel.fit_muon( muonringparam.ring_center_x, muonringparam.ring_center_y, muonringparam.ring_radius, x[dist_mask], y[dist_mask], image[dist_mask]) muonintensityoutput.tel_id = telid muonintensityoutput.run_id = event.dl0.run_id muonintensityoutput.event_id = event.dl0.event_id muonintensityoutput.mask = dist_mask print("Tel", telid, "Impact parameter = ", muonintensityoutput.impact_parameter, "mir_rad", mir_rad, "ring_width=", muonintensityoutput.ring_width) #if(muonintensityoutput.impact_parameter > muon_cuts['Impact'][dict_index][1]*mir_rad or muonintensityoutput.impact_parameter < muon_cuts['Impact'][dict_index][0]*mir_rad): # print("Failed on impact parameter low cut",muon_cuts['Impact'][dict_index][0]*mir_rad,"high cut",muon_cuts['Impact'][dict_index][1]*mir_rad,"for",geom.cam_id) #if(muonintensityoutput.ring_width > muon_cuts['RingWidth'][dict_index][1]*u.deg or muonintensityoutput.ring_width < muon_cuts['RingWidth'][dict_index][0]*u.deg): # print("Failed on ring width low cut",muon_cuts['RingWidth'][dict_index][0]*u.deg,"high cut",muon_cuts['RingWidth'][dict_index][1]*u.deg,"for ",geom.cam_id) #print("Cuts <",muon_cuts["Impact"][dict_index][1]*mir_rad,"cuts >",muon_cuts['Impact'][dict_index][0]*u.m,'ring_width < ',muon_cuts['RingWidth'][dict_index][1]*u.deg,'ring_width >',muon_cuts['RingWidth'][dict_index][0]*u.deg) if (muonintensityoutput.impact_parameter < muon_cuts['Impact'][dict_index][1] * mir_rad and muonintensityoutput.impact_parameter > muon_cuts['Impact'][dict_index][0] * u.m and muonintensityoutput.ring_width < muon_cuts['RingWidth'][dict_index][1] * u.deg and muonintensityoutput.ring_width > muon_cuts['RingWidth'][dict_index][0] * u.deg): muonintensityparam = muonintensityoutput idx = tellist.index(telid) muonintensitylist[idx] = muonintensityparam print("Muon in tel", telid, "# tels in event=", len(event.dl0.tels_with_data)) else: continue #print("Fitted ring centre (2):",muonringparam.ring_center_x,muonringparam.ring_center_y) #return muonringparam, muonintensityparam return muon_event_param
for tel_id, dl1 in event.dl1.tel.items(): camera = event.inst.subarray.tels[tel_id].camera focal_length = event.inst.subarray.tels[ tel_id].optics.equivalent_focal_length image = dl1.image[0] # telescope mc info mc_tel = event.mc.tel[tel_id] telescope_pointing = SkyCoord( alt=mc_tel['altitude_raw'], az=mc_tel['azimuth_raw'], unit='rad', frame=HorizonFrame(), ) camera_frame = CameraFrame(telescope_pointing=telescope_pointing, focal_length=focal_length) boundary, picture, min_neighbors = cleaning_level[camera.cam_id] clean = tailcuts_clean(camera, image, boundary_thresh=boundary, picture_thresh=picture, min_number_picture_neighbors=min_neighbors) cam_coords = SkyCoord(camera.pix_x[clean], camera.pix_y[clean], frame=camera_frame) nom = cam_coords.transform_to(nominal_frame) nom_delta_az.append(nom.delta_az.to_value(u.deg)) nom_delta_alt.append(nom.delta_alt.to_value(u.deg)) photons.append(image[clean])
def analyze_muon_event(subarray, event_id, image, geom, equivalent_focal_length, mirror_area, plot_rings, plots_path): """ Analyze an event to fit a muon ring Paramenters --------- event_id: `int` id of the analyzed event image: `np.ndarray` number of photoelectrons in each pixel geom: CameraGeometry equivalent_focal_length: `float` focal length of the telescope mirror_area: `float` mirror area of the telescope in square meters plot_rings: `bool` plot the muon ring plots_path: `string` path to store the figures Returns --------- muonintensityoutput MuonEfficiencyContainer dist_mask ndarray, pixels used in ring intensity likelihood fit ring_size float, in p.e. total intensity in ring size_outside_ring float, in p.e. to check for "shower contamination" muonringparam MuonParametersContainer good_ring bool, it determines whether the ring can be used for analysis or not radial_distribution dict, return of function radial_light_distribution mean_pixel_charge_around_ring float, charge "just outside" ring, to check the possible signal extrator bias muonparameters MuonParametersContainer TODO: several hard-coded quantities that can go into a configuration file """ lst1_tel_id = 1 lst1_description = subarray.tels[lst1_tel_id] tailcuts = [10, 5] cam_rad = (lst1_description.camera.geometry.guess_radius() / lst1_description.optics.equivalent_focal_length) * u.rad # some cuts for good ring selection: min_pix = 148 # (8%) minimum number of pixels in the ring with >0 signal min_pix_fraction_after_cleaning = 0.1 # minimum fraction of the ring pixels that must be above tailcuts[0] min_ring_radius = 0.8 * u.deg # minimum ring radius max_ring_radius = 1.5 * u.deg # maximum ring radius max_radial_stdev = 0.1 * u.deg # maximum standard deviation of the light distribution along ring radius max_radial_excess_kurtosis = 1. # maximum excess kurtosis min_impact_parameter = 0.2 # in fraction of mirror radius max_impact_parameter = 0.9 # in fraction of mirror radius ring_integration_width = 0.25 # +/- integration range along ring radius, in fraction of ring radius (was 0.4 until 20200326) outer_ring_width = 0.2 # in fraction of ring radius, width of ring just outside the integrated muon ring, used to check pedestal bias x, y = pixel_coords_to_telescope(geom, equivalent_focal_length) muonringparam, clean_mask, dist, image_clean = fit_muon( x, y, image, geom, tailcuts) mirror_radius = np.sqrt(mirror_area / np.pi) # meters dist_mask = np.abs(dist - muonringparam.radius ) < muonringparam.radius * ring_integration_width pix_ring = image * dist_mask pix_outside_ring = image * ~dist_mask # mask to select pixels just outside the ring that will be integrated to obtain the ring's intensity: dist_mask_2 = np.logical_and( ~dist_mask, np.abs(dist - muonringparam.radius) < muonringparam.radius * (ring_integration_width + outer_ring_width)) pix_ring_2 = image[dist_mask_2] # nom_dist = np.sqrt(np.power(muonringparam.center_x,2) # + np.power(muonringparam.center_y, 2)) muonparameters = MuonParametersContainer() muonparameters.containment = ring_containment(muonringparam.radius, muonringparam.center_x, muonringparam.center_y, cam_rad) radial_distribution = radial_light_distribution(muonringparam.center_x, muonringparam.center_y, x[clean_mask], y[clean_mask], image[clean_mask]) # Do complicated calculations (minuit-based max likelihood ring fit) only for selected rings: candidate_clean_ring = all([ radial_distribution['standard_dev'] < max_radial_stdev, radial_distribution['excess_kurtosis'] < max_radial_excess_kurtosis, (pix_ring > tailcuts[0]).sum() > min_pix_fraction_after_cleaning * min_pix, np.count_nonzero(pix_ring) > min_pix, muonringparam.radius < max_ring_radius, muonringparam.radius > min_ring_radius ]) if candidate_clean_ring: intensity_fitter = MuonIntensityFitter(subarray) # Use same hard-coded value for pedestal fluctuations as the previous # version of ctapipe: pedestal_stddev = 1.1 * np.ones(len(image)) muonintensityoutput = \ intensity_fitter(1, muonringparam.center_x, muonringparam.center_y, muonringparam.radius, image, pedestal_stddev, dist_mask) dist_ringwidth_mask = np.abs(dist - muonringparam.radius) < \ muonintensityoutput.width # We do the calculation of the ring completeness (i.e. fraction of whole circle) using the pixels # within the "width" fitted using MuonIntensityFitter muonparameters.completeness = ring_completeness( x[dist_ringwidth_mask], y[dist_ringwidth_mask], image[dist_ringwidth_mask], muonringparam.radius, muonringparam.center_x, muonringparam.center_y, threshold=30, bins=30) # No longer existing in ctapipe 0.8: # pix_ringwidth_im = image[dist_ringwidth_mask] # muonintensityoutput.ring_pix_completeness = \ # (pix_ringwidth_im > tailcuts[0]).sum() / len(pix_ringwidth_im) else: # just to have the default values with units: muonintensityoutput = MuonEfficiencyContainer() muonintensityoutput.width = u.Quantity(np.nan, u.deg) muonintensityoutput.impact = u.Quantity(np.nan, u.m) muonintensityoutput.impact_x = u.Quantity(np.nan, u.m) muonintensityoutput.impact_y = u.Quantity(np.nan, u.m) # muonintensityoutput.mask = dist_mask # no longer there in ctapipe 0.8 ring_size = np.sum(pix_ring) size_outside_ring = np.sum(pix_outside_ring * clean_mask) # This is just mean charge per pixel in pixels just around the ring # (on the outer side): mean_pixel_charge_around_ring = np.sum(pix_ring_2) / len(pix_ring_2) if candidate_clean_ring: print( "Impact parameter={:.3f}, ring_width={:.3f}, ring radius={:.3f}, " "ring completeness={:.3f}".format( muonintensityoutput.impact, muonintensityoutput.width, muonringparam.radius, muonparameters.completeness, )) # Now add the conditions based on the detailed muon ring fit: conditions = [ candidate_clean_ring, muonintensityoutput.impact < max_impact_parameter * mirror_radius, muonintensityoutput.impact > min_impact_parameter * mirror_radius, # TODO: To be applied when we have decent optics. # muonintensityoutput.width # < 0.08, # NOTE: inside "candidate_clean_ring" cuts there is already a cut in # the std dev of light distribution along ring radius, which is also # a measure of the ring width # muonintensityoutput.width # > 0.04 ] if all(conditions): good_ring = True else: good_ring = False if (plot_rings and plots_path and good_ring): focal_length = equivalent_focal_length ring_telescope = SkyCoord(muonringparam.center_x, muonringparam.center_y, TelescopeFrame()) ring_camcoord = ring_telescope.transform_to( CameraFrame( focal_length=focal_length, rotation=geom.cam_rotation, )) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) radius = muonringparam.radius width = muonintensityoutput.width ringrad_camcoord = 2 * radius.to(u.rad) * focal_length ringwidthfrac = width / radius ringrad_inner = ringrad_camcoord * (1. - ringwidthfrac) ringrad_outer = ringrad_camcoord * (1. + ringwidthfrac) fig, ax = plt.subplots(figsize=(10, 10)) plot_muon_event(ax, geom, image * clean_mask, centroid, ringrad_camcoord, ringrad_inner, ringrad_outer, event_id) plt.figtext(0.15, 0.20, 'radial std dev: {0:.3f}'. \ format(radial_distribution['standard_dev'])) plt.figtext(0.15, 0.18, 'radial excess kurtosis: {0:.3f}'. \ format(radial_distribution['excess_kurtosis'])) plt.figtext(0.15, 0.16, 'fitted ring width: {0:.3f}'.format(width)) plt.figtext(0.15, 0.14, 'ring completeness: {0:.3f}'. \ format(muonparameters.completeness)) fig.savefig('{}/Event_{}_fitted.png'.format(plots_path, event_id)) if (plot_rings and not plots_path): print("You are trying to plot without giving a path!") return muonintensityoutput, dist_mask, ring_size, size_outside_ring, \ muonringparam, good_ring, radial_distribution, \ mean_pixel_charge_around_ring, muonparameters
def prepare_event(self, source, return_stub=True, save_images=False, debug=False): """ Calibrate, clean and reconstruct the direction of an event. Parameters ---------- source : ctapipe.io.EventSource A container of selected showers from a simtel file. geom_cam_tel: dict Dictionary of of MyCameraGeometry objects for each camera in the file return_stub : bool If True, yield also images from events that won't be reconstructed. This feature is not currently available. save_images : bool If True, save photoelectron images from reconstructed events. debug : bool If True, print some debugging information (to be expanded). Yields ------ PreparedEvent: dict Dictionary containing event-image information to be written. """ # ============================================================= # TRANSFORMED CAMERA GEOMETRIES # ============================================================= # These are the camera geometries were the Hillas parametrization will # be performed. # They are transformed to TelescopeFrame using the effective focal # lengths # These geometries could be used also to performe the image cleaning, # but for the moment we still do that in the CameraFrame geom_cam_tel = {} for camera in source.subarray.camera_types: # Original geometry of each camera geom = camera.geometry # Same but with focal length as an attribute # This is planned to disappear and be imported by ctapipe focal_length = effective_focal_lengths(camera.camera_name) geom_cam_tel[camera.camera_name] = MyCameraGeometry( camera_name=camera.camera_name, pix_type=geom.pix_type, pix_id=geom.pix_id, pix_x=geom.pix_x, pix_y=geom.pix_y, pix_area=geom.pix_area, cam_rotation=geom.cam_rotation, pix_rotation=geom.pix_rotation, frame=CameraFrame(focal_length=focal_length), ).transform_to(TelescopeFrame()) # ============================================================= ievt = 0 for event in source: # Display event counts if debug: print( bcolors.BOLD + f"EVENT #{event.count}, EVENT_ID #{event.index.event_id}" + bcolors.ENDC) print(bcolors.BOLD + f"has triggered telescopes {event.r1.tel.keys()}" + bcolors.ENDC) ievt += 1 # if (ievt < 10) or (ievt % 10 == 0): # print(ievt) self.event_cutflow.count("noCuts") # LST stereo condition # whenever there is only 1 LST in an event, we remove that telescope # if the remaining telescopes are less than min_tel we remove the event lst_tel_ids = set( source.subarray.get_tel_ids_for_type("LST_LST_LSTCam")) triggered_LSTs = set(event.r0.tel.keys()).intersection(lst_tel_ids) n_triggered_LSTs = len(triggered_LSTs) n_triggered_non_LSTs = len(event.r0.tel.keys()) - n_triggered_LSTs bad_LST_stereo = False if self.LST_stereo and self.event_cutflow.cut( "no-LST-stereo + <2 other types", n_triggered_LSTs, n_triggered_non_LSTs): bad_LST_stereo = True if return_stub: print( bcolors.WARNING + "WARNING: LST_stereo is set to 'True'\n" + f"This event has < {self.min_ntel_LST} triggered LSTs\n" + "and < 2 triggered telescopes from other telescope types.\n" + "The event will be processed up to DL1b." + bcolors.ENDC) # we show this, but we proceed to analyze the event up to # DL1a/b for the associated benchmarks # this checks for < 2 triggered telescopes of ANY type if self.event_cutflow.cut("min2Tels trig", len(event.r1.tel.keys())): if return_stub: print( bcolors.WARNING + f"WARNING : < {self.min_ntel} triggered telescopes!" + bcolors.ENDC) # we show this, but we proceed to analyze it # ============================================================= # CALIBRATION # ============================================================= if debug: print(bcolors.OKBLUE + "Extracting all calibrated images..." + bcolors.ENDC) self.calib(event) # Calibrate the event # ============================================================= # BEGINNING OF LOOP OVER TELESCOPES # ============================================================= dl1_phe_image = {} dl1_phe_image_mask_reco = {} dl1_phe_image_mask_clusters = {} mc_phe_image = {} max_signals = {} n_pixel_dict = {} hillas_dict_reco = {} # for direction reconstruction hillas_dict = {} # for discrimination leakage_dict = {} concentration_dict = {} n_tels = { "Triggered": len(event.r1.tel.keys()), "LST_LST_LSTCam": 0, "MST_MST_NectarCam": 0, "MST_MST_FlashCam": 0, "MST_SCT_SCTCam": 0, "SST_1M_DigiCam": 0, "SST_ASTRI_ASTRICam": 0, "SST_GCT_CHEC": 0, } n_cluster_dict = {} impact_dict_reco = {} # impact distance measured in tilt system point_azimuth_dict = {} point_altitude_dict = {} good_for_reco = {} # 1 = success, 0 = fail # Array pointing in AltAz frame az = event.pointing.array_azimuth alt = event.pointing.array_altitude array_pointing = SkyCoord(az, alt, frame=AltAz()) ground_frame = GroundFrame() tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing) for tel_id in event.r1.tel.keys(): point_azimuth_dict[tel_id] = event.pointing.tel[tel_id].azimuth point_altitude_dict[tel_id] = event.pointing.tel[ tel_id].altitude if debug: print(bcolors.OKBLUE + f"Working on telescope #{tel_id}..." + bcolors.ENDC) self.image_cutflow.count("noCuts") camera = source.subarray.tel[tel_id].camera.geometry # count the current telescope according to its type tel_type = str(source.subarray.tel[tel_id]) n_tels[tel_type] += 1 # use ctapipe's functionality to get the calibrated image # and scale the reconstructed values if required pmt_signal = event.dl1.tel[tel_id].image / self.calibscale # If required... if save_images is True: # Save the simulated and reconstructed image of the event dl1_phe_image[tel_id] = pmt_signal mc_phe_image[tel_id] = event.simulation.tel[ tel_id].true_image # We now ASSUME that the event will be good good_for_reco[tel_id] = 1 # later we change to 0 if any condition is NOT satisfied if self.cleaner_reco.mode == "tail": # tail uses only ctapipe # Cleaning used for direction reconstruction image_biggest, mask_reco = self.cleaner_reco.clean_image( pmt_signal, camera) # calculate the leakage (before filtering) leakages = {} # this is needed by both cleanings # The check on SIZE shouldn't be here, but for the moment # I prefer to sacrifice elegancy... if np.sum(image_biggest[mask_reco]) != 0.0: leakage_biggest = leakage_parameters( camera, image_biggest, mask_reco) leakages["leak1_reco"] = leakage_biggest[ "intensity_width_1"] leakages["leak2_reco"] = leakage_biggest[ "intensity_width_2"] else: leakages["leak1_reco"] = 0.0 leakages["leak2_reco"] = 0.0 # find all islands using this cleaning num_islands, labels = number_of_islands(camera, mask_reco) if num_islands == 1: # if only ONE islands is left ... # ...use directly the old mask and reduce dimensions # to make Hillas parametrization faster camera_biggest = camera[mask_reco] image_biggest = image_biggest[mask_reco] if save_images is True: dl1_phe_image_mask_reco[tel_id] = mask_reco elif num_islands > 1: # if more islands survived.. # ...find the biggest one mask_biggest = largest_island(labels) # and also reduce dimensions camera_biggest = camera[mask_biggest] image_biggest = image_biggest[mask_biggest] if save_images is True: dl1_phe_image_mask_reco[tel_id] = mask_biggest else: # if no islands survived use old camera and image camera_biggest = camera dl1_phe_image_mask_reco[tel_id] = mask_reco # Cleaning used for score/energy estimation image_extended, mask_extended = self.cleaner_extended.clean_image( pmt_signal, camera) dl1_phe_image_mask_clusters[tel_id] = mask_extended # calculate the leakage (before filtering) # this part is not well coded, but for the moment it works if np.sum(image_extended[mask_extended]) != 0.0: leakage_extended = leakage_parameters( camera, image_extended, mask_extended) leakages["leak1"] = leakage_extended[ "intensity_width_1"] leakages["leak2"] = leakage_extended[ "intensity_width_2"] else: leakages["leak1"] = 0.0 leakages["leak2"] = 0.0 # find all islands with this cleaning # we will also register how many have been found n_cluster_dict[tel_id], labels = number_of_islands( camera, mask_extended) # NOTE: the next check shouldn't be necessary if we keep # all the isolated pixel clusters, but for now the # two cleanings are set the same in analysis.yml because # the performance of the extended one has never been really # studied in model estimation. # if some islands survived if n_cluster_dict[tel_id] > 0: # keep all of them and reduce dimensions camera_extended = camera[mask_extended] image_extended = image_extended[mask_extended] else: # otherwise continue with the old camera and image camera_extended = camera # could this go into `hillas_parameters` ...? # this is basically the charge of ALL islands # not calculated later by the Hillas parametrization! max_signals[tel_id] = np.max(image_extended) else: # for wavelets we stick to old pywi-cta code try: # "try except FileNotFoundError" not clear to me, but for now it stays... with warnings.catch_warnings(): # Image with biggest cluster (reco cleaning) image_biggest, mask_reco = self.cleaner_reco.clean_image( pmt_signal, camera) image_biggest2d = geometry_converter.image_1d_to_2d( image_biggest, camera.camera_name) image_biggest2d = filter_pixels_clusters( image_biggest2d) image_biggest = geometry_converter.image_2d_to_1d( image_biggest2d, camera.camera_name) # Image for score/energy estimation (with clusters) ( image_extended, mask_extended, ) = self.cleaner_extended.clean_image( pmt_signal, camera) # This last part was outside the pywi-cta block # before, but is indeed part of it because it uses # pywi-cta functions in the "extended" case # For cluster counts image_2d = geometry_converter.image_1d_to_2d( image_extended, camera.camera_name) n_cluster_dict[ tel_id] = pixel_clusters.number_of_pixels_clusters( array=image_2d, threshold=0) # could this go into `hillas_parameters` ...? max_signals[tel_id] = np.max(image_extended) except FileNotFoundError as e: print(e) continue # ============================================================= # PRELIMINARY IMAGE SELECTION # ============================================================= cleaned_image_is_good = True # we assume this if self.image_selection_source == "extended": cleaned_image_to_use = image_extended elif self.image_selection_source == "biggest": cleaned_image_to_use = image_biggest else: raise ValueError( "Only supported cleanings are 'biggest' or 'extended'." ) # Apply some selection if self.image_cutflow.cut("min pixel", cleaned_image_to_use): if debug: print(bcolors.WARNING + "WARNING : not enough pixels!" + bcolors.ENDC) good_for_reco[tel_id] = 0 # we record it as BAD cleaned_image_is_good = False if self.image_cutflow.cut("min charge", np.sum(cleaned_image_to_use)): if debug: print(bcolors.WARNING + "WARNING : not enough charge!" + bcolors.ENDC) good_for_reco[tel_id] = 0 # we record it as BAD cleaned_image_is_good = False if debug and (not cleaned_image_is_good): # BAD image quality print(bcolors.WARNING + "WARNING : The cleaned image didn't pass" + " preliminary cuts.\n" + "An attempt to parametrize it will be made," + " but the image will NOT be used for" + " direction reconstruction." + bcolors.ENDC) # ============================================================= # IMAGE PARAMETRIZATION # ============================================================= with np.errstate(invalid="raise", divide="raise"): try: # Filter the cameras in TelescopeFrame with the same # cleaning masks camera_biggest_tel = geom_cam_tel[camera.camera_name][ camera_biggest.pix_id] camera_extended_tel = geom_cam_tel[camera.camera_name][ camera_extended.pix_id] # Parametrize the image in the TelescopeFrame moments_reco = hillas_parameters( camera_biggest_tel, image_biggest) # for geometry (eg direction) moments = hillas_parameters( camera_extended_tel, image_extended ) # for discrimination and energy reconstruction if debug: print( "Image parameters from main cluster cleaning:") print(moments_reco) print( "Image parameters from all-clusters cleaning:") print(moments) # Add concentration parameters concentrations = {} concentrations_extended = concentration_parameters( camera_extended_tel, image_extended, moments) concentrations[ "concentration_cog"] = concentrations_extended[ "cog"] concentrations[ "concentration_core"] = concentrations_extended[ "core"] concentrations[ "concentration_pixel"] = concentrations_extended[ "pixel"] # =================================================== # PARAMETRIZED IMAGE SELECTION # =================================================== if self.image_selection_source == "extended": moments_to_use = moments else: moments_to_use = moments_reco # if width and/or length are zero (e.g. when there is # only only one pixel or when all pixel are exactly # in one row), the parametrisation # won't be very useful: skip if self.image_cutflow.cut("poor moments", moments_to_use): if debug: print(bcolors.WARNING + "WARNING : poor moments!" + bcolors.ENDC) good_for_reco[tel_id] = 0 # we record it as BAD if self.image_cutflow.cut("close to the edge", moments_to_use, camera.camera_name): if debug: print( bcolors.WARNING + "WARNING : out of containment radius!\n" + f"Camera radius = {self.camera_radius[camera.camera_name]}\n" + f"COG radius = {moments_to_use.r}" + bcolors.ENDC) good_for_reco[tel_id] = 0 if self.image_cutflow.cut("bad ellipticity", moments_to_use): if debug: print(bcolors.WARNING + "WARNING : bad ellipticity" + bcolors.ENDC) good_for_reco[tel_id] = 0 if debug and good_for_reco[tel_id] == 1: print( bcolors.OKGREEN + "Image survived and correctly parametrized." # + "\nIt will be used for direction reconstruction!" + bcolors.ENDC) elif debug and good_for_reco[tel_id] == 0: print( bcolors.WARNING + "Image not survived or " + "not good enough for parametrization." # + "\nIt will be NOT used for direction reconstruction, " # + "BUT it's information will be recorded." + bcolors.ENDC) hillas_dict[tel_id] = moments hillas_dict_reco[tel_id] = moments_reco n_pixel_dict[tel_id] = len( np.where(image_extended > 0)[0]) leakage_dict[tel_id] = leakages concentration_dict[tel_id] = concentrations except ( FloatingPointError, HillasParameterizationError, ValueError, ) as e: if debug: print(bcolors.FAIL + "Parametrization error: " + f"{e}\n" + "Dummy parameters recorded." + bcolors.ENDC) good_for_reco[tel_id] = 0 hillas_dict[ tel_id] = HillasParametersTelescopeFrameContainer( ) hillas_dict_reco[ tel_id] = HillasParametersTelescopeFrameContainer( ) n_pixel_dict[tel_id] = len( np.where(image_extended > 0)[0]) leakage_dict[tel_id] = leakages concentration_dict[tel_id] = concentrations # END OF THE CYCLE OVER THE TELESCOPES # ============================================================= # DIRECTION RECONSTRUCTION # ============================================================= if bad_LST_stereo: if debug: print( bcolors.WARNING + "WARNING: This event was triggered with 1 LST image and <2 images from other telescope types." + "\nWARNING : direction reconstruction will not be performed." + bcolors.ENDC) # Set all the involved images as NOT good for recosntruction # even though they might have been # but this is because of the LST stereo trigger.... for tel_id in event.r0.tel.keys(): good_for_reco[tel_id] = 0 # and set the number of good and bad images accordingly n_tels["GOOD images"] = 0 n_tels[ "BAD images"] = n_tels["Triggered"] - n_tels["GOOD images"] # create a dummy container for direction reconstruction reco_result = ReconstructedShowerContainer() if return_stub: # if saving all events (default) if debug: print(bcolors.OKBLUE + "Recording event..." + bcolors.ENDC) print( bcolors.WARNING + "WARNING: This is event shall NOT be used further along the pipeline." + bcolors.ENDC) yield stub( # record event with dummy info event, mc_phe_image, dl1_phe_image, dl1_phe_image_mask_reco, dl1_phe_image_mask_clusters, good_for_reco, hillas_dict, hillas_dict_reco, n_tels, leakage_dict, concentration_dict) continue else: continue # Now in case the only triggered telescopes were # - < self.min_ntel_LST LST, # - >=2 any other telescope type, # we remove the single-LST image and continue reconstruction with # the images from the other telescope types if self.LST_stereo and (n_triggered_LSTs < self.min_ntel_LST) and ( n_triggered_LSTs != 0) and (n_triggered_non_LSTs >= 2): if debug: print( bcolors.WARNING + f"WARNING: LST stereo trigger condition is active.\n" + f"This event triggered < {self.min_ntel_LST} LSTs " + f"and {n_triggered_non_LSTs} images from other telescope types.\n" + bcolors.ENDC) for tel_id in triggered_LSTs: # in case we test for min_ntel_LST>2 if good_for_reco[tel_id]: # we don't use it for reconstruction good_for_reco[tel_id] = 0 print( bcolors.WARNING + f"WARNING: LST image #{tel_id} removed, even though it passed quality cuts." + bcolors.ENDC) # TODO: book-keeping of this kind of events doesn't seem easy # convert dictionary in numpy array to get a "mask" images_status = np.asarray(list(good_for_reco.values())) # record how many images will be used for reconstruction n_tels["GOOD images"] = len( np.extract(images_status == 1, images_status)) n_tels["BAD images"] = n_tels["Triggered"] - n_tels["GOOD images"] if self.event_cutflow.cut("min2Tels reco", n_tels["GOOD images"]): if debug: print( bcolors.FAIL + f"WARNING: < {self.min_ntel} ({n_tels['GOOD images']}) images remaining!" + "\nWARNING : direction reconstruction is not possible!" + bcolors.ENDC) # create a dummy container for direction reconstruction reco_result = ReconstructedShowerContainer() if return_stub: # if saving all events (default) if debug: print(bcolors.OKBLUE + "Recording event..." + bcolors.ENDC) print( bcolors.WARNING + "WARNING: This is event shall NOT be used further along the pipeline." + bcolors.ENDC) yield stub( # record event with dummy info event, mc_phe_image, dl1_phe_image, dl1_phe_image_mask_reco, dl1_phe_image_mask_clusters, good_for_reco, hillas_dict, hillas_dict_reco, n_tels, leakage_dict, concentration_dict) continue else: continue if debug: print(bcolors.OKBLUE + "Starting direction reconstruction..." + bcolors.ENDC) try: with warnings.catch_warnings(): warnings.simplefilter("ignore") if self.image_selection_source == "extended": hillas_dict_to_use = hillas_dict else: hillas_dict_to_use = hillas_dict_reco # use only the successfully parametrized images # to reconstruct the direction of this event successfull_hillas = np.where(images_status == 1)[0] all_images = np.asarray(list(good_for_reco.keys())) good_images = set(all_images[successfull_hillas]) good_hillas_dict = { k: v for k, v in hillas_dict_to_use.items() if k in good_images } if debug: print( bcolors.PURPLE + f"{len(good_hillas_dict)} images " + f"(from telescopes #{list(good_hillas_dict.keys())}) will be " + "used to recover the shower's direction..." + bcolors.ENDC) # Reconstruction results reco_result = self.shower_reco.predict( good_hillas_dict, source.subarray, SkyCoord(alt=alt, az=az, frame="altaz"), None, # use the array direction ) # Impact parameter for telescope-wise energy estimation subarray = source.subarray for tel_id in hillas_dict_to_use.keys(): pos = subarray.positions[tel_id] tel_ground = SkyCoord(pos[0], pos[1], pos[2], frame=ground_frame) core_ground = SkyCoord( reco_result.core_x, reco_result.core_y, 0 * u.m, frame=ground_frame, ) # Go back to the tilted frame # this should be the same... tel_tilted = tel_ground.transform_to(tilted_frame) # but this not core_tilted = SkyCoord(x=core_ground.x, y=core_ground.y, frame=tilted_frame) impact_dict_reco[tel_id] = np.sqrt( (core_tilted.x - tel_tilted.x)**2 + (core_tilted.y - tel_tilted.y)**2) except (Exception, TooFewTelescopesException, InvalidWidthException) as e: if debug: print("exception in reconstruction:", e) raise if return_stub: if debug: print( bcolors.FAIL + "Shower could NOT be correctly reconstructed! " + "Recording event..." + "WARNING: This is event shall NOT be used further along the pipeline." + bcolors.ENDC) yield stub( # record event with dummy info event, mc_phe_image, dl1_phe_image, dl1_phe_image_mask_reco, dl1_phe_image_mask_clusters, good_for_reco, hillas_dict, hillas_dict_reco, n_tels, leakage_dict, concentration_dict) else: continue if self.event_cutflow.cut("direction nan", reco_result): if debug: print(bcolors.WARNING + "WARNING: undefined direction!" + bcolors.ENDC) if return_stub: if debug: print( bcolors.FAIL + "Shower could NOT be correctly reconstructed! " + "Recording event..." + "WARNING: This is event shall NOT be used further along the pipeline." + bcolors.ENDC) yield stub( # record event with dummy info event, mc_phe_image, dl1_phe_image, dl1_phe_image_mask_reco, dl1_phe_image_mask_clusters, good_for_reco, hillas_dict, hillas_dict_reco, n_tels, leakage_dict, concentration_dict) else: continue if debug: print(bcolors.BOLDGREEN + "Shower correctly reconstructed! " + "Recording event..." + bcolors.ENDC) yield PreparedEvent( event=event, dl1_phe_image=dl1_phe_image, dl1_phe_image_mask_reco=dl1_phe_image_mask_reco, dl1_phe_image_mask_clusters=dl1_phe_image_mask_clusters, mc_phe_image=mc_phe_image, n_pixel_dict=n_pixel_dict, hillas_dict=hillas_dict, hillas_dict_reco=hillas_dict_reco, leakage_dict=leakage_dict, concentration_dict=concentration_dict, n_tels=n_tels, max_signals=max_signals, n_cluster_dict=n_cluster_dict, reco_result=reco_result, impact_dict=impact_dict_reco, good_event=True, good_for_reco=good_for_reco, )
def predict(self, hillas_dict, subarray, 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) grd_coord = subarray.tel_coords tilt_coord = grd_coord.transform_to(tilted_frame) tel_ids = list(hillas_dict.keys()) tel_indices = subarray.tel_ids_to_indices(tel_ids) tel_x = { tel_id: tilt_coord.x[tel_index] for tel_id, tel_index in zip(tel_ids, tel_indices) } tel_y = { tel_id: tilt_coord.y[tel_index] for tel_id, tel_index in zip(tel_ids, tel_indices) } nom_frame = NominalFrame(origin=array_pointing) hillas_dict_mod = {} for tel_id, hillas in hillas_dict.items(): if isinstance(hillas, CameraHillasParametersContainer): focal_length = 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) else: telescope_frame = TelescopeFrame( telescope_pointing=telescopes_pointings[tel_id] ) cog_coords = SkyCoord( fov_lon=hillas.fov_lon, fov_lat=hillas.fov_lat, frame=telescope_frame, ) cog_coords_nom = cog_coords.transform_to(nom_frame) hillas_dict_mod[tel_id] = HillasParametersContainer( fov_lon=cog_coords_nom.fov_lon, fov_lat=cog_coords_nom.fov_lat, psi=hillas.psi, width=hillas.width, length=hillas.length, intensity=hillas.intensity, ) src_fov_lon, src_fov_lat, err_fov_lon, err_fov_lat = 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_fov_lon *= u.rad err_fov_lat *= u.rad nom = SkyCoord( fov_lon=src_fov_lon * u.rad, fov_lat=src_fov_lat * u.rad, frame=nom_frame ) sky_pos = nom.transform_to(array_pointing.frame) tilt = SkyCoord(x=core_x * u.m, y=core_y * u.m, frame=tilted_frame) grd = project_to_ground(tilt) x_max = self.reconstruct_xmax( nom.fov_lon, nom.fov_lat, tilt.x, tilt.y, hillas_dict_mod, tel_x, tel_y, 90 * u.deg - array_pointing.alt, ) src_error = np.sqrt(err_fov_lon ** 2 + err_fov_lat ** 2) result = ReconstructedGeometryContainer( alt=sky_pos.altaz.alt.to(u.rad), az=sky_pos.altaz.az.to(u.rad), core_x=grd.x, core_y=grd.y, core_uncert=u.Quantity(np.sqrt(core_err_x ** 2 + core_err_y ** 2), u.m), tel_ids=[h for h in hillas_dict_mod.keys()], average_intensity=np.mean([h.intensity for h in hillas_dict_mod.values()]), is_valid=True, alt_uncert=src_error.to(u.rad), az_uncert=src_error.to(u.rad), h_max=x_max, h_max_uncert=u.Quantity(np.nan * x_max.unit), goodness_of_fit=np.nan, ) return result
def test_intersection_nominal_reconstruction(): """ Testing the reconstruction of the position in the nominal frame with a three-telescopes system. This is done using a squared configuration, of which the impact point occupies a vertex, ad the three telescopes the other three vertices. """ hill_inter = HillasIntersection() delta = 1.0 * u.m horizon_frame = AltAz() altitude = 70 * u.deg azimuth = 10 * u.deg array_direction = SkyCoord(alt=altitude, az=azimuth, frame=horizon_frame) nominal_frame = NominalFrame(origin=array_direction) focal_length = 28 * u.m camera_frame = CameraFrame(focal_length=focal_length, telescope_pointing=array_direction) cog_coords_camera_1 = SkyCoord(x=delta, y=0 * u.m, frame=camera_frame) cog_coords_camera_2 = SkyCoord(x=delta / 0.7, y=delta / 0.7, frame=camera_frame) cog_coords_camera_3 = SkyCoord(x=0 * u.m, y=delta, frame=camera_frame) cog_coords_nom_1 = cog_coords_camera_1.transform_to(nominal_frame) cog_coords_nom_2 = cog_coords_camera_2.transform_to(nominal_frame) cog_coords_nom_3 = cog_coords_camera_3.transform_to(nominal_frame) # x-axis is along the altitude and y-axis is along the azimuth hillas_1 = HillasParametersContainer( x=cog_coords_nom_1.fov_lat, y=cog_coords_nom_1.fov_lon, intensity=100, psi=0 * u.deg, ) hillas_2 = HillasParametersContainer( x=cog_coords_nom_2.fov_lat, y=cog_coords_nom_2.fov_lon, intensity=100, psi=45 * u.deg, ) hillas_3 = HillasParametersContainer( x=cog_coords_nom_3.fov_lat, y=cog_coords_nom_3.fov_lon, intensity=100, psi=90 * u.deg, ) hillas_dict = {1: hillas_1, 2: hillas_2, 3: hillas_3} reco_nominal = hill_inter.reconstruct_nominal( hillas_parameters=hillas_dict) nominal_pos = SkyCoord( fov_lon=u.Quantity(reco_nominal[0], u.rad), fov_lat=u.Quantity(reco_nominal[1], u.rad), frame=nominal_frame, ) np.testing.assert_allclose(nominal_pos.altaz.az.to_value(u.deg), azimuth.to_value(u.deg), atol=1e-8) np.testing.assert_allclose(nominal_pos.altaz.alt.to_value(u.deg), altitude.to_value(u.deg), atol=1e-8)
def plot_muon_event(event, muonparams, args=None): if muonparams['MuonRingParams'] is not None: # Plot the muon event and overlay muon parameters fig = plt.figure(figsize=(16, 7)) colorbar = None colorbar2 = None #for tel_id in event.dl0.tels_with_data: for tel_id in muonparams['TelIds']: idx = muonparams['TelIds'].index(tel_id) if not muonparams['MuonRingParams'][idx]: continue #otherwise... npads = 2 # Only create two pads if there is timing information extracted # from the calibration ax1 = fig.add_subplot(1, npads, 1) plotter = CameraPlotter(event) image = event.dl1.tel[tel_id].image[0] geom = event.inst.subarray.tel[tel_id].camera tailcuts = (5., 7.) # Try a higher threshold for if geom.cam_id == 'FlashCam': tailcuts = (10., 12.) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) signals = image * clean_mask #print("Ring Centre in Nominal Coords:",muonparams[0].ring_center_x,muonparams[0].ring_center_y) muon_incl = np.sqrt(muonparams['MuonRingParams'][idx].ring_center_x**2. + muonparams['MuonRingParams'][idx].ring_center_y**2.) muon_phi = np.arctan(muonparams['MuonRingParams'][idx].ring_center_y / muonparams['MuonRingParams'][idx].ring_center_x) rotr_angle = geom.pix_rotation # if event.inst.optical_foclen[tel_id] > 10.*u.m and # event.dl0.tel[tel_id].num_pixels != 1764: if geom.cam_id == 'LSTCam' or geom.cam_id == 'NectarCam': #print("Resetting the rotation angle") rotr_angle = 0. * u.deg # Convert to camera frame (centre & radius) altaz = HorizonFrame(alt=event.mc.alt, az=event.mc.az) ring_nominal = NominalFrame(x=muonparams['MuonRingParams'][idx].ring_center_x, y=muonparams['MuonRingParams'][idx].ring_center_y, array_direction=altaz, pointing_direction=altaz) # embed() ring_camcoord = ring_nominal.transform_to(CameraFrame( pointing_direction=altaz, focal_length=event.inst.optical_foclen[tel_id], rotation=rotr_angle)) centroid_rad = np.sqrt(ring_camcoord.y**2 + ring_camcoord.x**2) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) ringrad_camcoord = muonparams['MuonRingParams'][idx].ring_radius.to(u.rad) \ * event.inst.optical_foclen[tel_id] * 2. # But not FC? px, py = event.inst.pixel_pos[tel_id] flen = event.inst.optical_foclen[tel_id] camera_coord = CameraFrame(x=px, y=py, z=np.zeros(px.shape) * u.m, focal_length=flen, rotation=geom.pix_rotation) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=altaz, pointing_direction=altaz) ) px = nom_coord.x.to(u.deg) py = nom_coord.y.to(u.deg) dist = np.sqrt(np.power( px - muonparams['MuonRingParams'][idx].ring_center_x, 2) + np.power(py - muonparams['MuonRingParams'][idx].ring_center_y, 2)) ring_dist = np.abs(dist - muonparams['MuonRingParams'][idx].ring_radius) pixRmask = ring_dist < muonparams['MuonRingParams'][idx].ring_radius * 0.4 #if muonparams[1] is not None: if muonparams['MuonIntensityParams'][idx] is not None: signals *= muonparams['MuonIntensityParams'][idx].mask camera1 = plotter.draw_camera(tel_id, signals, ax1) cmaxmin = (max(signals) - min(signals)) cmin = min(signals) if not cmin: cmin = 1. if not cmaxmin: cmaxmin = 1. cmap_charge = colors.LinearSegmentedColormap.from_list( 'cmap_c', [(0 / cmaxmin, 'darkblue'), (np.abs(cmin) / cmaxmin, 'black'), (2.0 * np.abs(cmin) / cmaxmin, 'blue'), (2.5 * np.abs(cmin) / cmaxmin, 'green'), (1, 'yellow')] ) camera1.pixels.set_cmap(cmap_charge) if not colorbar: camera1.add_colorbar(ax=ax1, label=" [photo-electrons]") colorbar = camera1.colorbar else: camera1.colorbar = colorbar camera1.update(True) camera1.add_ellipse(centroid, ringrad_camcoord.value, ringrad_camcoord.value, 0., 0., color="red") if muonparams['MuonIntensityParams'][idx] is not None: # continue #Comment this...(should ringwidthfrac also be *0.5?) ringwidthfrac = muonparams['MuonIntensityParams'][idx].ring_width / muonparams['MuonRingParams'][idx].ring_radius ringrad_inner = ringrad_camcoord * (1. - ringwidthfrac) ringrad_outer = ringrad_camcoord * (1. + ringwidthfrac) camera1.add_ellipse(centroid, ringrad_inner.value, ringrad_inner.value, 0., 0., color="magenta") camera1.add_ellipse(centroid, ringrad_outer.value, ringrad_outer.value, 0., 0., color="magenta") npads = 2 ax2 = fig.add_subplot(1, npads, npads) pred = muonparams['MuonIntensityParams'][idx].prediction if len(pred) != np.sum(muonparams['MuonIntensityParams'][idx].mask): print("Warning! Lengths do not match...len(pred)=", len(pred), "len(mask)=", np.sum(muonparams['MuonIntensityParams'][idx].mask)) # Numpy broadcasting - fill in the shape plotpred = np.zeros(image.shape) plotpred[muonparams['MuonIntensityParams'][idx].mask == True] = pred camera2 = plotter.draw_camera(tel_id, plotpred, ax2) if np.isnan(max(plotpred)) or np.isnan(min(plotpred)): print("nan prediction, skipping...") continue c2maxmin = (max(plotpred) - min(plotpred)) if not c2maxmin: c2maxmin = 1. c2map_charge = colors.LinearSegmentedColormap.from_list( 'c2map_c', [(0 / c2maxmin, 'darkblue'), (np.abs(min(plotpred)) / c2maxmin, 'black'), (2.0 * np.abs(min(plotpred)) / c2maxmin, 'blue'), (2.5 * np.abs(min(plotpred)) / c2maxmin, 'green'), (1, 'yellow')] ) camera2.pixels.set_cmap(c2map_charge) if not colorbar2: camera2.add_colorbar(ax=ax2, label=" [photo-electrons]") colorbar2 = camera2.colorbar else: camera2.colorbar = colorbar2 camera2.update(True) plt.pause(1.) # make shorter # plt.pause(0.1) # if pp is not None: # pp.savefig(fig) #fig.savefig(str(args.output_path) + "_" + # str(event.dl0.event_id) + '.png') plt.close()
def initialize_hillas_planes(self, hillas_dict, subarray, telescopes_pointings, array_pointing): """ Creates a dictionary of :class:`.HillasPlane` from a dictionary of hillas parameters Parameters ---------- hillas_dict : dictionary dictionary of hillas moments subarray : ctapipe.instrument.SubarrayDescription subarray information telescopes_pointings: dictionary dictionary of pointing direction per each telescope array_pointing: SkyCoord[AltAz] pointing direction of the array """ self.hillas_planes = {} k = next(iter(telescopes_pointings)) horizon_frame = telescopes_pointings[k].frame for tel_id, moments in hillas_dict.items(): pointing = SkyCoord( alt=telescopes_pointings[tel_id].alt, az=telescopes_pointings[tel_id].az, frame=horizon_frame, ) if moments.x.unit == u.Unit( "m"): # Image parameters are in CameraFrame # we just need any point on the main shower axis a bit away from the cog p2_x = moments.x + 0.1 * u.m * np.cos(moments.psi) p2_y = moments.y + 0.1 * u.m * np.sin(moments.psi) focal_length = subarray.tel[ tel_id].optics.equivalent_focal_length camera_frame = CameraFrame(focal_length=focal_length, telescope_pointing=pointing) cog_coord = SkyCoord( x=moments.x, y=moments.y, frame=camera_frame, ) p2_coord = SkyCoord(x=p2_x, y=p2_y, frame=camera_frame) # ============ # DIVERGENT # re-project from sky to a "fake"-parallel-pointing telescope # then recalculate the psi angle # WARNING: this part will need to be reproduced accordingly in the TelescopeFrame case! if self.divergent_mode: camera_frame_parallel = CameraFrame( focal_length=focal_length, telescope_pointing=array_pointing) cog_sky_to_parallel = cog_coord.transform_to( camera_frame_parallel) p2_sky_to_parallel = p2_coord.transform_to( camera_frame_parallel) angle_psi_corr = np.arctan2( cog_sky_to_parallel.y - p2_sky_to_parallel.y, cog_sky_to_parallel.x - p2_sky_to_parallel.x, ) self.corrected_angle_dict[tel_id] = angle_psi_corr # ============ else: # Image parameters are already in TelescopeFrame # we just need any point on the main shower axis a bit away from the cog p2_x = moments.x + 0.1 * u.deg * np.cos(moments.psi) p2_y = moments.y + 0.1 * u.deg * np.sin(moments.psi) telescope_frame = TelescopeFrame(telescope_pointing=pointing) cog_coord = SkyCoord( fov_lon=moments.x, fov_lat=moments.y, frame=telescope_frame, ) p2_coord = SkyCoord(fov_lon=p2_x, fov_lat=p2_y, frame=telescope_frame) cog_coord = cog_coord.transform_to(horizon_frame) p2_coord = p2_coord.transform_to(horizon_frame) circle = HillasPlane( p1=cog_coord, p2=p2_coord, telescope_position=subarray.positions[tel_id], weight=moments.intensity * (moments.length / moments.width), ) self.hillas_planes[tel_id] = circle
disp.image = data[:, ii] disp.set_limits_percent(70) plt.suptitle("Sample {:03d}".format(ii)) plt.pause(0.01) if args.write: plt.savefig('CT{:03d}_EV{:010d}_S{:02d}.png' .format(args.tel, event.dl0.event_id, ii)) else: # display integrated event: im = event.dl0.tel[args.tel].adc_sums[args.channel] im = apply_mc_calibration(im, args.tel) disp.image = im if args.hillas: clean_mask = reco.cleaning.tailcuts_clean(geom,im,1,picture_thresh=10,boundary_thresh=5) camera_coord = CameraFrame(x=x,y=y,z=np.zeros(x.shape)*u.m) nom_coord = camera_coord.transform_to(NominalFrame(array_direction=[70*u.deg,0*u.deg], pointing_direction=[70*u.deg,0*u.deg], focal_length=tel['TelescopeTable_VersionFeb2016'][tel['TelescopeTable_VersionFeb2016']['TelID']==args.tel]['FL'][0]*u.m)) image = np.asanyarray(im * clean_mask, dtype=np.float64) nom_x = nom_coord.x nom_y = nom_coord.y hillas = reco.hillas_parameters(x,y,im * clean_mask) hillas_nom = reco.hillas_parameters(nom_x,nom_y,im * clean_mask) print (hillas) print (hillas_nom)
def fill_pixel_wise_info(self, table, mask, histogram_binnings, focal_length, geom, event_type=''): """ Fills the quantities that are calculated pixel-wise Parameters ---------- table: DL1 parameters, event-wise astropy table "image" from DL1 files mask: indicates rows that have to be used for filling this container histogram_binnings: container of type DL1DataCheckHistogramBins, with definition of the binnings of all the histograms focal_length: quantity; telescope focal length geom: camera geometry, ctapipe.instrument.camera.geometry.CameraGeometry event_type: 'pedestals' 'flatfield' or 'cosmics' Returns ------- None """ charge = table['image'][mask] # average charge in each pixel through the subrun: self.charge_mean = charge.mean(axis=0) self.charge_stddev = charge.std(axis=0) # count, for each pixel, the number of entries with charge>x pe: self.num_pulses_above_0010_pe = np.sum(charge > 10, axis=0) self.num_pulses_above_0030_pe = np.sum(charge > 30, axis=0) self.num_pulses_above_0100_pe = np.sum(charge > 100, axis=0) self.num_pulses_above_0300_pe = np.sum(charge > 300, axis=0) self.num_pulses_above_1000_pe = np.sum(charge > 1000, axis=0) counts, _, _ = \ plt.hist(charge[charge > 0].flatten(), bins=histogram_binnings.hist_pixelchargespectrum) self.hist_pixelchargespectrum = counts # Find bright stars (mag<=8 within 3 deg of telescope pointing) and # count how many of them are close to each pixel: # Just use the time in the middle of the subrun, from the sampled times: sampled_times = self.dragon_time obstime = Time(sampled_times[int(len(sampled_times) / 2)], scale='utc', format='unix') horizon_frame = AltAz(location=location, obstime=obstime) pointing = SkyCoord(az=self.mean_az_tel, alt=self.mean_alt_tel, frame=horizon_frame) bright_stars = get_bright_stars(pointing=pointing, radius=3 * u.deg, magnitude_cut=8) # Account for average relative spot shift (outwards) due to coma # aberration: relative_shift = 1.0466 # For LST's paraboloid camera_frame = CameraFrame(telescope_pointing=pointing, focal_length=focal_length * relative_shift, obstime=obstime, location=location) telescope_frame = TelescopeFrame(obstime=obstime, location=location) # radius around star within which we consider the pixel may be affected # (hence we will later not raise a flag if e.g. its pedestal std dev is # high): r_around_star = 0.25 * u.deg stars = bright_stars['ra_dec'] pixels = SkyCoord(x=geom.pix_x, y=geom.pix_y, frame=camera_frame).transform_to(telescope_frame) angular_distance = pixels[:, np.newaxis].separation(stars) # This counts how many stars are close to each pixel; stars can be # counted more than once (for different pixels!) so don't add them up. self.num_nearby_stars = np.count_nonzero( angular_distance < r_around_star, axis=1) # for pedestal events nothing else to be done: if event_type == 'pedestals': return # For time plots we require at least 1 p.e. We will also exclude nans # from the calculations time = table['peak_time'][mask] with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) # Make nan all pulse times for charges less than 1 p.e.: time = np.where(charge > 1, time, np.nan) # count how many valid pixels per event: n_valid_pixels = np.count_nonzero(np.isfinite(time), axis=1) # mean and std dev for each pixel through the whole subrun: self.time_mean = np.nanmean(time, axis=0) self.time_stddev = np.nanstd(time, axis=0) # Now the average time in the camera, for each event: tmean = np.nanmean(time, axis=1) # We do the calculation of the relative times event by event, # instead of using events*pixels matrices, because keeping all # necessary matrices in memory to do it in one go results in too # large memory use (>5GB) for ievt, event_pixtimes in enumerate(time): # for each pixel we want the mean time of all the other pixels: mean_t_other = np.ones_like(event_pixtimes) * tmean[ievt] mean_t_other *= n_valid_pixels[ievt] mean_t_other -= event_pixtimes mean_t_other /= (n_valid_pixels[ievt] - 1) time[ievt] -= mean_t_other # Now time contains the times of each pixel relative to the average # of the rest of the pixels in the same event self.relative_time_mean = np.nanmean(time, axis=0) self.relative_time_stddev = np.nanstd(time, axis=0) if event_type == 'flatfield': return selected_entries = np.where(charge > 30, time, np.nan) self.time_mean_above_030_pe = np.nanmean(selected_entries, axis=0) self.time_stddev_above_030_pe = np.nanstd(selected_entries, axis=0)
container.dl0.tel = dict() # clear the previous telescopes table = "CameraTable_VersionFeb2016_TelID" for tel_id in container.dl0.tels_with_data: x, y = event.meta.pixel_pos[tel_id] if geom == 0: geom = io.CameraGeometry.guess(x, y,event.meta.optical_foclen[tel_id]) image = apply_mc_calibration(event.dl0.tel[tel_id].adc_sums[0], tel_id) if image.shape[0] >1000: continue clean_mask = tailcuts_clean(geom,image,1,picture_thresh=5,boundary_thresh=7) camera_coord = CameraFrame(x=x,y=y,z=np.zeros(x.shape)*u.m) nom_coord = camera_coord.transform_to(NominalFrame(array_direction=[container.mc.alt,container.mc.az], pointing_direction=[container.mc.alt,container.mc.az], focal_length=tel['TelescopeTable_VersionFeb2016'][tel['TelescopeTable_VersionFeb2016']['TelID']==tel_id]['FL'][0]*u.m)) x = nom_coord.x.to(u.deg) y = nom_coord.y.to(u.deg) img = image*clean_mask noise = 5 weight = img / (img+noise) centre_x,centre_y,radius = chaudhuri_kundu_circle_fit(x,y,image*clean_mask) dist = np.sqrt(np.power(x-centre_x,2) + np.power(y-centre_y,2)) ring_dist = np.abs(dist-radius)