Пример #1
0
def test_shower_impact_distance():
    """test several boundary cases using the function that takes a Subarray and
    Container
    """

    sub = SubarrayDescription(
        name="test",
        tel_positions={
            1: [0, 0, 0] * u.m,
            2: [0, 1, 0] * u.m,
            3: [0, 2, 0] * u.m
        },
        tel_descriptions={
            1: None,
            2: None,
            3: None
        },
    )

    # coming from zenith to the center of the array, the impact distance should
    # be the cartesian distance
    shower_geom = ReconstructedGeometryContainer(core_x=0 * u.m,
                                                 core_y=0 * u.m,
                                                 alt=90 * u.deg,
                                                 az=0 * u.deg)
    impact_distances = shower_impact_distance(shower_geom=shower_geom,
                                              subarray=sub)
    assert np.allclose(impact_distances, [0, 1, 2] * u.m)

    # alt=0  az=0 should be aligned to x-axis (north in ground frame)
    # therefore the distances should also be just the y-offset of the telecope
    shower_geom = ReconstructedGeometryContainer(core_x=0 * u.m,
                                                 core_y=0 * u.m,
                                                 alt=0 * u.deg,
                                                 az=0 * u.deg)
    impact_distances = shower_impact_distance(shower_geom=shower_geom,
                                              subarray=sub)
    assert np.allclose(impact_distances, [0, 1, 2] * u.m)

    # alt=0  az=90 should be aligned to y-axis (east in ground frame)
    # therefore the distances should also be just the x-offset of the telecope
    shower_geom = ReconstructedGeometryContainer(core_x=0 * u.m,
                                                 core_y=0 * u.m,
                                                 alt=0 * u.deg,
                                                 az=90 * u.deg)
    impact_distances = shower_impact_distance(shower_geom=shower_geom,
                                              subarray=sub)
    assert np.allclose(impact_distances, [0, 0, 0] * u.m)
Пример #2
0
    def test_image_prediction(self):
        pixel_x = np.array([0]) * u.deg
        pixel_y = np.array([0]) * u.deg

        image = np.array([1])
        pixel_area = np.array([1]) * u.deg * u.deg

        self.impact_reco.set_event_properties(
            {1: image},
            {1: pixel_x},
            {1: pixel_y},
            {1: pixel_area},
            {1: "CHEC"},
            {1: 0 * u.m},
            {1: 0 * u.m},
            array_direction=[0 * u.deg, 0 * u.deg],
        )
        """First check image prediction by directly accessing the function"""
        pred = self.impact_reco.image_prediction(
            "CHEC",
            zenith=0,
            azimuth=0,
            energy=1,
            impact=50,
            x_max=0,
            pix_x=pixel_x,
            pix_y=pixel_y,
        )

        assert np.sum(pred) != 0
        """Then check helper function gives the same answer"""
        shower = ReconstructedGeometryContainer()
        shower.is_valid = True
        shower.alt = 0 * u.deg
        shower.az = 0 * u.deg
        shower.core_x = 0 * u.m
        shower.core_y = 100 * u.m
        shower.h_max = 300 + 93 * np.log10(1)

        energy = ReconstructedEnergyContainer()
        energy.is_valid = True
        energy.energy = 1 * u.TeV
        pred2 = self.impact_reco.get_prediction(1,
                                                shower_reco=shower,
                                                energy_reco=energy)
        print(pred, pred2)
        assert pred.all() == pred2.all()
Пример #3
0
    def __call__(self, event: ArrayEventContainer):
        """overwrite this method with your favourite direction reconstruction
        algorithm

        Parameters
        ----------
        tels_dict : dict
            general dictionary containing all triggered telescopes data

        Returns
        -------
        `~ctapipe.containers.ReconstructedGeometryContainer`

        """
        return ReconstructedGeometryContainer()
Пример #4
0
    def __call__(self, event):
        """
        Perform the full shower geometry reconstruction on the input event.

        Parameters
        ----------
        event : container
            `ctapipe.containers.ArrayEventContainer`
        """

        # Read only valid HillasContainers
        hillas_dict = {
            tel_id: dl1.parameters.hillas
            for tel_id, dl1 in event.dl1.tel.items()
            if np.isfinite(dl1.parameters.hillas.intensity)
        }

        # Due to tracking the pointing of the array will never be a constant
        array_pointing = SkyCoord(
            az=event.pointing.array_azimuth,
            alt=event.pointing.array_altitude,
            frame=AltAz(),
        )

        telescope_pointings = {
            tel_id: SkyCoord(
                alt=event.pointing.tel[tel_id].altitude,
                az=event.pointing.tel[tel_id].azimuth,
                frame=AltAz(),
            )
            for tel_id in event.dl1.tel.keys()
        }

        try:
            result = self._predict(
                event, hillas_dict, self.subarray, array_pointing, telescope_pointings
            )
        except (TooFewTelescopesException, InvalidWidthException):
            result = ReconstructedGeometryContainer()

        event.dl2.stereo.geometry["HillasReconstructor"] = result
Пример #5
0
def test_compare_3d_and_frame_impact_distance(
    example_subarray: SubarrayDescription, ):
    """Test another (slower) way of computing the impact distance, using Frames
    and compare to the implemented method.
    """

    for alt in [90, 0, 50, 60] * u.deg:
        for az in [0, 90, 45, 270, 360] * u.deg:

            shower_geom = ReconstructedGeometryContainer(core_x=0 * u.m,
                                                         core_y=0 * u.m,
                                                         alt=alt,
                                                         az=az)
            impact_distances_3d = shower_impact_distance(
                shower_geom=shower_geom, subarray=example_subarray)

            impact_distances_frame = shower_impact_distance_with_frames(
                shower_geom=shower_geom, subarray=example_subarray)

            assert np.allclose(impact_distances_frame,
                               impact_distances_3d), f"failed at {alt=} {az=}"
Пример #6
0
    def predict(self, hillas_dict, subarray, array_pointing, telescopes_pointings=None):
        """
        The function you want to call for the reconstruction of the
        event. It takes care of setting up the event and consecutively
        calls the functions for the direction and core position
        reconstruction.  Shower parameters not reconstructed by this
        class are set to np.nan

        Parameters
        -----------
        hillas_dict: dict
            dictionary with telescope IDs as key and
            HillasParametersContainer instances as values
        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

        Raises
        ------
        TooFewTelescopesException
            if len(hillas_dict) < 2
        InvalidWidthException
            if any width is np.nan or 0
        """

        # 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"
            )

        # use the single telescope pointing also for parallel pointing: code is more general
        if telescopes_pointings is None:
            telescopes_pointings = {
                tel_id: array_pointing for tel_id in hillas_dict.keys()
            }
        else:
            self.divergent_mode = True
            self.corrected_angle_dict = {}

        self.initialize_hillas_planes(
            hillas_dict, subarray, telescopes_pointings, array_pointing
        )

        # algebraic direction estimate
        direction, err_est_dir = self.estimate_direction()

        # array pointing is needed to define the tilted frame
        core_pos = self.estimate_core_position(hillas_dict, array_pointing)

        # container class for reconstructed showers
        _, lat, lon = cartesian_to_spherical(*direction)

        # estimate max height of shower
        h_max = self.estimate_h_max()

        # astropy's coordinates system rotates counter-clockwise.
        # Apparently we assume it to be clockwise.
        # that's why lon get's a sign
        result = ReconstructedGeometryContainer(
            alt=lat,
            az=-lon,
            core_x=core_pos[0],
            core_y=core_pos[1],
            tel_ids=[h for h in hillas_dict.keys()],
            average_intensity=np.mean([h.intensity for h in hillas_dict.values()]),
            is_valid=True,
            alt_uncert=err_est_dir,
            h_max=h_max,
        )

        return result
Пример #7
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
Пример #8
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
Пример #9
0
from astropy.coordinates import SkyCoord, AltAz
from ctapipe.coordinates import (
    NominalFrame,
    CameraFrame,
    TelescopeFrame,
    TiltedGroundFrame,
    project_to_ground,
    MissingFrameAttributeWarning,
)
import warnings

from ctapipe.core import traits

__all__ = ["HillasIntersection"]

INVALID = ReconstructedGeometryContainer(tel_ids=[])


class HillasIntersection(Reconstructor):
    """
    This class is a simple re-implementation of Hillas parameter based event
    reconstruction. e.g. https://arxiv.org/abs/astro-ph/0607333

    In this case the Hillas parameters are all constructed in the shared
    angular (Nominal) system. Direction reconstruction is performed by
    extrapolation of the major axes of the Hillas parameters in the nominal
    system and the weighted average of the crossing points is taken. Core
    reconstruction is performed by performing the same procedure in the
    tilted ground system.

    The height of maximum is reconstructed by the projection os the image
Пример #10
0
    def _predict(self, event, hillas_dict, array_pointing,
                 telescopes_pointings):
        """
        The function you want to call for the reconstruction of the
        event. It takes care of setting up the event and consecutively
        calls the functions for the direction and core position
        reconstruction.  Shower parameters not reconstructed by this
        class are set to np.nan

        Parameters
        ----------
        hillas_dict: dict
            dictionary with telescope IDs as key and
            HillasParametersContainer instances as values
        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

        Raises
        ------
        InvalidWidthException
            if any width is np.nan or 0
        """

        # filter warnings for missing obs time. this is needed because MC data has no obs time
        warnings.filterwarnings(action="ignore",
                                category=MissingFrameAttributeWarning)

        # Here we perform some basic quality checks BEFORE applying reconstruction
        # This should be substituted by a DL1 QualityQuery specific to this
        # reconstructor

        hillas_planes, psi_core_dict = self.initialize_hillas_planes(
            hillas_dict, self.subarray, telescopes_pointings, array_pointing)

        # algebraic direction estimate
        direction, err_est_dir = self.estimate_direction(hillas_planes)

        # array pointing is needed to define the tilted frame
        core_pos_ground, core_pos_tilted = self.estimate_core_position(
            event, hillas_dict, array_pointing, psi_core_dict, hillas_planes)

        # container class for reconstructed showers
        _, lat, lon = cartesian_to_spherical(*direction)

        # estimate max height of shower
        h_max = self.estimate_h_max(hillas_planes)

        # astropy's coordinates system rotates counter-clockwise.
        # Apparently we assume it to be clockwise.
        # that's why lon get's a sign
        result = ReconstructedGeometryContainer(
            alt=lat,
            az=-lon,
            core_x=core_pos_ground.x,
            core_y=core_pos_ground.y,
            core_tilted_x=core_pos_tilted.x,
            core_tilted_y=core_pos_tilted.y,
            tel_ids=[h for h in hillas_dict.keys()],
            average_intensity=np.mean(
                [h.intensity for h in hillas_dict.values()]),
            is_valid=True,
            alt_uncert=err_est_dir,
            az_uncert=err_est_dir,
            h_max=h_max,
        )

        return result
Пример #11
0
"""
High level processing of showers.

This processor will be able to process a shower/event in 3 steps:
- shower geometry
- estimation of energy (optional, currently unavailable)
- estimation of classification (optional, currently unavailable)

"""
from ctapipe.core import Component, QualityQuery
from ctapipe.core.traits import List
from ctapipe.containers import ArrayEventContainer, ReconstructedGeometryContainer
from ctapipe.instrument import SubarrayDescription
from ctapipe.reco import HillasReconstructor

DEFAULT_SHOWER_PARAMETERS = ReconstructedGeometryContainer(tel_ids=[])


class ShowerQualityQuery(QualityQuery):
    """Configuring shower-wise data checks."""

    quality_criteria = List(
        default_value=[
            ("> 50 phe", "lambda p: p.hillas.intensity > 50"),
            ("Positive width", "lambda p: p.hillas.width.value > 0"),
            ("> 3 pixels", "lambda p: p.morphology.num_pixels > 3"),
        ],
        help=QualityQuery.quality_criteria.help,
    ).tag(config=True)