def fake_psf3d(sigma=0.15 * u.deg, shape="gauss"): offset_axis = MapAxis.from_nodes([0, 1, 2, 3] * u.deg, name="offset") energy_axis_true = MapAxis.from_energy_bounds("0.1 TeV", "10 TeV", nbin=4, name="energy_true") rad = np.linspace(0, 1.0, 101) * u.deg rad_axis = MapAxis.from_edges(rad, name="rad") O, R, E = np.meshgrid(offset_axis.center, rad_axis.edges, energy_axis_true.center) Rmid = 0.5 * (R[:-1] + R[1:]) if shape == "gauss": val = np.exp(-0.5 * Rmid**2 / sigma**2) else: val = Rmid < sigma drad = 2 * np.pi * (np.cos(R[:-1]) - np.cos(R[1:])) * u.Unit("sr") psf_value = val / ((val * drad).sum(0)[0]) return PSF3D( energy_axis_true=energy_axis_true, rad_axis=rad_axis, offset_axis=offset_axis, data=psf_value.T, )
def test_psf_table(): '''Test our psf is readable by gammapy''' pytest.importorskip('gammapy') from pyirf.io import create_psf_table_hdu from pyirf.utils import cone_solid_angle from gammapy.irf import PSF3D e_bins = np.geomspace(0.1, 100, 31) * u.TeV source_bins = np.linspace(0, 1, 101) * u.deg fov_bins = [0, 1, 2, 3] * u.deg psf = np.zeros((30, 100, 3)) psf[:, 0, :] = 1 psf = psf / cone_solid_angle(source_bins[1]) for point_like in [True, False]: with tempfile.NamedTemporaryFile(suffix='.fits') as f: hdu = create_psf_table_hdu(psf, e_bins, source_bins, fov_bins, point_like=point_like) fits.HDUList([fits.PrimaryHDU(), hdu]).writeto(f.name) # test reading with gammapy works psf3d = PSF3D.read(f.name, 'PSF') # gammapy does not transpose psf when reading from fits, # unlike how it handles effective area and edisp # see https://github.com/gammapy/gammapy/issues/3025 assert u.allclose(psf, psf3d.psf_value.T, atol=1e-16 / u.sr)
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())
def test_psf_3d_wrong_units(): energy_axis = MapAxis.from_energy_edges([80, 125] * u.GeV, name="energy_true") offset_axis = MapAxis.from_edges([0, 1, 2], unit="deg", name="offset") rad_axis = MapAxis.from_edges([0, 1, 2], unit="deg", name="rad") wrong_unit = u.cm**2 * u.s data = np.ones( (energy_axis.nbin, offset_axis.nbin, rad_axis.nbin)) * wrong_unit psf3d_test = PSF3D(axes=[energy_axis, offset_axis, rad_axis]) with pytest.raises(ValueError) as error: PSF3D(axes=[energy_axis, offset_axis, rad_axis], data=data) assert error.match( f"Error: {wrong_unit} is not an allowed unit. {psf3d_test.tag} requires {psf3d_test.default_unit} data quantities." )
def test_psf_table_gammapy(psf_hdu): '''Test our psf is readable by gammapy''' from gammapy.irf import PSF3D psf, hdu = psf_hdu with tempfile.NamedTemporaryFile(suffix='.fits') as f: fits.HDUList([fits.PrimaryHDU(), hdu]).writeto(f.name) # test reading with gammapy works psf3d = PSF3D.read(f.name, 'PSF') assert u.allclose(psf, psf3d.psf_value, atol=1e-16 / u.sr)
def test_psf_table_gammapy(psf_hdu): '''Test our psf is readable by gammapy''' from gammapy.irf import PSF3D psf, hdu = psf_hdu with tempfile.NamedTemporaryFile(suffix='.fits') as f: fits.HDUList([fits.PrimaryHDU(), hdu]).writeto(f.name) # test reading with gammapy works psf3d = PSF3D.read(f.name, 'PSF') # gammapy does not transpose psf when reading from fits, # unlike how it handles effective area and edisp # see https://github.com/gammapy/gammapy/issues/3025 assert u.allclose(psf, psf3d.psf_value.T, atol=1e-16 / u.sr)
def load(self): """Load HDU as appropriate class. TODO: this should probably go via an extensible registry. """ hdu_class = self.hdu_class filename = self.path() hdu = self.hdu_name if hdu_class == "events": from gammapy.data import EventList return EventList.read(filename, hdu=hdu) elif hdu_class == "gti": from gammapy.data import GTI return GTI.read(filename, hdu=hdu) elif hdu_class == "aeff_2d": from gammapy.irf import EffectiveAreaTable2D return EffectiveAreaTable2D.read(filename, hdu=hdu) elif hdu_class == "edisp_2d": from gammapy.irf import EnergyDispersion2D return EnergyDispersion2D.read(filename, hdu=hdu) elif hdu_class == "psf_table": from gammapy.irf import PSF3D return PSF3D.read(filename, hdu=hdu) elif hdu_class == "psf_3gauss": from gammapy.irf import EnergyDependentMultiGaussPSF return EnergyDependentMultiGaussPSF.read(filename, hdu=hdu) elif hdu_class == "psf_king": from gammapy.irf import PSFKing return PSFKing.read(filename, hdu=hdu) elif hdu_class == "bkg_2d": from gammapy.irf import Background2D return Background2D.read(filename, hdu=hdu) elif hdu_class == "bkg_3d": from gammapy.irf import Background3D return Background3D.read(filename, hdu=hdu) else: raise ValueError(f"Invalid hdu_class: {hdu_class}")
def test_psf_3d_basics(psf_3d): rad_axis = psf_3d.axes["rad"] assert_allclose(rad_axis.edges[-2].value, 0.659048, rtol=1e-5) assert rad_axis.nbin == 144 assert rad_axis.unit == "deg" energy_axis_true = psf_3d.axes["energy_true"] assert_allclose(energy_axis_true.edges[0].value, 0.01) assert energy_axis_true.nbin == 32 assert energy_axis_true.unit == "TeV" assert psf_3d.data.shape == (32, 6, 144) assert psf_3d.unit == "sr-1" assert_allclose(psf_3d.meta["LO_THRES"], 0.01) assert "PSF3D" in str(psf_3d) with pytest.raises(ValueError): PSF3D(axes=psf_3d.axes, data=psf_3d.data.T)
def fake_psf3d(sigma=0.15 * u.deg, shape="gauss"): offsets = np.array((0.0, 1.0, 2.0, 3.0)) * u.deg energy = np.logspace(-1, 1, 5) * u.TeV energy_lo = energy[:-1] energy_hi = energy[1:] energy = np.sqrt(energy_lo * energy_hi) rad = np.linspace(0, 1.0, 101) * u.deg rad_lo = rad[:-1] rad_hi = rad[1:] O, R, E = np.meshgrid(offsets, rad, energy) Rmid = 0.5 * (R[:-1] + R[1:]) if shape == "gauss": val = np.exp(-0.5 * Rmid**2 / sigma**2) else: val = Rmid < sigma drad = 2 * np.pi * (np.cos(R[:-1]) - np.cos(R[1:])) * u.Unit("sr") psf_values = val / ((val * drad).sum(0)[0]) return PSF3D(energy_lo, energy_hi, offsets, rad_lo, rad_hi, psf_values)
def test_psf_3d_to_gadf(): from gammapy.irf import PSF3D from gammapy.maps import MapAxis energy_axis = MapAxis.from_energy_bounds(1 * u.TeV, 10 * u.TeV, nbin=3, name='energy_true') offset_axis = MapAxis.from_bounds(0 * u.deg, 2 * u.deg, nbin=2, name='offset') rad_axis = MapAxis.from_bounds(0.0 * u.deg, 1 * u.deg, nbin=10, name='rad') data = np.zeros((energy_axis.nbin, offset_axis.nbin, rad_axis.nbin)) / u.sr psf = PSF3D(data=data, axes=[energy_axis, offset_axis, rad_axis]) hdu = psf.to_table_hdu(format='gadf-dl3') mandatory_columns = { 'ENERG_LO', 'ENERG_HI', 'THETA_LO', 'THETA_HI', 'RAD_LO', 'RAD_HI', 'RPSF', } columns = {column.name for column in hdu.columns} missing = mandatory_columns.difference(columns) assert len(missing) == 0, f'GADF HDU missing required column(s) {missing}' header = hdu.header assert header['HDUCLASS'] == 'GADF' assert header[ 'HDUDOC'] == 'https://github.com/open-gamma-ray-astro/gamma-astro-data-formats' assert header['HDUVERS'] == '0.2' assert header['HDUCLAS1'] == 'RESPONSE' assert header['HDUCLAS2'] == 'RPSF' assert header['HDUCLAS3'] == 'FULL-ENCLOSURE' assert header['HDUCLAS4'] == 'PSF_TABLE'
def test_psf_3d_basics(psf_3d): assert_allclose(psf_3d.rad_axis.edges[-2].value, 0.659048, rtol=1e-5) assert psf_3d.rad_axis.nbin == 144 assert psf_3d.rad_axis.unit == "deg" assert_allclose(psf_3d.energy_axis_true.edges[0].value, 0.01) assert psf_3d.energy_axis_true.nbin == 32 assert psf_3d.energy_axis_true.unit == "TeV" assert psf_3d.psf_value.shape == (32, 6, 144) assert psf_3d.psf_value.unit == "sr-1" assert_allclose(psf_3d.energy_thresh_lo.value, 0.01) assert "PSF3D" in str(psf_3d) with pytest.raises(ValueError): PSF3D( energy_axis_true=psf_3d.energy_axis_true, offset_axis=psf_3d.offset_axis, rad_axis=psf_3d.rad_axis, psf_value=psf_3d.psf_value.T, )
def psf_3d_plot(irf_file_path, ax=None, hdu="PSF"): if not ax: fig = plt.figure(figsize=(10, 7), ) ax = fig.add_subplot(111, projection='3d') psf = PSF3D.read(irf_file_path, hdu=hdu) energy_reco = np.logspace(-2, 2, 20) * u.TeV offsets = np.linspace(0, 6, 7) * u.deg d = np.linspace(0.01, 1.1, 11)[1::2] ticks = np.log10(d) # ticks = np.append(ticks, 1 + ticks) X, Y = np.meshgrid(energy_reco, offsets) Z = [] for x in energy_reco: zs = psf.containment_radius(x, offsets, fraction=0.68) Z.append(zs) Z = np.vstack(Z).T ax.plot_surface(np.log10(X.to_value('TeV')), Y, np.log10(Z), cmap='viridis', linewidth=1, antialiased=True) ax.xaxis.set_major_locator(mticker.FixedLocator([-2, -1, 0, 1, 2])) ax.zaxis.set_major_locator(mticker.FixedLocator(ticks)) ax.zaxis.set_major_formatter(mticker.FuncFormatter(_log_scale_formatter)) ax.xaxis.set_major_formatter(mticker.FuncFormatter(_log_tick_formatter)) ax.set_zlim([-1, 0.4]) ax.set_ylabel('Offset in FoV / deg') ax.set_zlabel('Angular Resolution / deg') ax.set_xlabel('Reconstructed Energy / TeV') return ax
def create_psf_3d( psf, true_energy_bins, source_offset_bins, fov_offset_bins, ): """ Create a ``gammapy.irf.PSF3D`` from pyirf outputs. Parameters ---------- psf: astropy.units.Quantity[(solid angle)^-1] Point spread function array, must have shape (n_energy_bins, n_fov_offset_bins, n_source_offset_bins) true_energy_bins: astropy.units.Quantity[energy] Bin edges in true energy source_offset_bins: astropy.units.Quantity[angle] Bin edges in the source offset. fov_offset_bins: astropy.units.Quantity[angle] Bin edges in the field of view offset. For Point-Like IRFs, only giving a single bin is appropriate. Returns ------- gammapy.irf.PSF3D """ offset_axis = _create_offset_axis(fov_offset_bins) energy_axis_true = _create_energy_axis_true(true_energy_bins) rad_axis = MapAxis.from_edges(source_offset_bins, name='rad') return PSF3D( energy_axis_true=energy_axis_true, offset_axis=offset_axis, rad_axis=rad_axis, psf_value=psf, )
def load_psf(): """Load a test PSF.""" filename = '$GAMMAPY_EXTRA/test_datasets/psf_table_023523.fits.gz' print('Reading {}'.format(filename)) return PSF3D.read(filename)
def test_psf_3d_write(psf_3d, tmp_path): psf_3d.write(tmp_path / "tmp.fits") psf_3d = PSF3D.read(tmp_path / "tmp.fits", hdu=1) assert_allclose(psf_3d.energy_axis_true.edges[0].value, 0.01)
def psf_3d(): filename = "$GAMMAPY_DATA/tests/psf_table_023523.fits.gz" return PSF3D.read(filename)
def setup(self): filename = "$GAMMAPY_DATA/tests/hess-hap-hd-prod3/psf_table.fits.gz" self.psf = PSF3D.read(filename)
def psf_3d(): filename = "$GAMMAPY_DATA/hess-dl3-dr1/data/hess_dl3_dr1_obs_id_023523.fits.gz" return PSF3D.read(filename, hdu="PSF")
"""Plot the PSF at a given offset from the camera center""" import matplotlib.pyplot as plt from astropy.coordinates import Angle from gammapy.irf import PSF3D filename = "$GAMMAPY_DATA/hess-dl3-dr1/data/hess_dl3_dr1_obs_id_020136.fits.gz" psf = PSF3D.read(filename, hdu="PSF") # offset at which we want to examine the PSF offset = Angle("0.5 deg") psf_table = psf.to_energy_dependent_table_psf(offset) psf_table.plot_psf_vs_rad() plt.show()
def test_psf_3d_write(psf_3d, tmpdir): filename = str(tmpdir / "temp.fits") psf_3d.write(filename) psf_3d = PSF3D.read(filename) assert_allclose(psf_3d.energy_lo[0].value, 0.02)