Repository: gammapy/gammapy

Issue:
info_table() fails for MapDatasets
**Gammapy version**

0.16.dev

**Bug description**

When working with MapDatasets calling the `info_table` method fails with the following error.
```
AttributeError: 'MapDataset' object has no attribute 'info_dict'
```

**Expected behavior**

I would expect at least not to expose the `info_table` method for MapDatasets




Discussion:
A possibility here is to create a `SpectrumDataset` from the `MapDataset` and use its `info_dict`.  Possibly passing a `region`.
My suggestion here is to add a `region` argument to `Datasets.info_table` that would be unused for a `SpectrumDataset` but would be used to integrate for maps. 

Can you do it @AtreyeeS ?
The `Datasets.info_table` calls `dataset.info_dict`, so it is necessary to define a separate `MapDataset.info_dict`.

I think that for maps it might useful to see the total `npred` or the `background`, or see that the effective area is not zero anywhere... For ex, I am looking for large scale diffuse emission now, so do not have a specific region in mind for a quick look.

What I can do is keep an optional `region` argument. If `region` is passed to a `MapDataset`, it is converted to a `SpectrumDataset` and the corresponding `info_dict` is used. If not, general info like the total `counts`, `background` `npred` `excess` `effective_area` (min and max) is printed?

Does that sound ok @registerrier ?

Yes this sounds good.
The other typical use case is to build a cumulative excess/significance of a source in a `MapDataset` using an extraction region.


Resolving patch:
diff --git a/gammapy/datasets/core.py b/gammapy/datasets/core.py
--- a/gammapy/datasets/core.py
+++ b/gammapy/datasets/core.py
@@ -267,7 +267,7 @@ def stack_reduce(self, name=None):
             dataset.stack(ds)
         return dataset
 
-    def info_table(self, cumulative=False):
+    def info_table(self, cumulative=False, region=None):
         """Get info table for datasets.
 
         Parameters
@@ -283,7 +283,7 @@ def info_table(self, cumulative=False):
         if not self.is_all_same_type:
             raise ValueError("Info table not supported for mixed dataset type.")
 
-        stacked = self[0].copy(name="stacked")
+        stacked = self[0].copy(name=self[0].name)
 
         rows = [stacked.info_dict()]
 
diff --git a/gammapy/datasets/map.py b/gammapy/datasets/map.py
--- a/gammapy/datasets/map.py
+++ b/gammapy/datasets/map.py
@@ -6,7 +6,7 @@
 from astropy.nddata.utils import NoOverlapError
 from astropy.table import Table
 from astropy.utils import lazyproperty
-from regions import CircleSkyRegion
+from regions import CircleSkyRegion, RectangleSkyRegion
 from gammapy.data import GTI
 from gammapy.irf import EDispKernel, EffectiveAreaTable
 from gammapy.irf.edisp_map import EDispMap
@@ -794,6 +794,55 @@ def to_dict(self, filename=""):
             "filename": str(filename),
         }
 
+    def info_dict(self, region=None):
+        """Basic info dict with summary statistics
+
+        If a region is passed, then a spectrum dataset is
+        extracted, and the corresponding info returned.
+
+        Parameters
+        ----------
+        region : `~regions.SkyRegion`, optional
+            the input ON region on which to extract the spectrum
+
+        Returns
+        -------
+        info_dict : dict
+            Dictionary with summary info.
+        """
+        if self.gti is not None:
+            if region is None:
+                region = RectangleSkyRegion(
+                    center=self._geom.center_skydir,
+                    width=self._geom.width[0][0],
+                    height=self._geom.width[1][0],
+                )
+            info = self.to_spectrum_dataset(on_region=region).info_dict()
+        else:
+            info = dict()
+            if self.counts:
+                info["counts"] = np.sum(self.counts.data)
+            if self.background_model:
+                info["background"] = np.sum(self.background_model.evaluate().data)
+                info["excess"] = info["counts"] - info["background"]
+
+            info["npred"] = np.sum(self.npred())
+            if self.mask_safe is not None:
+                mask = self.mask_safe.reduce_over_axes(np.logical_or).data
+                if not mask.any():
+                    mask = None
+            else:
+                mask = None
+            if self.exposure:
+                exposure_min = np.min(self.exposure.data[..., mask])
+                exposure_max = np.max(self.exposure.data[..., mask])
+                info["aeff_min"] = exposure_min * self.exposure.unit
+                info["aeff_max"] = exposure_max * self.exposure.unit
+
+        info["name"] = self.name
+
+        return info
+
     def to_spectrum_dataset(self, on_region, containment_correction=False, name=None):
         """Return a ~gammapy.spectrum.SpectrumDataset from on_region.
 
@@ -1368,6 +1417,37 @@ def from_hdulist(cls, hdulist, name=None):
             kwargs["gti"] = gti
         return cls(**kwargs)
 
+    def info_dict(self, region=None):
+        """Basic info dict with summary statistics
+
+        If a region is passed, then a spectrum dataset is
+        extracted, and the corresponding info returned.
+
+        Parameters
+        ----------
+        region : `~regions.SkyRegion`, optional
+            the input ON region on which to extract the spectrum
+
+        Returns
+        -------
+        info_dict : dict
+            Dictionary with summary info.
+        """
+        info = super().info_dict(region)
+        info["name"] = self.name
+        if self.gti is None:
+            if self.counts_off is not None:
+                info["counts_off"] = np.sum(self.counts_off.data)
+
+            if self.acceptance is not None:
+                info["acceptance"] = np.sum(self.acceptance.data)
+
+            if self.acceptance_off is not None:
+                info["acceptance_off"] = np.sum(self.acceptance_off.data)
+
+            info["excess"] = np.sum(self.excess.data)
+        return info
+
     def to_spectrum_dataset(self, on_region, containment_correction=False, name=None):
         """Return a ~gammapy.spectrum.SpectrumDatasetOnOff from on_region.
 
diff --git a/gammapy/datasets/spectrum.py b/gammapy/datasets/spectrum.py
--- a/gammapy/datasets/spectrum.py
+++ b/gammapy/datasets/spectrum.py
@@ -459,7 +459,9 @@ def create(
         background = RegionNDMap.create(region=region, axes=[e_reco])
 
         aeff = EffectiveAreaTable(
-            e_true.edges[:-1], e_true.edges[1:], np.zeros(e_true.edges[:-1].shape) * u.m ** 2
+            e_true.edges[:-1],
+            e_true.edges[1:],
+            np.zeros(e_true.edges[:-1].shape) * u.m ** 2,
         )
         edisp = EDispKernel.from_diagonal_response(e_true.edges, e_reco.edges)
         mask_safe = RegionNDMap.from_geom(counts.geom, dtype="bool")
@@ -1159,7 +1161,7 @@ def from_ogip_files(cls, filename):
             gti=gti,
         )
 
-    def info_dict(self, in_safe_energy_range=True):
+    def info_dict(self, in_safe_energy_range=True, **kwargs):
         """Info dict with summary statistics, summed over energy
 
         Parameters


Test patch:
diff --git a/gammapy/datasets/tests/test_map.py b/gammapy/datasets/tests/test_map.py
--- a/gammapy/datasets/tests/test_map.py
+++ b/gammapy/datasets/tests/test_map.py
@@ -210,6 +210,32 @@ def test_to_spectrum_dataset(sky_model, geom, geom_etrue):
     )
 
 
+@requires_data()
+def test_info_dict(sky_model, geom, geom_etrue):
+    dataset = get_map_dataset(sky_model, geom, geom_etrue)
+    dataset.counts = dataset.npred()
+    info_dict = dataset.info_dict()
+
+    assert_allclose(info_dict["counts"], 9526, rtol=1e-3)
+    assert_allclose(info_dict["background"], 4000.0)
+    assert_allclose(info_dict["excess"], 5525.756, rtol=1e-3)
+    assert_allclose(info_dict["aeff_min"].value, 8.32e8, rtol=1e-3)
+    assert_allclose(info_dict["aeff_max"].value, 1.105e10, rtol=1e-3)
+    assert info_dict["aeff_max"].unit == "m2 s"
+    assert info_dict["name"] == "test"
+
+    gti = GTI.create([0 * u.s], [1 * u.h], reference_time="2010-01-01T00:00:00")
+    dataset.gti = gti
+    info_dict = dataset.info_dict()
+    assert_allclose(info_dict["n_on"], 9526, rtol=1e-3)
+    assert_allclose(info_dict["background"], 4000.0, rtol=1e-3)
+    assert_allclose(info_dict["significance"], 74.024180, rtol=1e-3)
+    assert_allclose(info_dict["excess"], 5525.756, rtol=1e-3)
+    assert_allclose(info_dict["livetime"].value, 3600)
+
+    assert info_dict["name"] == "test"
+
+
 @requires_data()
 def test_to_image(geom):
 
@@ -846,3 +872,28 @@ def test_stack_dataset_dataset_on_off():
     dataset.stack(dataset_on_off)
 
     assert_allclose(dataset.background_model.map.data, 0.2)
+
+
+@requires_data()
+def test_info_dict_on_off(images):
+    dataset = get_map_dataset_onoff(images)
+    info_dict = dataset.info_dict()
+    assert_allclose(info_dict["counts"], 4299, rtol=1e-3)
+    assert_allclose(info_dict["excess"], -22.52, rtol=1e-3)
+    assert_allclose(info_dict["aeff_min"].value, 0.0, rtol=1e-3)
+    assert_allclose(info_dict["aeff_max"].value, 3.4298378e09, rtol=1e-3)
+    assert_allclose(info_dict["npred"], 0.0, rtol=1e-3)
+    assert_allclose(info_dict["counts_off"], 20407510.0, rtol=1e-3)
+    assert_allclose(info_dict["acceptance"], 4272.7075, rtol=1e-3)
+    assert_allclose(info_dict["acceptance_off"], 20175596.0, rtol=1e-3)
+
+    gti = GTI.create([0 * u.s], [1 * u.h], reference_time="2010-01-01T00:00:00")
+    dataset.gti = gti
+    info_dict = dataset.info_dict()
+    assert_allclose(info_dict["n_on"], 4299)
+    assert_allclose(info_dict["n_off"], 20407510.0)
+    assert_allclose(info_dict["a_on"], 0.068363324)
+    assert_allclose(info_dict["a_off"], 322.83185)
+    assert_allclose(info_dict["alpha"], 0.0002117614)
+    assert_allclose(info_dict["excess"], -22.52295)
+    assert_allclose(info_dict["livetime"].value, 3600)