示例#1
0
def grd_to_tilt():
    grd_coord = GroundFrame(x=1 * u.m, y=2 * u.m, z=0 * u.m)
    tilt_coord = grd_coord.transform_to(
        TiltedGroundFrame(
            pointing_direction=AltAz(alt=90 * u.deg, az=180 * u.deg)))
    print(project_to_ground(tilt_coord))
    print("Tilted Coordinate", tilt_coord)
def grd_to_tilt():
    grd_coord = GroundFrame(x=1 * u.m, y=2 * u.m, z=0 * u.m)
    tilt_coord = grd_coord.transform_to(
        TiltedGroundFrame(
            pointing_direction=HorizonFrame(alt=90 * u.deg, az=180 * u.deg)
        )
    )
    print(project_to_ground(tilt_coord))
    print("Tilted Coordinate", tilt_coord)
示例#3
0
    def plot_hillas_lines(self, hillas_dict, length, frame="ground"):

        # xx, yy, zz = np.array(spherical_to_cartesian(
        #     1,
        #     self.array_pointing.alt,
        #     0 * u.deg)
        # )  # -self.array_pointing.az))
        # # plane = Plane(Point3D(0, 0, 0), normal_vector=(xx, yy, zz))

        tilted_system = self.tilted_frame
        tel_coords_gnd = self.subarray.tel_coords
        tilt_tel_pos = tel_coords_gnd.transform_to(tilted_system)

        for tel_id in self.tel_ids:
            moments = hillas_dict[tel_id]
            if frame == "ground":
                color = [255, 0, 0]
                gnd_pos = project_to_ground(
                    tilt_tel_pos[self.subarray.tel_indices[tel_id]])
                tel_x_pos = gnd_pos.x.to_value(u.m)
                tel_y_pos = gnd_pos.y.to_value(u.m)
                tel_z_pos = gnd_pos.z.to_value(u.m)

            elif frame == "tilted":
                color = [0, 255, 0]
                # i need those coordinates in GroundFrame to transform to TiltedGroundFrame
                # The selection is done afterwards
                tel_x_pos = tilt_tel_pos[
                    self.subarray.tel_indices[tel_id]].x.to_value(u.m)
                tel_y_pos = tilt_tel_pos[
                    self.subarray.tel_indices[tel_id]].y.to_value(u.m)
                tel_z_pos = 0
                # tilted_tel_pos = tilt_tel_pos[self.subarray.tel_indices[tel_id]]

            hillas_line_actor = hillas_lines(
                moments=moments,
                length=length,
                tel_coords=[tel_x_pos, tel_y_pos, tel_z_pos],
                frame=frame,
                array_pointing=self.array_pointing,
                tilted_frame=tilted_system,
                # plane=plane,
            )

            # if frame == "tilted":
            #     hillas_line_actor.RotateZ(0)#self.array_pointing.az.value)
            #     hillas_line_actor.RotateY(0)#90 - self.array_pointing.alt.value)

            # self.tel_id[tel_id].append(hillas_line_actor)
            hillas_line_actor.GetProperty().SetColor(color)
            self.ren.AddActor(hillas_line_actor)
示例#4
0
    def estimate_core_position(self, hillas_dict, array_pointing):
        """
        Estimate the core position by intersection the major ellipse lines of each telescope.

        Parameters
        -----------
        hillas_dict: dict[HillasContainer]
            dictionary of hillas moments
        array_pointing: SkyCoord[HorizonFrame]
            Pointing direction of the array

        Returns
        -----------
        core_x: u.Quantity
            estimated x position of impact
        core_y: u.Quantity
            estimated y position of impact

        """
        if self.divergent_mode:
            psi = u.Quantity(list(self.corrected_angle_dict.values()))
        else:
            psi = u.Quantity([h.psi for h in hillas_dict.values()])

        z = np.zeros(len(psi))
        uvw_vectors = np.column_stack([np.cos(psi).value, np.sin(psi).value, z])

        tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing)
        ground_frame = GroundFrame()

        positions = [
            (
                SkyCoord(*plane.pos, frame=ground_frame)
                .transform_to(tilted_frame)
                .cartesian.xyz
            )
            for plane in self.hillas_planes.values()
        ]

        core_position = line_line_intersection_3d(uvw_vectors, positions)

        core_pos_tilted = SkyCoord(
            x=core_position[0] * u.m,
            y=core_position[1] * u.m,
            frame=tilted_frame
        )

        core_pos = project_to_ground(core_pos_tilted)

        return core_pos.x, core_pos.y
示例#5
0
    def estimate_core_position(self, hillas_dict, telescope_pointing):
        '''
        Estimate the core position by intersection the major ellipse lines of each telescope.

        Parameters
        -----------
        hillas_dict: dict[HillasContainer]
            dictionary of hillas moments
        telescope_pointing: SkyCoord[HorizonFrame]
            Pointing direction of the array

        Returns
        -----------
        core_x: u.Quantity
            estimated x position of impact
        core_y: u.Quantity
            estimated y position of impact

        '''
        psi = u.Quantity([h.psi for h in hillas_dict.values()])
        z = np.zeros(len(psi))
        uvw_vectors = np.column_stack([np.cos(psi).value, np.sin(psi).value, z])

        tilted_frame = TiltedGroundFrame(pointing_direction=telescope_pointing)
        ground_frame = GroundFrame()

        positions = [
            (
                SkyCoord(*plane.pos, frame=ground_frame)
                .transform_to(tilted_frame)
                .cartesian.xyz
            )
            for plane in self.hillas_planes.values()
        ]
        core_position = line_line_intersection_3d(uvw_vectors, positions)

        core_pos_tilted = SkyCoord(
            x=core_position[0] * u.m,
            y=core_position[1] * u.m,
            frame=tilted_frame
        )

        core_pos = project_to_ground(core_pos_tilted)

        return core_pos.x, core_pos.y
示例#6
0
    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
示例#7
0
    def predict(self, shower_seed, energy_seed):
        """
        Parameters
        ----------
        shower_seed: ReconstructedShowerContainer
            Seed shower geometry to be used in the fit
        energy_seed: ReconstructedEnergyContainer
            Seed energy to be used in fit

        Returns
        -------
        ReconstructedShowerContainer, ReconstructedEnergyContainer:
        Reconstructed ImPACT shower geometry and energy
        """

        horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt)
        nominal_seed = horizon_seed.transform_to(NominalFrame(
            array_direction=self.array_direction))

        source_x = nominal_seed.x.to(u.rad).value
        source_y = nominal_seed.y.to(u.rad).value
        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y, z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction)
        )
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value
        zenith = 90 * u.deg - self.array_direction.alt

        if len(self.hillas_parameters) > 3:
            shift = [1]
        else:
            shift = [1.5, 1, 0.5, 0, -0.5, -1, -1.5]

        seed_list = spread_line_seed(self.hillas_parameters,
                                     self.tel_pos_x, self.tel_pos_y,
                                     source_x[0], source_y[0], tilt_x, tilt_y,
                                     energy_seed.energy.value,
                                     shift_frac = shift)

        chosen_seed = self.choose_seed(seed_list)
        # Perform maximum likelihood fit
        fit_params, errors, like = self.minimise(params=chosen_seed[0],
                                                 step=chosen_seed[1],
                                                 limits=chosen_seed[2],
                                                 minimiser_name=self.minimiser_name)

        # Create a container class for reconstructed shower
        shower_result = ReconstructedShowerContainer()

        # Convert the best fits direction and core to Horizon and ground systems and
        # copy to the shower container
        nominal = NominalFrame(x=fit_params[0] * u.rad,
                               y=fit_params[1] * u.rad,
                               array_direction=self.array_direction)
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(x=fit_params[2] * u.m,
                                   y=fit_params[3] * u.m,
                                   pointing_direction=self.array_direction)
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        # Currently no errors not availible to copy NaN
        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan

        # Copy reconstructed Xmax
        shower_result.h_max = fit_params[5] * self.get_shower_max(fit_params[0],
                                                                  fit_params[1],
                                                                  fit_params[2],
                                                                  fit_params[3],
                                                                  zenith.to(u.rad).value)

        shower_result.h_max *= np.cos(zenith)
        shower_result.h_max_uncert = errors[5] * shower_result.h_max

        shower_result.goodness_of_fit = like

        # Create a container class for reconstructed energy
        energy_result = ReconstructedEnergyContainer()
        # Fill with results
        energy_result.energy = fit_params[4] * u.TeV
        energy_result.energy_uncert = errors[4] * u.TeV
        energy_result.is_valid = True

        return shower_result, energy_result
示例#8
0
    def predict(self, hillas_parameters, tel_x, tel_y, array_direction):
        """

        Parameters
        ----------
        hillas_parameters: dict
            Dictionary containing Hillas parameters for all telescopes
            in reconstruction
        tel_x: dict
            Dictionary containing telescope position on ground for all
            telescopes in reconstruction
        tel_y: dict
            Dictionary containing telescope position on ground for all
            telescopes in reconstruction
        array_direction: AltAz
            Pointing direction of the array

        Returns
        -------
        ReconstructedShowerContainer:

        """
        src_x, src_y, err_x, err_y = self.reconstruct_nominal(
            hillas_parameters)
        core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted(
            hillas_parameters, tel_x, tel_y)
        err_x *= u.rad
        err_y *= u.rad

        nom = SkyCoord(x=src_x * u.rad,
                       y=src_y * u.rad,
                       frame=NominalFrame(array_direction=array_direction))
        horiz = nom.transform_to(AltAz())

        result = ReconstructedShowerContainer()
        result.alt, result.az = horiz.alt, horiz.az

        tilt = SkyCoord(
            x=core_x * u.m,
            y=core_y * u.m,
            frame=TiltedGroundFrame(pointing_direction=array_direction),
        )
        grd = project_to_ground(tilt)
        result.core_x = grd.x
        result.core_y = grd.y

        x_max = self.reconstruct_xmax(
            nom.x,
            nom.y,
            tilt.x,
            tilt.y,
            hillas_parameters,
            tel_x,
            tel_y,
            90 * u.deg - array_direction.alt,
        )

        result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m

        result.tel_ids = [h for h in hillas_parameters.keys()]
        result.average_intensity = np.mean(
            [h.intensity for h in hillas_parameters.values()])
        result.is_valid = True

        src_error = np.sqrt(err_x**2 + err_y**2)
        result.alt_uncert = src_error.to(u.deg)
        result.az_uncert = src_error.to(u.deg)
        result.h_max = x_max
        result.h_max_uncert = np.nan
        result.goodness_of_fit = np.nan

        return result
示例#9
0
    def predict(self, shower_seed, energy_seed):
        """
        Parameters
        ----------
        shower_seed: ReconstructedShowerContainer
            Seed shower geometry to be used in the fit
        energy_seed: ReconstructedEnergyContainer
            Seed energy to be used in fit

        Returns
        -------
        ReconstructedShowerContainer, ReconstructedEnergyContainer:
        Reconstructed ImPACT shower geometry and energy
        """

        horizon_seed = SkyCoord(
            az=shower_seed.az, alt=shower_seed.alt, frame=HorizonFrame()
        )
        nominal_seed = horizon_seed.transform_to(
            NominalFrame(origin=self.array_direction)
        )

        source_x = nominal_seed.delta_az.to_value(u.rad)
        source_y = nominal_seed.delta_alt.to_value(u.rad)
        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y, z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction)
        )
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value
        zenith = 90 * u.deg - self.array_direction.alt

        if len(self.hillas_parameters) > 3:
            shift = [1]
        else:
            shift = [1.5, 1, 0.5, 0, -0.5, -1, -1.5]

        seed_list = spread_line_seed(self.hillas_parameters,
                                     self.tel_pos_x, self.tel_pos_y,
                                     source_x[0], source_y[0], tilt_x, tilt_y,
                                     energy_seed.energy.value,
                                     shift_frac = shift)

        chosen_seed = self.choose_seed(seed_list)
        # Perform maximum likelihood fit
        fit_params, errors, like = self.minimise(params=chosen_seed[0],
                                                 step=chosen_seed[1],
                                                 limits=chosen_seed[2],
                                                 minimiser_name=self.minimiser_name)

        # Create a container class for reconstructed shower
        shower_result = ReconstructedShowerContainer()

        # Convert the best fits direction and core to Horizon and ground systems and
        # copy to the shower container
        nominal = SkyCoord(
            x=fit_params[0] * u.rad,
            y=fit_params[1] * u.rad,
            frame=NominalFrame(origin=self.array_direction)
        )
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(
            x=fit_params[2] * u.m,
            y=fit_params[3] * u.m,
            pointing_direction=self.array_direction
        )
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        # Currently no errors not availible to copy NaN
        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan

        # Copy reconstructed Xmax
        shower_result.h_max = fit_params[5] * self.get_shower_max(fit_params[0],
                                                                  fit_params[1],
                                                                  fit_params[2],
                                                                  fit_params[3],
                                                                  zenith.to(u.rad).value)

        shower_result.h_max *= np.cos(zenith)
        shower_result.h_max_uncert = errors[5] * shower_result.h_max

        shower_result.goodness_of_fit = like

        # Create a container class for reconstructed energy
        energy_result = ReconstructedEnergyContainer()
        # Fill with results
        energy_result.energy = fit_params[4] * u.TeV
        energy_result.energy_uncert = errors[4] * u.TeV
        energy_result.is_valid = True

        return shower_result, energy_result
示例#10
0
def hillas_lines(moments,
                 length,
                 tel_coords,
                 frame,
                 array_pointing,
                 tilted_frame,
                 plane=None):

    angle_in = moments.psi.to_value(u.rad)

    # a = Point3D(1, 0, 0)
    # b = Point3D(np.cos(angle), np.sin(angle), 0)

    if frame == "tilted":
        angle = angle_in + np.pi / 2.

        # origin = Point3D(0, 0, 0)
        # line_a = Ray3D(origin, a)
        # line_b = Ray3D(origin, b)
        # angle_tilt = line_b.angle_between(line_a)
        # angle_tilted = (float(angle_tilt.evalf()) * u.rad).to_value(u.deg)

        a = np.array([1, 0, 0])
        b = np.array([np.cos(angle), np.sin(angle), 0])
        angle_tilt_np = u.Quantity(angle_util(a, b), u.rad).to_value(u.deg)
        psi = angle_tilt_np
    elif frame == "ground":
        # b_in_plane = plane.projection(b)
        # a_in_plane = plane.projection(a)
        # line_a_plane = Ray3D(origin, a_in_plane)
        # line_b_plane = Ray3D(origin, b_in_plane)
        # angle_gnd = line_b_plane.angle_between(line_a_plane)
        # angle_ground = (float(angle_gnd.evalf()) * u.rad).to_value(u.deg)
        # psi = angle_ground - array_pointing.az.to_value(u.deg)

        if angle_in < 0:
            angle_in = angle_in + np.pi

        angle = angle_in

        fix_a_in_plane = SkyCoord(x=1 * u.m, y=0 * u.m, frame=tilted_frame)
        fix_b_in_plane = SkyCoord(x=np.cos(angle) * u.m,
                                  y=np.sin(angle) * u.m,
                                  frame=tilted_frame)

        angle_fix_np = u.Quantity(
            angle_util(
                project_to_ground(fix_a_in_plane).cartesian.xyz.to_value(u.m),
                project_to_ground(fix_b_in_plane).cartesian.xyz.to_value(u.m)),
            u.rad).to_value(u.deg)

        psi = angle_fix_np - array_pointing.az.to_value(u.deg) + 90

    cylinder = vtk.vtkCylinderSource()
    cylinder.SetResolution(4)
    cylinder.SetRadius(1)
    cylinder.SetHeight(length)
    cylinder.Update()

    rotate_cylinder = rotate_object(cylinder, 'z', psi)

    mapper = vtk.vtkPolyDataMapper()
    mapper.SetInputConnection(rotate_cylinder.GetOutputPort())

    actor = vtk.vtkActor()
    actor.SetMapper(mapper)

    transform = vtk.vtkTransform()
    transform.PostMultiply()
    transform.Translate(*tel_coords)

    if frame == "tilted":
        transform.RotateY(90 - array_pointing.alt.value)
        transform.RotateZ(-array_pointing.az.value)

    actor.SetUserTransform(transform)

    return actor
示例#11
0
    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
示例#12
0
    def predict(self, shower_seed, energy_seed):
        """Predict method for the ImPACT reconstructor.
        Used to calculate the reconstructed ImPACT shower geometry and energy.

        Parameters
        ----------
        shower_seed: ReconstructedShowerContainer
            Seed shower geometry to be used in the fit
        energy_seed: ReconstructedEnergyContainer
            Seed energy to be used in fit

        Returns
        -------
        ReconstructedShowerContainer, ReconstructedEnergyContainer:
        """
        self.reset_interpolator()

        horizon_seed = SkyCoord(az=shower_seed.az,
                                alt=shower_seed.alt,
                                frame=AltAz())
        nominal_seed = horizon_seed.transform_to(self.nominal_frame)

        source_x = nominal_seed.fov_lon.to_value(u.rad)
        source_y = nominal_seed.fov_lat.to_value(u.rad)
        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y,
                             z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction))
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value
        zenith = 90 * u.deg - self.array_direction.alt

        seeds = spread_line_seed(
            self.hillas_parameters,
            self.tel_pos_x,
            self.tel_pos_y,
            source_x,
            source_y,
            tilt_x,
            tilt_y,
            energy_seed.energy.value,
            shift_frac=[1],
        )[0]

        # Perform maximum likelihood fit
        fit_params, errors, like = self.minimise(
            params=seeds[0],
            step=seeds[1],
            limits=seeds[2],
            minimiser_name=self.minimiser_name,
        )

        # Create a container class for reconstructed shower
        shower_result = ReconstructedGeometryContainer()

        # Convert the best fits direction and core to Horizon and ground systems and
        # copy to the shower container
        nominal = SkyCoord(
            fov_lon=fit_params[0] * u.rad,
            fov_lat=fit_params[1] * u.rad,
            frame=self.nominal_frame,
        )
        horizon = nominal.transform_to(AltAz())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(
            x=fit_params[2] * u.m,
            y=fit_params[3] * u.m,
            pointing_direction=self.array_direction,
        )
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        # Currently no errors not available to copy NaN
        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan

        # Copy reconstructed Xmax
        shower_result.h_max = fit_params[5] * self.get_shower_max(
            fit_params[0],
            fit_params[1],
            fit_params[2],
            fit_params[3],
            zenith.to(u.rad).value,
        )

        shower_result.h_max *= np.cos(zenith)
        shower_result.h_max_uncert = errors[5] * shower_result.h_max

        shower_result.goodness_of_fit = like

        # Create a container class for reconstructed energy
        energy_result = ReconstructedEnergyContainer()
        # Fill with results
        energy_result.energy = fit_params[4] * u.TeV
        energy_result.energy_uncert = errors[4] * u.TeV
        energy_result.is_valid = True

        return shower_result, energy_result
示例#13
0
文件: ImPACT.py 项目: pmunar/ctapipe
    def predict(self, shower_seed, energy_seed):
        """
        Parameters
        ----------
        shower_seed: ReconstructedShowerContainer
            Seed shower geometry to be used in the fit
        energy_seed: ReconstructedEnergyContainer
            Seed energy to be used in fit

        Returns
        -------
        ReconstructedShowerContainer, ReconstructedEnergyContainer:
        Reconstructed ImPACT shower geometry and energy        
        """

        horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt)
        nominal_seed = horizon_seed.transform_to(
            NominalFrame(array_direction=self.array_direction))
        print(nominal_seed)
        print(horizon_seed)
        print(self.array_direction)

        source_x = nominal_seed.x[0].to(u.rad).value
        source_y = nominal_seed.y[0].to(u.rad).value

        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y,
                             z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction))
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value

        lower_en_limit = energy_seed.energy * 0.5
        en_seed = energy_seed.energy
        if lower_en_limit < 0.04 * u.TeV:
            lower_en_limit = 0.04 * u.TeV
            en_seed = 0.041 * u.TeV

        seed = (source_x, source_y, tilt_x, tilt_y, en_seed.value, 0.8)
        step = (0.001, 0.001, 10, 10, en_seed.value * 0.1, 0.1)
        limits = ((source_x - 0.01, source_x + 0.01), (source_y - 0.01,
                                                       source_y + 0.01),
                  (tilt_x - 100, tilt_x + 100), (tilt_y - 100, tilt_y + 100),
                  (lower_en_limit.value, en_seed.value * 2), (0.5, 2))

        fit_params, errors = self.minimise(params=seed,
                                           step=step,
                                           limits=limits,
                                           minimiser_name=self.minimiser_name)

        # container class for reconstructed showers '''
        shower_result = ReconstructedShowerContainer()

        nominal = NominalFrame(x=fit_params[0] * u.rad,
                               y=fit_params[1] * u.rad,
                               array_direction=self.array_direction)
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(x=fit_params[2] * u.m,
                                   y=fit_params[3] * u.m,
                                   pointing_direction=self.array_direction)
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan

        zenith = 90 * u.deg - self.array_direction.alt
        shower_result.h_max = fit_params[5] * \
            self.get_shower_max(fit_params[0],
                                fit_params[1],
                                fit_params[2],
                                fit_params[3],
                                zenith.to(u.rad).value)

        shower_result.h_max_uncert = errors[5] * shower_result.h_max

        shower_result.goodness_of_fit = np.nan
        shower_result.tel_ids = list(self.image.keys())

        energy_result = ReconstructedEnergyContainer()
        energy_result.energy = fit_params[4] * u.TeV
        energy_result.energy_uncert = errors[4] * u.TeV
        energy_result.is_valid = True
        energy_result.tel_ids = list(self.image.keys())
        # Return interesting stuff

        return shower_result, energy_result
示例#14
0
    def predict(self, shower_seed, energy_seed):
        """

        Parameters
        ----------
        source_x: float
            Initial guess of source position in the nominal frame
        source_y: float
            Initial guess of source position in the nominal frame
        core_x: float
            Initial guess of the core position in the tilted system
        core_y: float
            Initial guess of the core position in the tilted system
        energy: float
            Initial guess of energy

        Returns
        -------
        Shower object with fit results
        """
        horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt)
        nominal_seed = horizon_seed.transform_to(
            NominalFrame(array_direction=self.array_direction)
        )

        source_x = nominal_seed.x.to(u.rad).value
        source_y = nominal_seed.y.to(u.rad).value

        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y, z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction)
        )
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value

        lower_en_limit = energy_seed.energy * 0.1
        if lower_en_limit < 0.04 * u.TeV:
            lower_en_limit = 0.04 * u.TeV
        # Create Minuit object with first guesses at parameters, strip away the
        # units as Minuit doesnt like them
        min = Minuit(self.get_likelihood,
                     print_level=1,
                     source_x=source_x,
                     error_source_x=0.01 / 57.3,
                     fix_source_x=False,
                     limit_source_x=(source_x - 0.5 / 57.3,
                                     source_x + 0.5 / 57.3),
                     source_y=source_y,
                     error_source_y=0.01 / 57.3,
                     fix_source_y=False,
                     limit_source_y=(source_y - 0.5 / 57.3,
                                     source_y + 0.5 / 57.3),
                     core_x=tilt_x,
                     error_core_x=10,
                     limit_core_x=(tilt_x - 200, tilt_x + 200),
                     core_y=tilt_y,
                     error_core_y=10,
                     limit_core_y=(tilt_y - 200, tilt_y + 200),
                     energy=energy_seed.energy.value,
                     error_energy=energy_seed.energy.value * 0.05,
                     limit_energy=(lower_en_limit.value,
                                   energy_seed.energy.value * 10.),
                     x_max_scale=1, error_x_max_scale=0.1,
                     limit_x_max_scale=(0.5, 2),
                     fix_x_max_scale=False,
                     errordef=1)

        min.tol *= 1000
        min.strategy = 0

        # Perform minimisation
        migrad = min.migrad()
        fit_params = min.values
        errors = min.errors
    #    print(migrad)
    #    print(min.minos())

        # container class for reconstructed showers '''
        shower_result = ReconstructedShowerContainer()

        nominal = NominalFrame(x=fit_params["source_x"] * u.rad,
                               y=fit_params["source_y"] * u.rad,
                               array_direction=self.array_direction)
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(x=fit_params["core_x"] * u.m,
                                   y=fit_params["core_y"] * u.m,
                                   pointing_direction=self.array_direction)
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan
        zenith = 90 * u.deg - self.array_direction[0]
        shower_result.h_max = fit_params["x_max_scale"] * \
            self.get_shower_max(fit_params["source_x"],
                                fit_params["source_y"],
                                fit_params["core_x"],
                                fit_params["core_y"],
                                zenith.to(u.rad).value)
        shower_result.h_max_uncert = errors["x_max_scale"] * shower_result.h_max
        shower_result.goodness_of_fit = np.nan
        shower_result.tel_ids = list(self.image.keys())

        energy_result = ReconstructedEnergyContainer()
        energy_result.energy = fit_params["energy"] * u.TeV
        energy_result.energy_uncert = errors["energy"] * u.TeV
        energy_result.is_valid = True
        energy_result.tel_ids = list(self.image.keys())
        # Return interesting stuff

        return shower_result, energy_result
示例#15
0
    def predict(self, shower_seed, energy_seed):
        """

        Parameters
        ----------
        source_x: float
            Initial guess of source position in the nominal frame
        source_y: float
            Initial guess of source position in the nominal frame
        core_x: float
            Initial guess of the core position in the tilted system
        core_y: float
            Initial guess of the core position in the tilted system
        energy: float
            Initial guess of energy

        Returns
        -------
        Shower object with fit results
        """
        horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt)
        nominal_seed = horizon_seed.transform_to(
            NominalFrame(array_direction=self.array_direction))

        source_x = nominal_seed.x.to(u.rad).value
        source_y = nominal_seed.y.to(u.rad).value

        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y,
                             z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction))
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value

        lower_en_limit = energy_seed.energy * 0.1
        if lower_en_limit < 0.04 * u.TeV:
            lower_en_limit = 0.04 * u.TeV
        # Create Minuit object with first guesses at parameters, strip away the
        # units as Minuit doesnt like them
        min = Minuit(self.get_likelihood,
                     print_level=1,
                     source_x=source_x,
                     error_source_x=0.01 / 57.3,
                     fix_source_x=False,
                     limit_source_x=(source_x - 0.5 / 57.3,
                                     source_x + 0.5 / 57.3),
                     source_y=source_y,
                     error_source_y=0.01 / 57.3,
                     fix_source_y=False,
                     limit_source_y=(source_y - 0.5 / 57.3,
                                     source_y + 0.5 / 57.3),
                     core_x=tilt_x,
                     error_core_x=10,
                     limit_core_x=(tilt_x - 200, tilt_x + 200),
                     core_y=tilt_y,
                     error_core_y=10,
                     limit_core_y=(tilt_y - 200, tilt_y + 200),
                     energy=energy_seed.energy.value,
                     error_energy=energy_seed.energy.value * 0.05,
                     limit_energy=(lower_en_limit.value,
                                   energy_seed.energy.value * 10.),
                     x_max_scale=1,
                     error_x_max_scale=0.1,
                     limit_x_max_scale=(0.5, 2),
                     fix_x_max_scale=False,
                     errordef=1)

        min.tol *= 1000
        min.strategy = 0

        # Perform minimisation
        migrad = min.migrad()
        fit_params = min.values
        errors = min.errors
        #    print(migrad)
        #    print(min.minos())

        # container class for reconstructed showers '''
        shower_result = ReconstructedShowerContainer()

        nominal = NominalFrame(x=fit_params["source_x"] * u.rad,
                               y=fit_params["source_y"] * u.rad,
                               array_direction=self.array_direction)
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(x=fit_params["core_x"] * u.m,
                                   y=fit_params["core_y"] * u.m,
                                   pointing_direction=self.array_direction)
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan
        zenith = 90 * u.deg - self.array_direction[0]
        shower_result.h_max = fit_params["x_max_scale"] * \
            self.get_shower_max(fit_params["source_x"],
                                fit_params["source_y"],
                                fit_params["core_x"],
                                fit_params["core_y"],
                                zenith.to(u.rad).value)
        shower_result.h_max_uncert = errors["x_max_scale"] * shower_result.h_max
        shower_result.goodness_of_fit = np.nan
        shower_result.tel_ids = list(self.image.keys())

        energy_result = ReconstructedEnergyContainer()
        energy_result.energy = fit_params["energy"] * u.TeV
        energy_result.energy_uncert = errors["energy"] * u.TeV
        energy_result.is_valid = True
        energy_result.tel_ids = list(self.image.keys())
        # Return interesting stuff

        return shower_result, energy_result
示例#16
0
    def predict(self, shower_seed, energy_seed):
        """
        
        Parameters
        ----------
        shower_seed: ReconstructedShowerContainer
            Seed shower geometry to be used in the fit
        energy_seed: ReconstructedEnergyContainer
            Seed energy to be used in fit

        Returns
        -------
        ReconstructedShowerContainer, ReconstructedEnergyContainer:
        Reconstructed ImPACT shower geometry and energy
        
        """
        horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt)
        nominal_seed = horizon_seed.transform_to(NominalFrame(array_direction=self.array_direction))
        print(nominal_seed)
        print(horizon_seed)
        print(self.array_direction)

        source_x = nominal_seed.x[0].to(u.rad).value
        source_y = nominal_seed.y[0].to(u.rad).value

        ground = GroundFrame(x=shower_seed.core_x,
                             y=shower_seed.core_y, z=0 * u.m)
        tilted = ground.transform_to(
            TiltedGroundFrame(pointing_direction=self.array_direction)
        )
        tilt_x = tilted.x.to(u.m).value
        tilt_y = tilted.y.to(u.m).value

        lower_en_limit = energy_seed.energy * 0.5
        en_seed =  energy_seed.energy
        if lower_en_limit < 0.04 * u.TeV:
            lower_en_limit = 0.04 * u.TeV
            en_seed = 0.041 * u.TeV

        seed = (source_x, source_y, tilt_x,
                tilt_y, en_seed.value, 0.8)
        step = (0.001, 0.001, 10, 10, en_seed.value*0.1, 0.1)
        limits = ((source_x-0.01, source_x+0.01),
                  (source_y-0.01, source_y+0.01),
                  (tilt_x-100, tilt_x+100),
                  (tilt_y-100, tilt_y+100),
                  (lower_en_limit.value, en_seed.value*2),
                  (0.5,2))

        fit_params, errors = self.minimise(params=seed, step=step, limits=limits,
                                             minimiser_name=self.minimiser_name)

        # container class for reconstructed showers '''
        shower_result = ReconstructedShowerContainer()

        nominal = NominalFrame(x=fit_params[0] * u.rad,
                               y=fit_params[1] * u.rad,
                               array_direction=self.array_direction)
        horizon = nominal.transform_to(HorizonFrame())

        shower_result.alt, shower_result.az = horizon.alt, horizon.az
        tilted = TiltedGroundFrame(x=fit_params[2] * u.m,
                                   y=fit_params[3] * u.m,
                                   pointing_direction=self.array_direction)
        ground = project_to_ground(tilted)

        shower_result.core_x = ground.x
        shower_result.core_y = ground.y

        shower_result.is_valid = True

        shower_result.alt_uncert = np.nan
        shower_result.az_uncert = np.nan
        shower_result.core_uncert = np.nan

        zenith = 90*u.deg - self.array_direction.alt
        shower_result.h_max = fit_params[5] * \
                              self.get_shower_max(fit_params[0],
                                                  fit_params[1],
                                                  fit_params[2],
                                                  fit_params[3],
                                                  zenith.to(u.rad).value)

        shower_result.h_max_uncert = errors[5] * shower_result.h_max

        shower_result.goodness_of_fit = np.nan
        shower_result.tel_ids = list(self.image.keys())

        energy_result = ReconstructedEnergyContainer()
        energy_result.energy = fit_params[4] * u.TeV
        energy_result.energy_uncert = errors[4] * u.TeV
        energy_result.is_valid = True
        energy_result.tel_ids = list(self.image.keys())
        # Return interesting stuff

        return shower_result, energy_result
示例#17
0
    def estimate_core_position(self, event, hillas_dict, array_pointing,
                               corrected_angle_dict, hillas_planes):
        """
        Estimate the core position by intersection the major ellipse lines of each telescope.

        Parameters
        -----------
        hillas_dict: dict[HillasContainer]
            dictionary of hillas moments
        array_pointing: SkyCoord[HorizonFrame]
            Pointing direction of the array

        Returns
        -----------
        core_x: u.Quantity
            estimated x position of impact
        core_y: u.Quantity
            estimated y position of impact

        Notes
        -----
        The part of the algorithm taking into account divergent pointing
        mode and the usage of a corrected psi angle is explained in [gasparetto]_
        section 7.1.4.

        """

        # Since psi has been recalculated in the fake CameraFrame
        # it doesn't need any further corrections because it is now independent
        # of both pointing and cleaning/parametrization frame.
        # This angle will be used to visualize the telescope-wise directions of
        # the shower core the ground.
        psi_core = corrected_angle_dict

        # Record these values
        for tel_id in hillas_dict.keys():
            event.dl1.tel[tel_id].parameters.core.psi = psi_core[tel_id]

        # Transform them for numpy
        psi = u.Quantity(list(psi_core.values()))

        # Estimate the position of the shower's core
        # from the TiltedFram to the GroundFrame

        z = np.zeros(len(psi))
        uvw_vectors = np.column_stack(
            [np.cos(psi).value, np.sin(psi).value, z])

        tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing)
        ground_frame = GroundFrame()

        positions = [(SkyCoord(
            *plane.pos,
            frame=ground_frame).transform_to(tilted_frame).cartesian.xyz)
                     for plane in hillas_planes.values()]

        core_position = line_line_intersection_3d(uvw_vectors, positions)

        core_pos_tilted = SkyCoord(x=core_position[0] * u.m,
                                   y=core_position[1] * u.m,
                                   frame=tilted_frame)

        core_pos = project_to_ground(core_pos_tilted)

        return core_pos.x, core_pos.y
示例#18
0
    def predict(self, hillas_parameters, tel_x, tel_y, array_direction):
        """

        Parameters
        ----------
        hillas_parameters: dict
            Dictionary containing Hillas parameters for all telescopes
            in reconstruction
        tel_x: dict
            Dictionary containing telescope position on ground for all
            telescopes in reconstruction
        tel_y: dict
            Dictionary containing telescope position on ground for all
            telescopes in reconstruction
        array_direction: HorizonFrame
            Pointing direction of the array

        Returns
        -------
        ReconstructedShowerContainer:

        """
        src_x, src_y, err_x, err_y = self.reconstruct_nominal(hillas_parameters)
        core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted(
            hillas_parameters, tel_x, tel_y)
        err_x *= u.rad
        err_y *= u.rad

        nom = SkyCoord(
            x=src_x * u.rad,
            y=src_y * u.rad,
            frame=NominalFrame(array_direction=array_direction)
        )
        horiz = nom.transform_to(HorizonFrame())

        result = ReconstructedShowerContainer()
        result.alt, result.az = horiz.alt, horiz.az

        tilt = SkyCoord(
            x=core_x * u.m,
            y=core_y * u.m,
            frame=TiltedGroundFrame(pointing_direction=array_direction),
        )
        grd = project_to_ground(tilt)
        result.core_x = grd.x
        result.core_y = grd.y

        x_max = self.reconstruct_xmax(
            nom.x, nom.y,
            tilt.x, tilt.y,
            hillas_parameters,
            tel_x, tel_y,
            90 * u.deg - array_direction.alt,
        )

        result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m

        result.tel_ids = [h for h in hillas_parameters.keys()]
        result.average_intensity = np.mean([h.intensity for h in hillas_parameters.values()])
        result.is_valid = True

        src_error = np.sqrt(err_x**2 + err_y**2)
        result.alt_uncert = src_error.to(u.deg)
        result.az_uncert = src_error.to(u.deg)
        result.h_max = x_max
        result.h_max_uncert = np.nan
        result.goodness_of_fit = np.nan

        return result