Exemplo n.º 1
0
    def fit_muon(self, centre_x, centre_y, radius, pixel_x, pixel_y, image):
        """

        Parameters
        ----------
        centre_x: float
            Centre of muon ring in the field of view from circle fitting
        centre_y: float
            Centre of muon ring in the field of view from circle fitting
        radius: float
            Radius of muon ring from circle fitting
        pixel_x: ndarray
            X position of pixels in image from circle fitting
        pixel_y: ndarray
            Y position of pixel in image from circle fitting
        image: ndarray
            Amplitude of image pixels

        Returns
        -------
        MuonIntensityParameters
        """

        # First store these parameters in the class so we can use them in minimisation
        self.image = image
        self.pixel_x = pixel_x.to(u.deg)
        self.pixel_y = pixel_y.to(u.deg)
        self.unit = pixel_x.unit

        radius.to(u.deg)
        centre_x.to(u.deg)
        centre_y.to(u.deg)

        # Return interesting stuff
        fitoutput = MuonIntensityParameter()

        init_params = {}
        init_errs = {}
        init_constrain = {}
        init_params['impact_parameter'] = 4.
        init_params['phi'] = 0.
        init_params['radius'] = radius.value
        init_params['centre_x'] = centre_x.value
        init_params['centre_y'] = centre_y.value
        init_params['ring_width'] = 0.1
        init_params['optical_efficiency_muon'] = 0.1
        init_errs['error_impact_parameter'] = 2.
        init_constrain['limit_impact_parameter'] = (0.,25.)
        init_errs['error_phi'] = 0.1
        init_errs['error_ring_width'] = 0.001 * radius.value
        init_errs['error_optical_efficiency_muon'] = 0.05
        init_constrain['limit_phi'] = (-np.pi,np.pi)
        init_constrain['fix_radius'] = True
        init_constrain['fix_centre_x'] = True
        init_constrain['fix_centre_y'] = True
        init_constrain['limit_ring_width'] = (0.,1.)
        init_constrain['limit_optical_efficiency_muon'] = (0.,1.)

        print("radius =",radius," pre migrad")

        parameter_names = init_params.keys()

        # Create Minuit object with first guesses at parameters
        # strip away the units as Minuit doesnt like them
        minuit = Minuit(
            self.likelihood,
            #forced_parameters=parameter_names,
            **init_params,
            **init_errs,
            **init_constrain,
            errordef=1.,
            print_level=1
            #pedantic=False
        )

        # Perform minimisation
        minuit.migrad()
            
        # Get fitted values
        fit_params = minuit.values
        fitoutput.impact_parameter = fit_params['impact_parameter']*u.m
        #fitoutput.phi = fit_params['phi']*u.rad
        fitoutput.impact_parameter_pos_x = fit_params['impact_parameter'] * np.cos(fit_params['phi'] * u.rad) * u.m
        fitoutput.impact_parameter_pos_y = fit_params['impact_parameter'] * np.sin(fit_params['phi'] * u.rad) * u.m
        fitoutput.ring_width = fit_params['ring_width']*self.unit
        fitoutput.optical_efficiency_muon = fit_params['optical_efficiency_muon']

        fitoutput.prediction = self.prediction

        return fitoutput
Exemplo n.º 2
0
    def fit_muon(self, centre_x, centre_y, radius, pixel_x, pixel_y, image):
        """

        Parameters
        ----------
        centre_x: float
            Centre of muon ring in the field of view from circle fitting
        centre_y: float
            Centre of muon ring in the field of view from circle fitting
        radius: float
            Radius of muon ring from circle fitting
        pixel_x: ndarray
            X position of pixels in image from circle fitting
        pixel_y: ndarray
            Y position of pixel in image from circle fitting
        image: ndarray
            Amplitude of image pixels

        Returns
        -------
        MuonIntensityParameters
        """

        # First store these parameters in the class so we can use them in minimisation
        self.image = image
        self.pixel_x = pixel_x.to(u.deg)
        self.pixel_y = pixel_y.to(u.deg)
        self.unit = pixel_x.unit

        radius.to(u.deg)
        centre_x.to(u.deg)
        centre_y.to(u.deg)

        # Return interesting stuff
        fitoutput = MuonIntensityParameter()



        # Create Minuit object with first guesses at parameters
        # strip away the units as Minuit doesnt like them
        minuit = Minuit(
            self.likelihood,
            impact_parameter=4,
            limit_impact_parameter=(0, 25),
            error_impact_parameter=5,
            phi=0,
            limit_phi=(-np.pi, np.pi),
            error_phi=0.1,
            radius=radius.value,
            fix_radius=True,
            error_radius=0.,
            centre_x=centre_x.value,
            fix_centre_x=True,
            error_centre_x=0.,
            centre_y=centre_y.value,
            fix_centre_y=True,
            error_centre_y=0.,
            ring_width=0.1,
            error_ring_width=0.001*radius.value,
            limit_ring_width=(0, 1),
            optical_efficiency_muon=0.1,
            error_optical_efficiency_muon=0.05,
            limit_optical_efficiency_muon=(0, 1),
            throw_nan=False,
            print_level=0,
            pedantic=False,
            errordef=1,
        )

        # Perform minimisation
        minuit.migrad()
            
        # Get fitted values
        fit_params = minuit.values
        fitoutput.impact_parameter = fit_params['impact_parameter']*u.m
        #fitoutput.phi = fit_params['phi']*u.rad
        fitoutput.ring_width = fit_params['ring_width']*self.unit
        fitoutput.optical_efficiency_muon = fit_params['optical_efficiency_muon']

        #embed()
        #
        fitoutput.prediction = self.prediction

        return fitoutput
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    def fit_muon(self, centre_x, centre_y, radius, pixel_x, pixel_y, image):
        """

        Parameters
        ----------
        centre_x: float
            Centre of muon ring in the field of view from circle fitting
        centre_y: float
            Centre of muon ring in the field of view from circle fitting
        radius: float
            Radius of muon ring from circle fitting
        pixel_x: ndarray
            X position of pixels in image from circle fitting
        pixel_y: ndarray
            Y position of pixel in image from circle fitting
        image: ndarray
            Amplitude of image pixels

        Returns
        -------
        MuonIntensityParameters
        """

        # First store these parameters in the class so we can use them in minimisation
        self.image = image
        self.pixel_x = pixel_x.to(u.deg)
        self.pixel_y = pixel_y.to(u.deg)
        self.unit = pixel_x.unit

        radius.to(u.deg)
        centre_x.to(u.deg)
        centre_y.to(u.deg)

        # Return interesting stuff
        fitoutput = MuonIntensityParameter()

        init_params = {}
        init_errs = {}
        init_constrain = {}
        init_params['impact_parameter'] = 4.
        init_params['phi'] = 0.
        init_params['radius'] = radius.value
        init_params['centre_x'] = centre_x.value
        init_params['centre_y'] = centre_y.value
        init_params['ring_width'] = 0.1
        init_params['optical_efficiency_muon'] = 0.1
        init_errs['error_impact_parameter'] = 2.
        init_constrain['limit_impact_parameter'] = (0., 25.)
        init_errs['error_phi'] = 0.1
        init_errs['error_ring_width'] = 0.001 * radius.value
        init_errs['error_optical_efficiency_muon'] = 0.05
        init_constrain['limit_phi'] = (-np.pi, np.pi)
        init_constrain['fix_radius'] = True
        init_constrain['fix_centre_x'] = True
        init_constrain['fix_centre_y'] = True
        init_constrain['limit_ring_width'] = (0., 1.)
        #init_constrain['limit_optical_efficiency_muon'] = (0., 1.) # Unneeded constraint - and misleading in case of changes leading to >1 values!

        logger.debug("radius = %3.3f pre migrad", radius)

        # Create Minuit object with first guesses at parameters
        # strip away the units as Minuit doesnt like them

        minuit = Minuit(
            self.likelihood,
            # forced_parameters=parameter_names,
            **init_params,
            **init_errs,
            **init_constrain,
            errordef=0.1,
            print_level=0,
            pedantic=False
        )

        # Perform minimisation
        minuit.migrad()

        # Get fitted values
        fit_params = minuit.values

        fitoutput.impact_parameter = fit_params['impact_parameter'] * u.m
        # fitoutput.phi = fit_params['phi']*u.rad
        fitoutput.impact_parameter_pos_x = fit_params['impact_parameter'] * np.cos(
            fit_params['phi'] * u.rad) * u.m
        fitoutput.impact_parameter_pos_y = fit_params['impact_parameter'] * np.sin(
            fit_params['phi'] * u.rad) * u.m
        fitoutput.ring_width = fit_params['ring_width'] * self.unit
        fitoutput.optical_efficiency_muon = fit_params['optical_efficiency_muon']

        fitoutput.prediction = self.prediction

        return fitoutput
Exemplo n.º 5
0
    def fit_muon(self, centre_x, centre_y, radius, pixel_x, pixel_y, image):
        """

        Parameters
        ----------
        centre_x: float
            Centre of muon ring in the field of view from circle fitting
        centre_y: float
            Centre of muon ring in the field of view from circle fitting
        radius: float
            Radius of muon ring from circle fitting
        pixel_x: ndarray
            X position of pixels in image from circle fitting
        pixel_y: ndarray
            Y position of pixel in image from circle fitting
        image: ndarray
            Amplitude of image pixels

        Returns
        -------
        MuonIntensityParameters
        """

        # First store these parameters in the class so we can use them in minimisation
        self.image = image
        self.pixel_x = pixel_x.to(u.deg)
        self.pixel_y = pixel_y.to(u.deg)
        self.unit = pixel_x.unit

        radius.to(u.deg)
        centre_x.to(u.deg)
        centre_y.to(u.deg)

        # Return interesting stuff
        fitoutput = MuonIntensityParameter()

        # Create Minuit object with first guesses at parameters
        # strip away the units as Minuit doesnt like them
        minuit = Minuit(
            self.likelihood,
            impact_parameter=4,
            limit_impact_parameter=(0, 25),
            error_impact_parameter=5,
            phi=0,
            limit_phi=(-np.pi, np.pi),
            error_phi=0.1,
            radius=radius.value,
            fix_radius=True,
            error_radius=0.,
            centre_x=centre_x.value,
            fix_centre_x=True,
            error_centre_x=0.,
            centre_y=centre_y.value,
            fix_centre_y=True,
            error_centre_y=0.,
            ring_width=0.1,
            error_ring_width=0.001 * radius.value,
            limit_ring_width=(0, 1),
            optical_efficiency_muon=0.1,
            error_optical_efficiency_muon=0.05,
            limit_optical_efficiency_muon=(0, 1),
            throw_nan=False,
            print_level=0,
            pedantic=False,
            errordef=1,
        )

        # Perform minimisation
        minuit.migrad()

        # Get fitted values
        fit_params = minuit.values
        fitoutput.impact_parameter = fit_params['impact_parameter'] * u.m
        #fitoutput.phi = fit_params['phi']*u.rad
        fitoutput.ring_width = fit_params['ring_width'] * self.unit
        fitoutput.optical_efficiency_muon = fit_params[
            'optical_efficiency_muon']

        #embed()
        #
        fitoutput.prediction = self.prediction

        return fitoutput