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 analyze_muon_event(event): """ Generic muon event analyzer. Parameters ---------- event : ctapipe dl1 event container Returns ------- muonringparam, muonintensityparam : MuonRingParameter and MuonIntensityParameter container event """ names = ['LST:LSTCam', 'MST:NectarCam', '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[0] # 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=HorizonFrame() ) 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 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', 'SST-ASTRI:CHEC' ] TailCuts = [(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 TotalPix = [1855., 1855., 1764., 11328., 1296., 2048., 2368., 2048] # 8% (or 6%) as limit MinPix = [148., 148., 141., 680., 104., 164., 142., 164] # 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, 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] muon_cuts = { 'Name': names, 'TailCuts': TailCuts, 'Impact': impact, 'RingWidth': ringwidth, 'TotalPix': TotalPix, 'MinPix': MinPix, 'CamRad': cam_rad, 'SecRad': sec_rad, 'SCT': sct, 'AngPixW': AngPixelWidth, '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) muonringparam = None muonintensityparam = None image = event.dl1.tel[telid].image[0] # 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['TailCuts'][dict_index] logger.debug("Tailcuts are %s", tailcuts) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) camera_coord = CameraFrame( x=x, y=y, focal_length=teldes.optics.equivalent_focal_length, 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 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['MinPix'][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 (np.sum(pix_im > tailcuts[0]) > 0.1 * minpix and np.sum(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): # 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['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.obs_id = event.dl0.obs_id muonintensityoutput.event_id = event.dl0.event_id muonintensityoutput.mask = dist_mask 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 analyze_muon_event(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 plot_rings: `bool` plot the muon ring plots_path: `string` path to store the figures Returns --------- muonintensityoutput `` muonringparam `` good_ring `bool` it determines whether the ring can be used for analysis or not TODO: several hard-coded quantities that can go into a configuration file """ tailcuts = [10, 5] cam_rad = 2.26 * u.deg # 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) dist_mask = np.abs(dist - muonringparam.ring_radius ) < muonringparam.ring_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.ring_radius) < muonringparam.ring_radius * (ring_integration_width + outer_ring_width)) pix_ring_2 = image[dist_mask_2] # nom_dist = np.sqrt(np.power(muonringparam.ring_center_x,2) # + np.power(muonringparam.ring_center_y, 2)) muonringparam.ring_containment = ring_containment( muonringparam.ring_radius, cam_rad, muonringparam.ring_center_x, muonringparam.ring_center_y) radial_distribution = radial_light_distribution( muonringparam.ring_center_x, muonringparam.ring_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, npix_above_threshold( pix_ring, tailcuts[0]) > min_pix_fraction_after_cleaning * min_pix, npix_composing_ring(pix_ring) > min_pix, muonringparam.ring_radius < max_ring_radius, muonringparam.ring_radius > min_ring_radius ]) if candidate_clean_ring: ctel = MuonLineIntegrate(mirror_radius, hole_radius=0.308 * u.m, pixel_width=0.1 * u.deg, sct_flag=False, secondary_radius=0. * u.m) muonintensityoutput = ctel.fit_muon(muonringparam.ring_center_x, muonringparam.ring_center_y, muonringparam.ring_radius, x[dist_mask], y[dist_mask], image[dist_mask]) dist_ringwidth_mask = np.abs(dist - muonringparam.ring_radius) < ( muonintensityoutput.ring_width) # We do the calculation of the ring completeness (i.e. fraction of whole circle) using the pixels # within the "width" fitted using MuonLineIntegrate. muonintensityoutput.ring_completeness = ring_completeness( x[dist_ringwidth_mask], y[dist_ringwidth_mask], image[dist_ringwidth_mask], muonringparam.ring_radius, muonringparam.ring_center_x, muonringparam.ring_center_y, threshold=30, bins=30) pix_ringwidth_im = image[dist_ringwidth_mask] muonintensityoutput.ring_pix_completeness = npix_above_threshold( pix_ringwidth_im, tailcuts[0]) / len(pix_ringwidth_im) else: muonintensityoutput = MuonIntensityParameter() # Set default values for cases in which the muon intensity fit (with MuonLineIntegrate) is not done: muonintensityoutput.ring_width = np.nan * u.deg muonintensityoutput.impact_parameter_pos_x = np.nan * u.m muonintensityoutput.impact_parameter_pos_y = np.nan * u.m muonintensityoutput.impact_parameter = np.nan * u.m muonintensityoutput.ring_pix_completeness = np.nan muonintensityoutput.ring_completeness = np.nan muonintensityoutput.mask = dist_mask muonintensityoutput.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_parameter, muonintensityoutput.ring_width, muonringparam.ring_radius, muonintensityoutput.ring_completeness, )) # Now add the conditions based on the detailed muon ring fit made by MuonLineIntegrate: conditions = [ candidate_clean_ring, muonintensityoutput.impact_parameter < max_impact_parameter * mirror_radius, muonintensityoutput.impact_parameter > min_impact_parameter * mirror_radius, # TODO: To be applied when we have decent optics. # muonintensityoutput.ring_width # < 0.08, # NOTE: inside "candidate_clean_ring" cuts there is already a cut in the st dev of light distribution along ring radius, # which is also a measure of the ring width # muonintensityoutput.ring_width # > 0.04 ] muonintensityparam = muonintensityoutput 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(delta_az=muonringparam.ring_center_x, delta_alt=muonringparam.ring_center_y, frame=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.ring_radius width = muonintensityoutput.ring_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( muonintensityoutput.ring_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 muonintensityparam, size_outside_ring, muonringparam, good_ring, radial_distribution, mean_pixel_charge_around_ring
def analyze_muon_event(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 plot_rings: `bool` plot the muon ring plots_path: `string` path to store the figures Returns --------- muonintensityoutput `` muonringparam `` good_ring `bool` it determines whether the ring can be used for analysis or not TODO: several hard-coded quantities that can go into a configuration file """ tailcuts = [5, 10] cam_rad = 2.26 * u.deg min_pix = 148 # 8% x, y = get_muon_center(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) dist_mask = np.abs( dist - muonringparam.ring_radius) < muonringparam.ring_radius * 0.4 pix_ring = image * dist_mask pix_out_ring = image * ~dist_mask nom_dist = np.sqrt( np.power(muonringparam.ring_center_x, 2) + np.power(muonringparam.ring_center_y, 2)) muonringparam.ring_containment = ring_containment( muonringparam.ring_radius, cam_rad, muonringparam.ring_center_x, muonringparam.ring_center_y) ctel = MuonLineIntegrate(mirror_radius, hole_radius=0.308 * u.m, pixel_width=0.1 * u.deg, sct_flag=False, secondary_radius=0. * u.m) 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.mask = dist_mask idx_ring = np.nonzero(pix_ring) muonintensityoutput.ring_completeness = ring_completeness( x[idx_ring], y[idx_ring], pix_ring[idx_ring], muonringparam.ring_radius, muonringparam.ring_center_x, muonringparam.ring_center_y, threshold=30, bins=30) muonintensityoutput.ring_size = np.sum(pix_ring) size_outside_ring = np.sum(pix_out_ring * clean_mask) 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_ring[idx_ringwidth]) print( "Impact parameter = %s" "ring_width=%s, ring radius=%s, ring completeness=%s" % (muonintensityoutput.impact_parameter, muonintensityoutput.ring_width, muonringparam.ring_radius, muonintensityoutput.ring_completeness)) conditions = [ muonintensityoutput.impact_parameter < 0.9 * mirror_radius, # 90% inside the mirror muonintensityoutput.impact_parameter > 0.2 * mirror_radius, # 20% inside the mirror npix_above_threshold(pix_ring, tailcuts[0]) > 0.1 * min_pix, npix_composing_ring(pix_ring) > min_pix, muonringparam.ring_radius < 1.5 * u.deg, muonringparam.ring_radius > 1. * u.deg # TODO: To be applied when we have decent optics # muonintensityoutput.ring_width # < 0.08, # muonintensityoutput.ring_width # > 0.04 ] muonintensityparam = muonintensityoutput if all(conditions): good_ring = True else: good_ring = False if (plot_rings and plots_path and good_ring): altaz = AltAz(alt=70 * u.deg, az=0 * u.deg) focal_length = equivalent_focal_length ring_nominal = SkyCoord(delta_az=muonringparam.ring_center_x, delta_alt=muonringparam.ring_center_y, frame=NominalFrame(origin=altaz)) ring_camcoord = ring_nominal.transform_to( CameraFrame(focal_length=focal_length, rotation=geom.pix_rotation, telescope_pointing=altaz)) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) radius = muonringparam.ring_radius width = muonintensityoutput.ring_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) 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 muonintensityparam, size_outside_ring, muonringparam, good_ring