Repository: gammapy/gammapy

Issue:
Allow to specify spectral model in ExcessMapEstimator
Currently the `ExcessMapEstimator` does not allow to define the spectral model, that is used for the flux computation. It is easy to support and should be done...



Discussion:
Merci! We will respond to your issue shortly. In the meantime, try `import gammapy; gammapy.song()`

Resolving patch:
diff --git a/gammapy/estimators/excess_map.py b/gammapy/estimators/excess_map.py
--- a/gammapy/estimators/excess_map.py
+++ b/gammapy/estimators/excess_map.py
@@ -89,6 +89,9 @@ class ExcessMapEstimator(Estimator):
         A `~gammapy.datasets.MapDataset.mask_fit` must be present on the input dataset
     correlate_off : Bool
         Correlate OFF events in the case of a MapDatasetOnOff
+    spectral_model : `~gammapy.modeling.models.SpectralModel`
+        Spectral model used for the computation of the flux map. 
+        If None, a Power Law of index 2 is assumed (default). 
     """
 
     tag = "ExcessMapEstimator"
@@ -102,7 +105,8 @@ def __init__(
         selection_optional=None,
         energy_edges=None,
         apply_mask_fit=False,
-        correlate_off=False
+        correlate_off=False,
+        spectral_model=None,
     ):
         self.correlation_radius = correlation_radius
         self.n_sigma = n_sigma
@@ -111,6 +115,7 @@ def __init__(
         self.selection_optional = selection_optional
         self.energy_edges = energy_edges
         self.correlate_off = correlate_off
+        self.spectral_model = spectral_model
 
     @property
     def correlation_radius(self):
@@ -193,7 +198,9 @@ def estimate_excess_map(self, dataset):
         else:
             mask = np.ones(dataset.data_shape, dtype=bool)
 
-        counts_stat = convolved_map_dataset_counts_statistics(dataset, kernel, mask, self.correlate_off)
+        counts_stat = convolved_map_dataset_counts_statistics(
+            dataset, kernel, mask, self.correlate_off
+        )
 
         n_on = Map.from_geom(geom, data=counts_stat.n_on)
         bkg = Map.from_geom(geom, data=counts_stat.n_on - counts_stat.n_sig)
@@ -209,7 +216,7 @@ def estimate_excess_map(self, dataset):
         result.update({"err": err})
 
         if dataset.exposure:
-            reco_exposure = estimate_exposure_reco_energy(dataset)
+            reco_exposure = estimate_exposure_reco_energy(dataset, self.spectral_model)
             with np.errstate(invalid="ignore", divide="ignore"):
                 flux = excess / reco_exposure
             flux.quantity = flux.quantity.to("1 / (cm2 s)")


Test patch:
diff --git a/gammapy/estimators/tests/test_excess_map.py b/gammapy/estimators/tests/test_excess_map.py
--- a/gammapy/estimators/tests/test_excess_map.py
+++ b/gammapy/estimators/tests/test_excess_map.py
@@ -12,7 +12,7 @@
     SkyModel,
 )
 from gammapy.utils.testing import requires_data
-
+from gammapy.estimators.utils import estimate_exposure_reco_energy
 
 def image_to_cube(input_map, energy_min, energy_max):
     energy_min = u.Quantity(energy_min)
@@ -120,13 +120,17 @@ def test_significance_map_estimator_map_dataset(simple_dataset):
     assert_allclose(result["ul"].data[0, 10, 10], 122.240837, atol=1e-3)
 
 
-def test_significance_map_estimator_map_dataset_on_off_no_correlation(simple_dataset_on_off):
+def test_significance_map_estimator_map_dataset_on_off_no_correlation(
+    simple_dataset_on_off,
+):
     exposure = simple_dataset_on_off.exposure
     exposure.data += 1e6
 
     # Test with exposure
     simple_dataset_on_off.exposure = exposure
-    estimator_image = ExcessMapEstimator(0.11 * u.deg, energy_edges=[0.1, 1] * u.TeV, correlate_off=False)
+    estimator_image = ExcessMapEstimator(
+        0.11 * u.deg, energy_edges=[0.1, 1] * u.TeV, correlate_off=False
+    )
 
     result_image = estimator_image.run(simple_dataset_on_off)
     assert result_image["counts"].data.shape == (1, 20, 20)
@@ -137,7 +141,9 @@ def test_significance_map_estimator_map_dataset_on_off_no_correlation(simple_dat
     assert_allclose(result_image["flux"].data[:, 10, 10], 9.7e-9, atol=1e-5)
 
 
-def test_significance_map_estimator_map_dataset_on_off_with_correlation(simple_dataset_on_off):
+def test_significance_map_estimator_map_dataset_on_off_with_correlation(
+    simple_dataset_on_off,
+):
     exposure = simple_dataset_on_off.exposure
     exposure.data += 1e6
 
@@ -158,7 +164,9 @@ def test_significance_map_estimator_map_dataset_on_off_with_correlation(simple_d
 
     # Test with exposure
     simple_dataset_on_off.exposure = exposure
-    estimator_image = ExcessMapEstimator(0.11 * u.deg, energy_edges=[0.1, 1] * u.TeV, correlate_off=True)
+    estimator_image = ExcessMapEstimator(
+        0.11 * u.deg, energy_edges=[0.1, 1] * u.TeV, correlate_off=True
+    )
 
     result_image = estimator_image.run(simple_dataset_on_off)
     assert result_image["counts"].data.shape == (1, 20, 20)
@@ -177,7 +185,9 @@ def test_significance_map_estimator_map_dataset_on_off_with_correlation(simple_d
     mask_fit.data[:, 10, :] = False
     simple_dataset_on_off.mask_fit = mask_fit
 
-    estimator_image = ExcessMapEstimator(0.11 * u.deg, apply_mask_fit=True, correlate_off=True)
+    estimator_image = ExcessMapEstimator(
+        0.11 * u.deg, apply_mask_fit=True, correlate_off=True
+    )
 
     result_image = estimator_image.run(simple_dataset_on_off)
     assert result_image["counts"].data.shape == (1, 20, 20)
@@ -207,7 +217,9 @@ def test_significance_map_estimator_map_dataset_on_off_with_correlation(simple_d
     #
     simple_dataset_on_off.models = [model]
 
-    estimator_mod = ExcessMapEstimator(0.11 * u.deg, apply_mask_fit=False, correlate_off=True)
+    estimator_mod = ExcessMapEstimator(
+        0.11 * u.deg, apply_mask_fit=False, correlate_off=True
+    )
     result_mod = estimator_mod.run(simple_dataset_on_off)
     assert result_mod["counts"].data.shape == (1, 20, 20)
 
@@ -217,9 +229,24 @@ def test_significance_map_estimator_map_dataset_on_off_with_correlation(simple_d
     assert_allclose(result_mod["excess"].data[0, 10, 10], 148.68057)
     assert_allclose(result_mod["background"].data[0, 10, 10], 239.31943)
 
-    assert result_mod["flux"].unit == u.Unit("cm-2s-1")
+    assert result_mod["flux"].unit == "cm-2s-1"
     assert_allclose(result_mod["flux"].data[0, 10, 10], 1.486806e-08, rtol=1e-3)
+    assert_allclose(result_mod["flux"].data.sum(), 5.254442192077636e-06, rtol=1e-8)
+
+    spectral_model=PowerLawSpectralModel(index=15)
+    estimator_mod = ExcessMapEstimator(
+        0.11 * u.deg,
+        apply_mask_fit=False,
+        correlate_off=True,
+        spectral_model=spectral_model,
+    )
+    result_mod = estimator_mod.run(simple_dataset_on_off)
+
+    assert result_mod["flux"].unit == "cm-2s-1"
+    assert_allclose(result_mod["flux"].data.sum(), 5.254442192077636e-06, rtol=1e-8)
 
+    reco_exposure=estimate_exposure_reco_energy(simple_dataset_on_off, spectral_model=spectral_model)
+    assert_allclose(reco_exposure.data.sum(), 7.977796e+12, rtol=0.001)
 
 def test_incorrect_selection():
     with pytest.raises(ValueError):