Exemplo n.º 1
0
def test_mixed_axes():
    label_axis = LabelMapAxis(labels=["label-1", "label-2", "label-3"],
                              name="label")

    time_axis = TimeMapAxis(
        edges_min=[1, 10] * u.day,
        edges_max=[2, 13] * u.day,
        reference_time=Time("2020-03-19"),
    )

    energy_axis = MapAxis.from_energy_bounds("1 TeV", "10 TeV", nbin=4)

    axes = MapAxes(axes=[energy_axis, time_axis, label_axis])

    coords = axes.get_coord()

    assert coords["label"].shape == (1, 1, 3)
    assert coords["energy"].shape == (4, 1, 1)
    assert coords["time"].shape == (1, 2, 1)

    idx = axes.coord_to_idx(coords)

    assert_allclose(idx[0], np.arange(4).reshape((4, 1, 1)))
    assert_allclose(idx[1], np.arange(2).reshape((1, 2, 1)))
    assert_allclose(idx[2], np.arange(3).reshape((1, 1, 3)))

    hdu = axes.to_table_hdu(format="gadf")

    table = Table.read(hdu)

    assert table["LABEL"].dtype == np.dtype("<U7")
    assert len(table) == 24
Exemplo n.º 2
0
    def to_psf3d(self, rad=None):
        """Create a PSF3D from a parametric PSF.

        It will be defined on the same energy and offset values than the input psf.

        Parameters
        ----------
        rad : `~astropy.units.Quantity`
            Rad values

        Returns
        -------
        psf3d : `~gammapy.irf.PSF3D`
            PSF3D.
        """
        from gammapy.irf import PSF3D
        from gammapy.datasets.map import RAD_AXIS_DEFAULT

        offset_axis = self.axes["offset"]
        energy_axis_true = self.axes["energy_true"]

        if rad is None:
            rad_axis = RAD_AXIS_DEFAULT.center
        else:
            rad_axis = MapAxis.from_edges(rad, name="rad")

        axes = MapAxes([energy_axis_true, offset_axis, rad_axis])
        data = self.evaluate(**axes.get_coord())

        return PSF3D(axes=axes,
                     data=data.value,
                     unit=data.unit,
                     meta=self.meta.copy())
Exemplo n.º 3
0
    def to_energy_dependent_table_psf(self, offset, rad=None):
        """Convert to energy-dependent table PSF.

        Parameters
        ----------
        offset : `~astropy.coordinates.Angle`
            Offset in the field of view. Default theta = 0 deg
        rad : `~astropy.coordinates.Angle`
            Offset from PSF center used for evaluating the PSF on a grid.
            Default offset = [0, 0.005, ..., 1.495, 1.5] deg.

        Returns
        -------
        table_psf : `~gammapy.irf.EnergyDependentTablePSF`
            Energy-dependent PSF
        """
        from gammapy.irf import EnergyDependentTablePSF
        from gammapy.datasets.map import RAD_AXIS_DEFAULT

        energy_axis_true = self.axes["energy_true"]

        if rad is None:
            rad_axis = RAD_AXIS_DEFAULT
        else:
            rad_axis = MapAxis.from_edges(rad, name="rad")

        axes = MapAxes([energy_axis_true, rad_axis])
        data = self.evaluate(**axes.get_coord(), offset=offset)
        return EnergyDependentTablePSF(axes=axes,
                                       data=data.value,
                                       unit=data.unit)
Exemplo n.º 4
0
    def from_parametrization(cls, energy_axis_true=None, instrument="HESS"):
        r"""Create parametrized effective area.

        Parametrizations of the effective areas of different Cherenkov
        telescopes taken from Appendix B of Abramowski et al. (2010), see
        https://ui.adsabs.harvard.edu/abs/2010MNRAS.402.1342A .

        .. math::
            A_{eff}(E) = g_1 \left(\frac{E}{\mathrm{MeV}}\right)^{-g_2}\exp{\left(-\frac{g_3}{E}\right)}

        This method does not model the offset dependence of the effective area,
        but just assumes that it is constant.

        Parameters
        ----------
        energy_axis_true : `MapAxis`
            Energy binning, analytic function is evaluated at log centers
        instrument : {'HESS', 'HESS2', 'CTA'}
            Instrument name

        Returns
        -------
        aeff : `EffectiveAreaTable2D`
            Effective area table
        """
        # Put the parameters g in a dictionary.
        # Units: g1 (cm^2), g2 (), g3 (MeV)
        pars = {
            "HESS": [6.85e9, 0.0891, 5e5],
            "HESS2": [2.05e9, 0.0891, 1e5],
            "CTA": [1.71e11, 0.0891, 1e5],
        }

        if instrument not in pars.keys():
            ss = f"Unknown instrument: {instrument}\n"
            ss += f"Valid instruments: {list(pars.keys())}"
            raise ValueError(ss)

        if energy_axis_true is None:
            energy_axis_true = MapAxis.from_energy_bounds("2 GeV",
                                                          "200 TeV",
                                                          nbin=20,
                                                          per_decade=True,
                                                          name="energy_true")

        g1, g2, g3 = pars[instrument]

        offset_axis = MapAxis.from_edges([0., 5.] * u.deg, name="offset")
        axes = MapAxes([energy_axis_true, offset_axis])
        coords = axes.get_coord()

        energy, offset = coords["energy_true"].to_value(
            "MeV"), coords["offset"]
        data = np.ones_like(offset.value) * g1 * energy**(-g2) * np.exp(
            -g3 / energy)

        # TODO: fake offset dependence?
        meta = {"TELESCOP": instrument}
        return cls(axes=axes, data=data, unit="cm2", meta=meta)
Exemplo n.º 5
0
    def to_edisp_kernel(self, offset, energy_true=None, energy=None):
        """Detector response R(Delta E_reco, Delta E_true)

        Probability to reconstruct an energy in a given true energy band
        in a given reconstructed energy band

        Parameters
        ----------
        offset : `~astropy.coordinates.Angle`
            Offset
        energy_true : `~astropy.units.Quantity`, None
            True energy axis
        energy : `~astropy.units.Quantity`
            Reconstructed energy axis

        Returns
        -------
        edisp : `~gammapy.irf.EDispKernel`
            Energy dispersion matrix
        """
        offset = Angle(offset)

        # TODO: expect directly MapAxis here?
        if energy is None:
            energy_axis = self.axes["energy_true"].copy(name="energy")
        else:
            energy_axis = MapAxis.from_energy_edges(energy)

        if energy_true is None:
            energy_axis_true = self.axes["energy_true"]
        else:
            energy_axis_true = MapAxis.from_energy_edges(
                energy_true,
                name="energy_true",
            )

        axes = MapAxes([energy_axis_true, energy_axis])
        coords = axes.get_coord(mode="edges", axis_name="energy")

        # migration value of energy bounds
        migra = coords["energy"] / coords["energy_true"]

        values = self.integral(
            axis_name="migra",
            offset=offset,
            energy_true=coords["energy_true"],
            migra=migra,
        )

        data = np.diff(values)

        return EDispKernel(
            axes=axes,
            data=data.to_value(""),
        )
Exemplo n.º 6
0
    def from_gauss(cls,
                   energy_axis_true,
                   migra_axis,
                   offset_axis,
                   bias,
                   sigma,
                   pdf_threshold=1e-6):
        """Create Gaussian energy dispersion matrix (`EnergyDispersion2D`).

        The output matrix will be Gaussian in (energy_true / energy).

        The ``bias`` and ``sigma`` should be either floats or arrays of same dimension than
        ``energy_true``. ``bias`` refers to the mean value of the ``migra``
        distribution minus one, i.e. ``bias=0`` means no bias.

        Note that, the output matrix is flat in offset.

        Parameters
        ----------
        energy_axis_true : `MapAxis`
            True energy axis
        migra_axis : `~astropy.units.Quantity`
            Migra axis
        offset_axis : `~astropy.units.Quantity`
            Bin edges of offset
        bias : float or `~numpy.ndarray`
            Center of Gaussian energy dispersion, bias
        sigma : float or `~numpy.ndarray`
            RMS width of Gaussian energy dispersion, resolution.
        pdf_threshold : float, optional
            Zero suppression threshold
        """
        axes = MapAxes([energy_axis_true, migra_axis, offset_axis])
        coords = axes.get_coord(mode="edges", axis_name="migra")

        migra_min = coords["migra"][:, :-1, :]
        migra_max = coords["migra"][:, 1:, :]

        # Analytical formula for integral of Gaussian
        s = np.sqrt(2) * sigma
        t1 = (migra_max - 1 - bias) / s
        t2 = (migra_min - 1 - bias) / s
        pdf = (scipy.special.erf(t1) - scipy.special.erf(t2)) / 2
        pdf = pdf / (migra_max - migra_min)

        # no offset dependence
        data = pdf * np.ones(axes.shape)
        data[data < pdf_threshold] = 0

        return cls(
            axes=axes,
            data=data.value,
        )
Exemplo n.º 7
0
    def get_edisp_kernel(self, position=None, energy_axis=None):
        """Get energy dispersion at a given position.

        Parameters
        ----------
        position : `~astropy.coordinates.SkyCoord`
            the target position. Should be a single coordinates
        energy_axis : `MapAxis`
            Reconstructed energy axis

        Returns
        -------
        edisp : `~gammapy.irf.EnergyDispersion`
            the energy dispersion (i.e. rmf object)
        """
        if position is None:
            position = self.edisp_map.geom.center_skydir

        if position.size != 1:
            raise ValueError(
                "EnergyDispersion can be extracted at one single position only."
            )

        position = self._get_nearest_valid_position(position)
        energy_axis_true = self.edisp_map.geom.axes["energy_true"]

        axes = MapAxes([energy_axis_true, energy_axis])

        coords = axes.get_coord(mode="edges", axis_name="energy")

        # migration value of energy bounds
        migra = coords["energy"] / coords["energy_true"]

        coords = {
            "skycoord": position,
            "energy_true": coords["energy_true"],
            "migra": migra,
        }

        values = self.edisp_map.integral(axis_name="migra", coords=coords)
        data = np.diff(np.clip(values, 0, np.inf))

        return EDispKernel(axes=axes, data=data.to_value(""))
Exemplo n.º 8
0
    def to_3d(self):
        """ "Convert to Background3D"""

        edges = np.concatenate((
            np.negative(self.axes["offset"].edges)[::-1][:-1],
            self.axes["offset"].edges,
        ))
        fov_lat = MapAxis.from_edges(edges=edges, name="fov_lat")
        fov_lon = MapAxis.from_edges(edges=edges, name="fov_lon")

        axes = MapAxes([self.axes["energy"], fov_lon, fov_lat])
        coords = axes.get_coord()
        offset = np.sqrt(coords["fov_lat"]**2 + coords["fov_lon"]**2)
        data = self.evaluate(offset=offset, energy=coords["energy"])

        return Background3D(
            axes=axes,
            data=data,
        )
Exemplo n.º 9
0
    def from_gauss(cls, energy_axis_true, rad_axis=None, sigma=0.1 * u.deg):
        """Create all -sky PSF map from Gaussian width.

        This is used for testing and examples.

        The width can be the same for all energies
        or be an array with one value per energy node.
        It does not depend on position.

        Parameters
        ----------
        energy_axis_true : `~gammapy.maps.MapAxis`
            True energy axis.
        rad_axis : `~gammapy.maps.MapAxis`
            Offset angle wrt source position axis.
        sigma : `~astropy.coordinates.Angle`
            Gaussian width.

        Returns
        -------
        psf_map : `PSFMap`
            Point spread function map.
        """
        from gammapy.datasets.map import RAD_AXIS_DEFAULT

        if rad_axis is None:
            rad_axis = RAD_AXIS_DEFAULT.copy()

        axes = MapAxes([energy_axis_true, rad_axis])
        coords = axes.get_coord()

        sigma = np.broadcast_to(u.Quantity(sigma),
                                energy_axis_true.nbin,
                                subok=True)
        gauss = Gauss2DPDF(sigma=sigma.reshape((-1, 1)))
        data = gauss(coords["rad"])

        table_psf = EnergyDependentTablePSF(axes=axes,
                                            unit=data.unit,
                                            data=data.value)
        return cls.from_energy_dependent_table_psf(table_psf)