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 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(subarray, tel_id, event_id, image, good_ring_config, plot_rings, plots_path): """ Analyze an event to fit a muon ring Parameters ---------- subarray: `ctapipe.instrument.subarray.SubarrayDescription` Telescopes subarray tel_id : `int` Id of the telescope used event_id : `int` Id of the analyzed event image : `np.ndarray` Number of photoelectrons in each pixel good_ring_config : `dict` or None Set of parameters used to identify good muon rings to update LST-1 defaults 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` Total intensity in ring in photoelectrons size_outside_ring : `float` Intensity outside the muon ring in photoelectrons to check for "shower contamination" muonringparam : `MuonRingContainer` 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 extractor bias muonparameters : `MuonParametersContainer` """ tel_description = subarray.tels[tel_id] cam_rad = (tel_description.camera.geometry.guess_radius() / tel_description.optics.equivalent_focal_length) * u.rad geom = tel_description.camera.geometry equivalent_focal_length = tel_description.optics.equivalent_focal_length mirror_area = tel_description.optics.mirror_area # some parameters for analysis and cuts for good ring selection: params = update_parameters(good_ring_config, geom.n_pixels) x, y = pixel_coords_to_telescope(geom, equivalent_focal_length) muonringparam, clean_mask, dist, image_clean = fit_muon( x, y, image, geom, params['tailcuts']) mirror_radius = np.sqrt(mirror_area / np.pi) # meters dist_mask = np.abs( dist - muonringparam.radius ) < muonringparam.radius * params['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 * (params['ring_integration_width'] + params['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'] < params['max_radial_stdev'], radial_distribution['excess_kurtosis'] < params['max_radial_excess_kurtosis'], (pix_ring > params['tailcuts'][0]).sum() > params['min_pix_fraction_after_cleaning'] * params['min_pix'], np.count_nonzero(pix_ring) > params['min_pix'], muonringparam.radius < params['max_ring_radius'], muonringparam.radius > params['min_ring_radius'] ]) if candidate_clean_ring: intensity_fitter = MuonIntensityFitter(subarray, hole_radius_m=0.308) # 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(tel_id, 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=params['ring_completeness_threshold'], 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 < params['max_impact_parameter'] * mirror_radius, muonintensityoutput.impact > params['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 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