Exemple #1
0
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)
Exemple #4
0
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
Exemple #7
0
    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
Exemple #8
0
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
Exemple #11
0
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
Exemple #12
0
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))
Exemple #13
0
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
Exemple #14
0
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
Exemple #15
0
    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,
        )
Exemple #16
0
    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,
        )
Exemple #17
0
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)
Exemple #18
0
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
Exemple #19
0
    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
Exemple #20
0
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
Exemple #23
0
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()
Exemple #24
0
            # 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)
Exemple #25
0
            # 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
Exemple #29
0
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])
Exemple #31
0
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()
Exemple #36
0
    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
Exemple #37
0
                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)
Exemple #38
0
    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)
Exemple #39
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)