def position_error(self): """Get 95% containment position error as (`~regions.EllipseSkyRegion`)""" if self.covariance is None: return EllipseSkyRegion( center=self.position, height=np.nan * u.deg, width=np.nan * u.deg, angle=np.nan * u.deg, ) pars = self.parameters sub_covar = self.covariance.get_subcovariance(["lon_0", "lat_0"]).data.copy() cos_lat = np.cos(self.lat_0.quantity.to_value("rad")) sub_covar[0, 0] *= cos_lat**2.0 sub_covar[0, 1] *= cos_lat sub_covar[1, 0] *= cos_lat eig_vals, eig_vecs = np.linalg.eig(sub_covar) lon_err, lat_err = np.sqrt(eig_vals) y_vec = eig_vecs[:, 0] phi = (np.arctan2(y_vec[1], y_vec[0]) * u.rad).to("deg") + self.phi_0 err = np.sort([lon_err, lat_err]) scale_r95 = Gauss2DPDF().containment_radius(0.95) err *= scale_r95 if err[1] == lon_err * scale_r95: phi += 90 * u.deg height = 2 * err[1] * pars["lon_0"].unit width = 2 * err[0] * pars["lat_0"].unit else: height = 2 * err[1] * pars["lat_0"].unit width = 2 * err[0] * pars["lon_0"].unit return EllipseSkyRegion(center=self.position, height=height, width=width, angle=phi)
def test_position_error(cat): scale_r95 = Gauss2DPDF().containment_radius(0.95) model = cat["HESS J1729-345"].spatial_model() pos_err = model.position_error assert_allclose(pos_err.angle.value, 0.0) assert_allclose(pos_err.height.value, 2 * 0.0414315 * scale_r95, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.0344351 * scale_r95, rtol=1e-4) assert_allclose(model.position.l.value, pos_err.center.l.value) assert_allclose(model.position.b.value, pos_err.center.b.value) model = cat["HESS J1858+020"].spatial_model() pos_err = model.position_error assert_allclose(pos_err.angle.value, 90.0) assert_allclose(pos_err.height.value, 2 * 0.0222614 * scale_r95, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.0145084 * scale_r95, rtol=1e-4) assert_allclose(model.position.l.value, pos_err.center.l.value) assert_allclose(model.position.b.value, pos_err.center.b.value)
def _set_spatial_errors(self, model): d = self.data if "Pos_err_68" in d: percent = 0.68 semi_minor = d["Pos_err_68"] semi_major = d["Pos_err_68"] phi_0 = 0.0 else: percent = 0.95 semi_minor = d["Conf_95_SemiMinor"] semi_major = d["Conf_95_SemiMajor"] phi_0 = d["Conf_95_PosAng"] if np.isnan(phi_0): phi_0 = 0.0 * u.deg scale_1sigma = Gauss2DPDF().containment_radius(percent) lat_err = semi_major / scale_1sigma lon_err = semi_minor / scale_1sigma / np.cos(d["DEJ2000"]) if "TemplateSpatialModel" not in model.tag: model.parameters["lon_0"].error = lon_err model.parameters["lat_0"].error = lat_err model.phi_0 = phi_0
def test_spatial_model(cat): m = cat[1].spatial_model() # p = m.parameters assert isinstance(m, PointSpatialModel) assert m.lon_0.unit == "deg" assert_allclose(m.lon_0.value, 195.614, atol=1e-2) # TODO: add assert on position error # assert_allclose(p.error("lon_0"), tbd) assert m.lat_0.unit == "deg" assert_allclose(m.lat_0.value, 3.507, atol=1e-2) assert m.frame == "galactic" m = cat[1].spatial_model("extended") assert isinstance(m, DiskSpatialModel) assert m.lon_0.unit == "deg" assert_allclose(m.lon_0.value, 195.614, atol=1e-10) assert m.lat_0.unit == "deg" assert_allclose(m.lat_0.value, 3.507, atol=1e-10) assert m.frame == "galactic" assert m.r_0.unit == "deg" assert_allclose(m.r_0.value, 2.0, atol=1e-3) model = cat["2HWC J0534+220"].spatial_model() pos_err = model.position_error scale_r95 = Gauss2DPDF().containment_radius(0.95) assert_allclose(pos_err.height.value, 2 * 0.057 * scale_r95, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.057 * scale_r95, rtol=1e-4) assert_allclose(model.position.l.value, pos_err.center.l.value) assert_allclose(model.position.b.value, pos_err.center.b.value)
def test_spatial_model(ca_3hwc): m = ca_3hwc[1].spatial_model() assert isinstance(m, PointSpatialModel) assert m.lon_0.unit == "deg" assert_allclose(m.lon_0.value, 184.583, atol=1e-2) assert_allclose(m.lon_0.error, 0.112, atol=1e-2) assert m.lat_0.unit == "deg" assert_allclose(m.lat_0.value, -4.129, atol=1e-2) assert m.frame == "galactic" m = ca_3hwc["3HWC J0621+382"].spatial_model() assert isinstance(m, DiskSpatialModel) assert m.lon_0.unit == "deg" assert_allclose(m.lon_0.value, 175.444, atol=1e-10) assert m.lat_0.unit == "deg" assert_allclose(m.lat_0.value, 10.966, atol=1e-10) assert m.frame == "galactic" assert m.r_0.unit == "deg" assert_allclose(m.r_0.value, 0.5, atol=1e-3) model = ca_3hwc["3HWC J0621+382"].spatial_model() pos_err = model.position_error scale_r95 = Gauss2DPDF().containment_radius(0.95) assert_allclose(pos_err.height.value, 2 * 0.301 * scale_r95, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.301 * scale_r95, rtol=1e-4) assert_allclose(model.position.l.value, pos_err.center.l.value) assert_allclose(model.position.b.value, pos_err.center.b.value)
def test_compute_flux_spatial(): center = SkyCoord("0 deg", "0 deg", frame="galactic") region = CircleSkyRegion(center=center, radius=0.1 * u.deg) nbin = 2 energy_axis_true = MapAxis.from_energy_bounds(".1 TeV", "10 TeV", nbin=nbin, name="energy_true") spectral_model = ConstantSpectralModel() spatial_model = PointSpatialModel(lon_0=0 * u.deg, lat_0=0 * u.deg, frame="galactic") models = SkyModel(spectral_model=spectral_model, spatial_model=spatial_model) model = Models(models) exposure_region = RegionNDMap.create(region, axes=[energy_axis_true], binsz_wcs="0.01deg", unit="m2 s", data=1.0) geom = RegionGeom(region, axes=[energy_axis_true], binsz_wcs="0.01deg") psf = PSFKernel.from_gauss(geom.to_wcs_geom(), sigma="0.1 deg") evaluator = MapEvaluator(model=model[0], exposure=exposure_region, psf=psf) flux = evaluator.compute_flux_spatial() g = Gauss2DPDF(0.1) reference = g.containment_fraction(0.1) assert_allclose(flux.value, reference, rtol=0.003)
def from_gauss(cls, energy_axis_true, rad_axis=None, sigma=0.1 * u.deg, geom=None): """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. geom : `Geom` Image geometry. By default an allsky geometry is created. 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() if geom is None: geom = WcsGeom.create( npix=(2, 1), proj="CAR", binsz=180, ) geom = geom.to_cube([rad_axis, energy_axis_true]) coords = geom.get_coord(sparse=True) sigma = u.Quantity(sigma).reshape((-1, 1, 1, 1)) gauss = Gauss2DPDF(sigma=sigma) data = gauss(coords["rad"]) * np.ones(geom.data_shape) psf_map = Map.from_geom(geom=geom, data=data.to_value("sr-1"), unit="sr-1") exposure_map = Map.from_geom(geom=geom.squash(axis_name="rad"), unit="m2 s", data=1.) return cls(psf_map=psf_map, exposure_map=exposure_map)
def test_spatial_model(self): model = self.cat[221].spatial_model() assert model.tag == "PointSpatialModel" assert model.frame == "icrs" p = model.parameters assert_allclose(p["lon_0"].value, 221.281998, rtol=1e-5) assert_allclose(p["lat_0"].value, -3.4943, rtol=1e-5) model = self.cat["2FHL J1304.5-4353"].spatial_model() pos_err = model.position_error scale = Gauss2DPDF().containment_radius(0.95) / Gauss2DPDF().containment_radius( 0.68 ) assert_allclose(pos_err.height.value, 2 * 0.041987 * scale, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.041987 * scale, rtol=1e-4) assert_allclose(model.position.ra.value, pos_err.center.ra.value) assert_allclose(model.position.dec.value, pos_err.center.dec.value) model = self.cat[97].spatial_model() assert model.tag == "GaussianSpatialModel" assert model.frame == "icrs" p = model.parameters assert_allclose(p["lon_0"].value, 94.309998, rtol=1e-5) assert_allclose(p["lat_0"].value, 22.58, rtol=1e-5) assert_allclose(p["sigma"].value, 0.27) model = self.cat[134].spatial_model() assert model.tag == "DiskSpatialModel" assert model.frame == "icrs" p = model.parameters assert_allclose(p["lon_0"].value, 125.660004, rtol=1e-5) assert_allclose(p["lat_0"].value, -42.84, rtol=1e-5) assert_allclose(p["r_0"].value, 0.37) model = self.cat[256].spatial_model() assert model.tag == "TemplateSpatialModel" assert model.frame == "fk5" assert model.normalize is True
def from_gauss( cls, geom, sigma, max_radius=None, containment_fraction=0.99, factor=4 ): """Create Gaussian PSF. This is used for testing and examples. The map geometry parameters (pixel size, energy bins) are taken from ``geom``. The Gaussian width ``sigma`` is a scalar, TODO : support array input if it should vary along the energy axis. Parameters ---------- geom : `~gammapy.maps.WcsGeom` Map geometry sigma : `~astropy.coordinates.Angle` Gaussian width. max_radius : `~astropy.coordinates.Angle` Desired kernel map size. factor : int Oversample factor to compute the PSF Returns ------- kernel : `~gammapy.irf.PSFKernel` the kernel Map with reduced geometry according to the max_radius """ sigma = Angle(sigma) if max_radius is None: max_radius = ( Gauss2DPDF(sigma.deg).containment_radius( containment_fraction=containment_fraction ) * u.deg ) max_radius = Angle(max_radius) # Create a new geom according to given input geom = _make_kernel_geom(geom, max_radius) rad = Angle(np.linspace(0.0, max_radius.deg, 200), "deg") table_psf = TablePSF.from_shape(shape="gauss", width=sigma, rad=rad) return cls.from_table_psf(table_psf, geom=geom, factor=factor)
def test_spatial_model(self, gammacat, ref): source = gammacat[ref["name"]] spatial_model = source.spatial_model() assert spatial_model.frame == "galactic" # TODO: put better asserts on model properties # TODO: add a point and shell source -> separate list of sources for morphology test parametrization? assert spatial_model.__class__.__name__ == ref["spatial_model"] model = gammacat["HESS J1634-472"].spatial_model() pos_err = model.position_error scale_r95 = Gauss2DPDF().containment_radius(0.95) assert_allclose(pos_err.height.value, 2 * 0.044721 * scale_r95, rtol=1e-4) assert_allclose(pos_err.width.value, 2 * 0.044721 * scale_r95, rtol=1e-4) assert_allclose(model.position.l.value, pos_err.center.l.value) assert_allclose(model.position.b.value, pos_err.center.b.value)
def from_shape(cls, shape, width, rad): """Make TablePSF objects with commonly used shapes. This function is mostly useful for examples and testing. Parameters ---------- shape : {'disk', 'gauss'} PSF shape. width : `~astropy.units.Quantity` with angle units PSF width angle (radius for disk, sigma for Gauss). rad : `~astropy.units.Quantity` with angle units Offset angle Returns ------- psf : `TablePSF` Table PSF Examples -------- >>> import numpy as np >>> from astropy.coordinates import Angle >>> from gammapy.irf import TablePSF >>> rad = Angle(np.linspace(0, 0.7, 100), 'deg') >>> psf = TablePSF.from_shape(shape='gauss', width='0.2 deg', rad=rad) """ width = Angle(width) rad = Angle(rad) if shape == "disk": amplitude = 1 / (np.pi * width.radian**2) psf_value = np.where(rad < width, amplitude, 0) elif shape == "gauss": gauss2d_pdf = Gauss2DPDF(sigma=width.radian) psf_value = gauss2d_pdf(rad.radian) else: raise ValueError("Invalid shape: {}".format(shape)) psf_value = u.Quantity(psf_value, "sr^-1") return cls(rad, psf_value)
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)
def test_gauss_convolve(self): g = Gauss2DPDF(sigma=3 * u.deg).gauss_convolve(sigma=4 * u.deg) assert_allclose(g.sigma, 5 * u.deg)
def setup(self): self.gs = [ Gauss2DPDF(0.1 * u.deg), Gauss2DPDF(1 * u.deg), Gauss2DPDF(1 * u.deg), ]
def test_gauss_convolve(self): g = Gauss2DPDF(sigma=3).gauss_convolve(sigma=4) assert_equal(g.sigma, 5)
def setup(self): self.gs = [Gauss2DPDF(0.1), Gauss2DPDF(1), Gauss2DPDF(1)]