def make_psf_map(psf, pointing, geom, max_offset, exposure_map=None): """Make a psf map for a single observation Expected axes : rad and true energy in this specific order The name of the rad MapAxis is expected to be 'rad' Parameters ---------- psf : `~gammapy.irf.PSF3D` the PSF IRF pointing : `~astropy.coordinates.SkyCoord` the pointing direction geom : `~gammapy.maps.MapGeom` the map geom to be used. It provides the target geometry. rad and true energy axes should be given in this specific order. max_offset : `~astropy.coordinates.Angle` maximum offset w.r.t. fov center exposure_map : `~gammapy.maps.Map`, optional the associated exposure map. default is None Returns ------- psfmap : `~gammapy.cube.PSFMap` the resulting PSF map """ energy = geom.get_axis_by_name("energy").center rad_axis = geom.get_axis_by_name("theta") rad = Angle(rad_axis.center, unit=rad_axis.unit) # Compute separations with pointing position separations = pointing.separation(geom.to_image().get_coord().skycoord) valid = np.where(separations < max_offset) # Compute PSF values psf_values = psf.evaluate(offset=separations[valid], energy=energy, rad=rad) # Re-order axes to be consistent with expected geometry psf_values = np.transpose(psf_values, axes=(2, 0, 1)) # TODO: this probably does not ensure that probability is properly normalized in the PSFMap # Create Map and fill relevant entries psfmap = Map.from_geom(geom, unit="sr-1") psfmap.data[:, :, valid[0], valid[1]] += psf_values.to_value(psfmap.unit) return PSFMap(psfmap, exposure_map)
def run(self, dataset, observation): """Make map dataset. Parameters ---------- dataset : `~gammapy.datasets.MapDataset` Reference dataset. observation : `~gammapy.data.Observation` Observation Returns ------- dataset : `~gammapy.datasets.MapDataset` Map dataset. """ kwargs = {"gti": observation.gti} kwargs["meta_table"] = self.make_meta_table(observation) mask_safe = Map.from_geom(dataset.counts.geom, dtype=bool) mask_safe.data |= True kwargs["mask_safe"] = mask_safe if "counts" in self.selection: counts = self.make_counts(dataset.counts.geom, observation) kwargs["counts"] = counts if "exposure" in self.selection: exposure = self.make_exposure(dataset.exposure.geom, observation) kwargs["exposure"] = exposure if "background" in self.selection: kwargs["background"] = self.make_background( dataset.counts.geom, observation) if "psf" in self.selection: psf = self.make_psf(dataset.psf.psf_map.geom, observation) kwargs["psf"] = psf if "edisp" in self.selection: if dataset.edisp.edisp_map.geom.axes[0].name.upper() == "MIGRA": edisp = self.make_edisp(dataset.edisp.edisp_map.geom, observation) else: edisp = self.make_edisp_kernel(dataset.edisp.edisp_map.geom, observation) kwargs["edisp"] = edisp return MapDataset(name=dataset.name, **kwargs)
def run(self, images, niter_min=2, niter_max=10): """Run iterations until mask does not change (stopping condition). Parameters ---------- images : dict Input sky images: counts, background, exclusion niter_min : int Minimum number of iterations, to prevent early termination of the algorithm. niter_max : int Maximum number of iterations after which the algorithm is terminated, if the termination condition (no change of mask between iterations) is not already satisfied. Returns ------- images : dict Sky images: background, exclusion, significance """ # initial mask, if not present if "exclusion" not in images: exclusion = Map.from_geom(images["counts"].geom) exclusion.data += 1 images["exclusion"] = exclusion # initial background estimate, if not present if "background" not in images: images["background"] = self._estimate_background( images["counts"], images["exclusion"] ) images["significance"] = self._estimate_significance( images["counts"], images["background"] ) self.images_stack.append(images) for idx in range(niter_max): result = self.run_iteration(images) if self.parameters["keep_record"]: self.images_stack.append(result) if self._is_converged(result, images) and (idx >= niter_min): log.info(f"Exclusion mask converged after {idx} iterations.") break return result
def estimate_kernel(self, dataset): """Get the convolution kernel for the input dataset. Convolves the model with the PSFKernel at the center of the dataset. Parameters ---------- dataset : `~gammapy.datasets.MapDataset` Input dataset. Returns ------- kernel : `Map` Kernel map """ # TODO: further simplify the code below geom = dataset.counts.geom model = self.model.copy() model.spatial_model.position = geom.center_skydir binsz = np.mean(geom.pixel_scales) width_pix = self.kernel_width / binsz npix = round_up_to_odd(width_pix.to_value("")) axis = dataset.exposure.geom.axes["energy_true"] geom_kernel = WcsGeom.create( skydir=model.position, proj="TAN", npix=npix, axes=[axis], binsz=binsz ) exposure = Map.from_geom(geom_kernel, unit="cm2 s1") exposure.data += 1.0 # We use global evaluation mode to not modify the geometry evaluator = MapEvaluator(model, evaluation_mode="global") evaluator.update(exposure, dataset.psf, dataset.edisp, dataset.counts.geom) kernel = evaluator.compute_npred() kernel.data /= kernel.data.sum() if (self.kernel_width + binsz >= geom.width).any(): raise ValueError( "Kernel shape larger than map shape, please adjust" " size of the kernel" ) return kernel
def test_get_spectrum_weights(): axis = MapAxis.from_bounds(1, 10, nbin=3, unit="TeV", name="energy") geom = WcsGeom.create( skydir=(0, 0), width=(2.5, 2.5), binsz=0.5, axes=[axis], frame="galactic" ) m_int = Map.from_geom(geom, dtype="int") m_int.data += 1 weights = Map.from_geom(geom, dtype="bool") weights.data[:, 2, 2] = True bad_weights = Map.from_geom(geom.to_image(), dtype="bool") center = SkyCoord(0, 0, frame="galactic", unit="deg") region = CircleSkyRegion(center=center, radius=1 * u.deg) spec_int = m_int.get_spectrum(region=region, weights=weights) assert spec_int.data.dtype == np.dtype("int") assert_allclose(spec_int.data.squeeze(), [1, 1, 1]) with pytest.raises(ValueError): m_int.get_spectrum(region=region, weights=bad_weights)
def from_energy_dependent_table_psf(cls, table_psf, geom=None): """Create PSF map from table PSF object. Helper function to create an allsky PSF map from table PSF, which does not depend on position. Parameters ---------- table_psf : `EnergyDependentTablePSF` Table PSF Returns ------- psf_map : `PSFMap` Point spread function map. """ if geom is None: geom = WcsGeom.create( npix=(2, 1), proj="CAR", binsz=180, axes=[table_psf.rad_axis, table_psf.energy_axis_true]) coords = geom.get_coord() # TODO: support broadcasting in .evaluate() data = table_psf._interpolate( (coords["energy_true"], coords["rad"])).to_value("sr-1") psf_map = Map.from_geom(geom, data=data, unit="sr-1") geom_exposure = geom.squash(axis_name="rad") data = table_psf.exposure.reshape((-1, 1, 1, 1)) exposure_map = Map.from_geom(geom_exposure, unit="cm2 s") exposure_map.quantity += data return cls(psf_map=psf_map, exposure_map=exposure_map)
def test_downsample_onoff(): axis = MapAxis.from_energy_bounds(1, 10, 4, unit="TeV") geom = WcsGeom.create(npix=(10, 10), binsz=0.05, axes=[axis]) counts = Map.from_geom(geom, data=np.ones((4, 10, 10))) counts_off = Map.from_geom(geom, data=np.ones((4, 10, 10))) acceptance = Map.from_geom(geom, data=np.ones((4, 10, 10))) acceptance_off = Map.from_geom(geom, data=np.ones((4, 10, 10))) acceptance_off *= 2 dataset_onoff = MapDatasetOnOff( counts=counts, counts_off=counts_off, acceptance=acceptance, acceptance_off=acceptance_off, ) downsampled = dataset_onoff.downsample(2, axis_name="energy") assert downsampled.counts.data.shape == (2, 10, 10) assert downsampled.counts.data.sum() == dataset_onoff.counts.data.sum() assert downsampled.counts_off.data.sum( ) == dataset_onoff.counts_off.data.sum() assert_allclose(downsampled.alpha.data, 0.5)
def get_map_dataset_onoff(images, **kwargs): """Returns a MapDatasetOnOff""" mask_geom = images["counts"].geom mask_data = np.ones(images["counts"].data.shape, dtype=bool) mask_safe = Map.from_geom(mask_geom, data=mask_data) return MapDatasetOnOff( counts=images["counts"], counts_off=images["counts_off"], acceptance=images["acceptance"], acceptance_off=images["acceptance_off"], exposure=images["exposure"], mask_safe=mask_safe, **kwargs )
def make_psf_map(psf, pointing, geom, exposure_map=None): """Make a psf map for a single observation Expected axes : rad and true energy in this specific order The name of the rad MapAxis is expected to be 'rad' Parameters ---------- psf : `~gammapy.irf.PSF3D` the PSF IRF pointing : `~astropy.coordinates.SkyCoord` the pointing direction geom : `~gammapy.maps.Geom` the map geom to be used. It provides the target geometry. rad and true energy axes should be given in this specific order. exposure_map : `~gammapy.maps.Map`, optional the associated exposure map. default is None Returns ------- psfmap : `~gammapy.cube.PSFMap` the resulting PSF map """ energy_axis = geom.get_axis_by_name("energy") energy = energy_axis.center rad_axis = geom.get_axis_by_name("theta") rad = rad_axis.center # Compute separations with pointing position offset = geom.separation(pointing) # Compute PSF values # TODO: allow broadcasting in PSF3D.evaluate() psf_values = psf._interpolate( ( rad[:, np.newaxis, np.newaxis], offset, energy[:, np.newaxis, np.newaxis, np.newaxis], ) ) # TODO: this probably does not ensure that probability is properly normalized in the PSFMap # Create Map and fill relevant entries data = psf_values.to_value("sr-1") psfmap = Map.from_geom(geom, data=data, unit="sr-1") return PSFMap(psfmap, exposure_map)
def run(self, dataset, observation): """Make map dataset. Parameters ---------- dataset : `~gammapy.cube.MapDataset` Reference dataset. observation : `~gammapy.data.Observation` Observation Returns ------- dataset : `~gammapy.cube.MapDataset` Map dataset. """ kwargs = {"gti": observation.gti} mask_safe = Map.from_geom(dataset.counts.geom, dtype=bool) mask_safe.data |= True kwargs["mask_safe"] = mask_safe if "counts" in self.selection: counts = self.make_counts(dataset.counts.geom, observation) kwargs["counts"] = counts if "exposure" in self.selection: exposure = self.make_exposure(dataset.exposure.geom, observation) kwargs["exposure"] = exposure if "background" in self.selection: background_map = self.make_background(dataset.counts.geom, observation) kwargs["models"] = BackgroundModel( background_map, name=dataset.name + "-bkg", datasets_names=[dataset.name], ) if "psf" in self.selection: psf = self.make_psf(dataset.psf.psf_map.geom, observation) kwargs["psf"] = psf if "edisp" in self.selection: edisp = self.make_edisp(dataset.edisp.edisp_map.geom, observation) kwargs["edisp"] = edisp return MapDataset(name=dataset.name, **kwargs)
def get_psf_kernel(self, geom, position=None, max_radius=None, factor=4): """Returns a PSF kernel at the given position. The PSF is returned in the form a WcsNDMap defined by the input Geom. Parameters ---------- geom : `~gammapy.maps.Geom` Target geometry to use position : `~astropy.coordinates.SkyCoord` Target position. Should be a single coordinate. By default the center position is used. max_radius : `~astropy.coordinates.Angle` maximum angular size of the kernel map factor : int oversampling factor to compute the PSF Returns ------- kernel : `~gammapy.irf.PSFKernel` the resulting kernel """ # TODO: try to simplify...is the oversampling needed? if position is None: position = self.psf_map.geom.center_skydir position = self._get_nearest_valid_position(position) if max_radius is None: max_radius = np.max(self.psf_map.geom.axes["rad"].center) min_radius_geom = np.min(geom.width) / 2.0 max_radius = min(max_radius, min_radius_geom) geom = geom.to_odd_npix(max_radius=max_radius) geom_upsampled = geom.upsample(factor=factor) rad = geom_upsampled.separation(geom.center_skydir) energy_axis = geom.axes["energy_true"] energy = energy_axis.center[:, np.newaxis, np.newaxis] coords = {"energy_true": energy, "rad": rad, "skycoord": position} data = self.psf_map.interp_by_coord(coords=coords, method="linear",) kernel_map = Map.from_geom(geom=geom_upsampled, data=np.clip(data, 0, np.inf)) kernel_map = kernel_map.downsample(factor, preserve_counts=True) return PSFKernel(kernel_map, normalize=True)
def plot_regions(self, ax=None, kwargs_point=None, path_effect=None, **kwargs): """Plot extent of the spatial models on a given wcs axis Parameters ---------- ax : `~astropy.visualization.WCSAxes` Axes to plot on. If no axes are given, an all-sky wcs is chosen using a CAR projection kwargs_point : dict Keyword arguments passed to `~matplotlib.lines.Line2D` for plotting of point sources path_effect : `~matplotlib.patheffects.PathEffect` Path effect applied to artists and lines. **kwargs : dict Keyword arguments passed to `~matplotlib.artists.Artist` Returns ------- ax : `~astropy.visualization.WcsAxes` WCS axes """ from astropy.visualization.wcsaxes import WCSAxes kwargs_point = kwargs_point or {} if ax is None or not isinstance(ax, WCSAxes): ax = Map.from_geom(self.wcs_geom).plot() kwargs.setdefault("color", "tab:blue") kwargs.setdefault("fc", "None") kwargs_point.setdefault("marker", "*") kwargs_point.setdefault("markersize", 10) kwargs_point.setdefault("markeredgecolor", "None") kwargs_point.setdefault("color", kwargs["color"]) for region in self.to_regions(): if isinstance(region, PointSkyRegion): artist = region.to_pixel(ax.wcs).as_artist(**kwargs_point) else: artist = region.to_pixel(ax.wcs).as_artist(**kwargs) if path_effect: artist.set_path_effects([path_effect]) ax.add_artist(artist) return ax
def test_reduce(): # Check summing over a specific axis ax1 = MapAxis.from_nodes([1, 2, 3, 4], name="ax1") ax2 = MapAxis.from_nodes([5, 6, 7], name="ax2") ax3 = MapAxis.from_nodes([8, 9], name="ax3") geom = WcsGeom.create(npix=(5, 5), axes=[ax1, ax2, ax3]) m1 = Map.from_geom(geom=geom) m1.data = np.ones(m1.data.shape) m2 = m1.reduce(axis_name="ax1", keepdims=True) assert_allclose(m2.geom.data_shape, (2, 3, 1, 5, 5)) assert_allclose(m2.data[0][0][0][0][0], 4.0) m3 = m1.reduce(axis_name="ax1", keepdims=False) assert_allclose(m3.geom.data_shape, (2, 3, 5, 5)) assert_allclose(m3.data[0][0][0][0], 4.0)
def test_mask_fit_modifications(): axis = MapAxis.from_energy_bounds("0.1 TeV", "10 TeV", nbin=2) geom = WcsGeom.create( skydir=(266.40498829, -28.93617776), binsz=0.02, width=(2, 2), frame="icrs", axes=[axis], ) mask_fit = Map.from_geom(geom) mask_fit.data = np.ones(mask_fit.data.shape, dtype=bool) mask_fit = mask_fit & geom.boundary_mask(width=(0.3 * u.deg, 0.1 * u.deg)) assert np.sum(mask_fit.data[0, :, :]) == 6300 assert np.sum(mask_fit.data[1, :, :]) == 6300 mask_fit = mask_fit.binary_dilate(width=(0.3 * u.deg, 0.1 * u.deg)) assert np.sum(mask_fit.data) == np.prod(mask_fit.data.shape)
def test_map_dataset_on_off_fake(geom): rad_axis = MapAxis(nodes=np.linspace(0.0, 1.0, 51), unit="deg", name="rad") energy_true_axis = geom.axes["energy"].copy(name="energy_true") empty_dataset = MapDataset.create(geom, energy_true_axis, rad_axis=rad_axis) empty_dataset = MapDatasetOnOff.from_map_dataset( empty_dataset, acceptance=1, acceptance_off=10.0 ) empty_dataset.acceptance_off.data[0, 50, 50] = 0 background_map = Map.from_geom(geom, data=1) empty_dataset.fake(background_map, random_state=42) assert_allclose(empty_dataset.counts.data[0, 50, 50], 0) assert_allclose(empty_dataset.counts.data.mean(), 0.99445, rtol=1e-3) assert_allclose(empty_dataset.counts_off.data.mean(), 10.00055, rtol=1e-3)
def backgrounds(): axis = MapAxis.from_edges(np.logspace(-1, 1, 3), unit=u.TeV, name="energy") geom = WcsGeom.create(skydir=(0, 0), npix=(5, 4), frame="galactic", axes=[axis]) m = Map.from_geom(geom) m.quantity = np.ones(geom.data_shape) * 1e-7 background1 = TemplateNPredModel(m, name="bkg1", datasets_names="dataset-1") background2 = TemplateNPredModel(m, name="bkg2", datasets_names=["dataset-2"]) backgrounds = [background1, background2] return backgrounds
def get_map_dataset_onoff(images, **kwargs): """Returns a MapDatasetOnOff""" mask_geom = images["counts"].geom mask_data = np.ones(images["counts"].data.shape, dtype=bool) mask_safe = Map.from_geom(mask_geom, data=mask_data) gti = GTI.create([0 * u.s], [1 * u.h], reference_time="2010-01-01T00:00:00") return MapDatasetOnOff(counts=images["counts"], counts_off=images["counts_off"], acceptance=images["acceptance"], acceptance_off=images["acceptance_off"], exposure=images["exposure"], mask_safe=mask_safe, gti=gti, **kwargs)
def from_table_psf(cls, table_psf, geom, max_radius=None, factor=4): """Create a PSF kernel from a TablePSF or an EnergyDependentTablePSF on a given Geom. If the Geom is not an image, the same kernel will be present on all axes. The PSF is estimated by oversampling defined by a given factor. Parameters ---------- table_psf : `~gammapy.irf.EnergyDependentTablePSF` Input table PSF geom : `~gammapy.maps.WcsGeom` Target geometry. The PSF kernel will be centered on the central pixel. The geometry axes should contain an axis with name "energy" max_radius : `~astropy.coordinates.Angle` Maximum radius of the PSF kernel. factor : int Oversample factor to compute the PSF Returns ------- kernel : `~gammapy.irf.PSFKernel` the kernel Map with reduced geometry according to the max_radius """ # TODO : use PSF containment radius if max_radius is None if max_radius is not None: geom = _make_kernel_geom(geom, max_radius) geom_upsampled = geom.upsample(factor=factor) rad = geom_upsampled.separation(geom.center_skydir) if isinstance(table_psf, EnergyDependentTablePSF): energy_axis = geom.axes["energy_true"] energy = energy_axis.center[:, np.newaxis, np.newaxis] data = table_psf.evaluate(energy=energy, rad=rad).value else: try: nbin = geom.axes[0].nbin except IndexError: nbin = 1 data = table_psf.evaluate(rad=rad).value data = data * np.ones(nbin).reshape((-1, 1, 1)) kernel_map = Map.from_geom(geom=geom_upsampled, data=data) kernel_map = kernel_map.downsample(factor, preserve_counts=True) return cls(kernel_map, normalize=True)
def integrate_geom(self, geom): """Integrate model on `~gammapy.maps.Geom` Parameters ---------- geom : `Geom` Map geometry Returns ------- flux : `Map` Predicted flux map """ x, y = geom.get_pix()[0:2] x0, y0 = self.position.to_pixel(geom.wcs) data = self._grid_weights(x, y, x0, y0) return Map.from_geom(geom=geom, data=data, unit="")
def make_mask_safe(self, observation): """Make offset mask. Parameters ---------- observation : `DataStoreObservation` Observation container. Returns ------- mask : `Map` Mask """ geom = self._cutout_geom(self.geom.to_image(), observation) offset = geom.separation(observation.pointing_radec) data = offset >= self.offset_max return Map.from_geom(geom, data=data)
def make_psf_map(psf, pointing, geom, exposure_map=None): """Make a psf map for a single observation Expected axes : rad and true energy in this specific order The name of the rad MapAxis is expected to be 'rad' Parameters ---------- psf : `~gammapy.irf.PSF3D` the PSF IRF pointing : `~astropy.coordinates.SkyCoord` the pointing direction geom : `~gammapy.maps.Geom` the map geom to be used. It provides the target geometry. rad and true energy axes should be given in this specific order. exposure_map : `~gammapy.maps.Map`, optional the associated exposure map. default is None use_region_center: bool If geom is a RegionGeom, whether to just consider the values at the region center or the insted the average over the whole region Returns ------- psfmap : `~gammapy.irf.PSFMap` the resulting PSF map """ energy_true = geom.axes["energy_true"].center rad = geom.axes["rad"].center # Compute separations with pointing position offset = geom.separation(pointing) # Compute PSF values psf_values = psf.evaluate( energy_true=energy_true[:, np.newaxis, np.newaxis, np.newaxis], offset=offset, rad=rad[:, np.newaxis, np.newaxis], ) # TODO: this probably does not ensure that probability is properly normalized in the PSFMap # Create Map and fill relevant entries data = psf_values.to_value("sr-1") psfmap = Map.from_geom(geom, data=data, unit="sr-1") return PSFMap(psfmap, exposure_map)
def to_edisp_kernel_map(self, energy_axis): """Convert to map with edisp kernels Parameters ---------- energy_axis : `~gammapy.maps.MapAxis` Reconstructed energy axis. Returns ------- edisp : `~gammapy.maps.EDispKernelMap` Energy dispersion kernel map. """ energy_axis_true = self.edisp_map.geom.axes["energy_true"] geom_image = self.edisp_map.geom.to_image() # migration value of energy bounds migra = energy_axis.edges / energy_axis_true.center[:, np.newaxis] coords = { "energy_true": energy_axis_true.center[:, np.newaxis, np.newaxis, np.newaxis], "migra": migra[:, :, np.newaxis, np.newaxis], "skycoord": geom_image.get_coord().skycoord } values = self.edisp_map.integral(axis_name="migra", coords=coords) axis = self.edisp_map.geom.axes.index_data("migra") data = np.clip(np.diff(values, axis=axis), 0, np.inf) geom = geom_image.to_cube([energy_axis, energy_axis_true]) edisp_kernel_map = Map.from_geom(geom=geom, data=data.to_value(""), unit="") if self.exposure_map: geom = geom.squash(axis_name=energy_axis.name) exposure_map = self.exposure_map.copy(geom=geom) else: exposure_map = None return EDispKernelMap(edisp_kernel_map=edisp_kernel_map, exposure_map=exposure_map)
def make_counts(self, observation): """Make counts map. Parameters ---------- observation : `DataStoreObservation` Observation container. Returns ------- counts : `Map` Counts map. """ geom = self._cutout_geom(self.geom, observation) counts = Map.from_geom(geom) fill_map_counts(counts, observation.events) return counts
def estimate_flux_map(self, dataset): """Estimate flux and ts maps for single dataset Parameters ---------- dataset : `MapDataset` Map dataset """ maps = self.estimate_fit_input_maps(dataset=dataset) wrap = functools.partial( _ts_value, counts=maps["counts"].data.astype(float), exposure=maps["exposure"].data.astype(float), background=maps["background"].data.astype(float), kernel=maps["kernel"].data, norm=maps["norm"].data, flux_estimator=self._flux_estimator, ) x, y = np.where(np.squeeze(maps["mask"].data)) positions = list(zip(x, y)) if self.n_jobs is None: results = list(map(wrap, positions)) else: with contextlib.closing(Pool(processes=self.n_jobs)) as pool: log.info("Using {} jobs to compute TS map.".format( self.n_jobs)) results = pool.map(wrap, positions) pool.join() result = {} j, i = zip(*positions) geom = maps["counts"].geom.squash(axis_name="energy") for name in self.selection_all: m = Map.from_geom(geom=geom, data=np.nan, unit="") m.data[0, j, i] = [_[name] for _ in results] result[name] = m return result
def estimate_kernel(self, dataset): """Get the convolution kernel for the input dataset. Convolves the model with the PSFKernel at the center of the dataset. Parameters ---------- dataset : `~gammapy.datasets.MapDataset` Input dataset. Returns ------- kernel : `Map` Kernel map """ # TODO: further simplify the code below geom = dataset.exposure.geom model = self.model.copy() model.spatial_model.position = geom.center_skydir geom_kernel = geom.to_odd_npix(max_radius=self.kernel_width / 2) exposure = Map.from_geom(geom_kernel, unit="cm2 s1") exposure.data += 1.0 # We use global evaluation mode to not modify the geometry evaluator = MapEvaluator(model, evaluation_mode="global") evaluator.update( exposure, dataset.psf, dataset.edisp, dataset.counts.geom, dataset.mask_fit, ) kernel = evaluator.compute_npred() kernel.data /= kernel.data.sum() if (self.kernel_width >= geom.width).any(): raise ValueError( "Kernel shape larger than map shape, please adjust" " size of the kernel") return kernel
def make_map_exposure_true_energy(pointing, livetime, aeff, geom, use_region_center=True): """Compute exposure map. This map has a true energy axis, the exposure is not combined with energy dispersion. Parameters ---------- pointing : `~astropy.coordinates.SkyCoord` Pointing direction livetime : `~astropy.units.Quantity` Livetime aeff : `~gammapy.irf.EffectiveAreaTable2D` Effective area geom : `~gammapy.maps.WcsGeom` Map geometry (must have an energy axis) use_region_center: bool If geom is a RegionGeom, whether to just consider the values at the region center or the instead the average over the whole region Returns ------- map : `~gammapy.maps.WcsNDMap` Exposure map """ if not use_region_center: coords, weights = geom.get_wcs_coord_and_weights() else: coords, weights = geom.get_coord(sparse=True), None offset = coords.skycoord.separation(pointing) exposure = aeff.evaluate(offset=offset, energy_true=coords["energy_true"]) data = (exposure * livetime).to("m2 s") meta = {"livetime": livetime, "is_pointlike": aeff.is_pointlike} if not use_region_center: data = np.average(data, axis=1, weights=weights) return Map.from_geom(geom=geom, data=data.value, unit=data.unit, meta=meta)
def to_edisp_kernel_map(self, energy_axis): """Convert to map with edisp kernels Parameters ---------- energy_axis : `~gammapy.maps.MapAxis` Reconstructed energy axis. Returns ------- edisp : `~gammapy.maps.EDispKernelMap` Energy dispersion kernel map. """ energy_axis_true = self.edisp_map.geom.axes["energy_true"] geom_image = self.edisp_map.geom.to_image() geom = geom_image.to_cube([energy_axis, energy_axis_true]) coords = geom.get_coord(sparse=True, mode="edges", axis_name="energy") migra = coords["energy"] / coords["energy_true"] coords = { "skycoord": coords.skycoord, "energy_true": coords["energy_true"], "migra": migra, } values = self.edisp_map.integral(axis_name="migra", coords=coords) axis = self.edisp_map.geom.axes.index_data("migra") data = np.clip(np.diff(values, axis=axis), 0, np.inf) edisp_kernel_map = Map.from_geom(geom=geom, data=data.to_value(""), unit="") if self.exposure_map: geom = geom.squash(axis_name=energy_axis.name) exposure_map = self.exposure_map.copy(geom=geom) else: exposure_map = None return EDispKernelMap(edisp_kernel_map=edisp_kernel_map, exposure_map=exposure_map)
def run(self, dataset, observation=None): """Make safe data range mask. Parameters ---------- dataset : `~gammapy.datasets.MapDataset` or `~gammapy.datasets.SpectrumDataset` Dataset to compute mask for. observation: `~gammapy.data.Observation` Observation to compute mask for. Returns ------- dataset : `Dataset` Dataset with defined safe range mask. """ if dataset.mask_safe: mask_safe = dataset.mask_safe.data else: mask_safe = np.ones(dataset._geom.data_shape, dtype=bool) if dataset.background is not None: # apply it first so only clipped values are removed for "bkg-peak" mask_safe &= self.make_mask_bkg_invalid(dataset) if "offset-max" in self.methods: mask_safe &= self.make_mask_offset_max(dataset, observation) if "aeff-default" in self.methods: mask_safe &= self.make_mask_energy_aeff_default( dataset, observation) if "aeff-max" in self.methods: mask_safe &= self.make_mask_energy_aeff_max(dataset) if "edisp-bias" in self.methods: mask_safe &= self.make_mask_energy_edisp_bias(dataset) if "bkg-peak" in self.methods: mask_safe &= self.make_mask_energy_bkg_peak(dataset) dataset.mask_safe = Map.from_geom(dataset._geom, data=mask_safe, dtype=bool) return dataset
def make_counts(geom, observation): """Make counts map. Parameters ---------- geom : `~gammapy.maps.Geom` Reference map geom. observation : `~gammapy.data.Observation` Observation container. Returns ------- counts : `~gammapy.maps.Map` Counts map. """ counts = Map.from_geom(geom) counts.fill_events(observation.events) return counts
def run_fit(self, optimize_opts=None): """Fitting reduced datasets to model.""" if not self.models: raise RuntimeError("Missing models") fit_settings = self.config.fit for dataset in self.datasets: if fit_settings.fit_range: energy_min = fit_settings.fit_range.min energy_max = fit_settings.fit_range.max geom = dataset.counts.geom data = geom.energy_mask(energy_min, energy_max) dataset.mask_fit = Map.from_geom(geom=geom, data=data) log.info("Fitting datasets.") self.fit = Fit(self.datasets) self.fit_result = self.fit.run(optimize_opts=optimize_opts) log.info(self.fit_result)