Repository: gammapy/gammapy

Issue:
Cryptic error from MapMaker / make_counts_image
I accidentally typed this:
```python
import astropy.units as u
from gammapy.maps import WcsGeom
from gammapy.cube import MapMaker
from gammapy.data import DataStore
data_store = DataStore.from_dir('$GAMMAPY_EXTRA/datasets/cta-1dc/index/gps/')
obs_id = [110380, 111140, 111159]
obs_list = data_store.obs_list(obs_id)
geom = WcsGeom.create(
    skydir=(0, 0),
    npix=(800, 600),
    binsz=0.02,
    coordsys='GAL',
)
maker = MapMaker(geom, offset_max=u.Quantity('2 deg'))
images = maker.run(obs_list)
```
and it blows up with a cryptic error message:
```
$ python temp.py 
|===========================================>--------------------------------------------------------------------------------------|   1 /  3  (33.33%) ETA     0sTraceback (most recent call last):
  File "temp.py", line 15, in <module>
    images = maker.run(obs_list)
  File "/Users/deil/work/code/gammapy/gammapy/cube/new.py", line 324, in run
    self.process_obs(obs)
  File "/Users/deil/work/code/gammapy/gammapy/cube/new.py", line 280, in process_obs
    obs.events, cutout_geom, obs.pointing_radec, self.offset_max,
  File "/Users/deil/work/code/gammapy/gammapy/cube/new.py", line 79, in make_map_counts
    counts_map.data[:, offset_mask] = 0
IndexError: too many indices for array
```

The problem is in `make_map_counts` here:
https://github.com/gammapy/gammapy/blob/a013ff8ac532ab8b15cee95c5da2abb8937bde9c/gammapy/cube/new.py#L79

It doesn't work for 2D images.

There's other obvious issues one encounters when making maps, e.g. replacing `offset_max=u.Quantity('2 deg')` with `offset_max='2 deg'` above gives another cryptic error, because the mapmaker just does `self.offset_max = offset_max` but should do `self.offset_max = Angle(offset_max)` to be kind to users.

The solution is to rewrite the functions in `new.py` to take a mask instead of a max offset, and to improve their test coverage, e.g. also trying to run them on a 2D geom (and either succeed, or error out with a good error message).

I consider this high priority, we should do that tomorrow.

@registerrier - you or me?


Discussion:
Indeed, the code in new.py  expects explicit 3D maps. Having an explicit energy range is necessary for projected irf maps any way. It is probably safe to enforce explicit energy range for the counts map as well. We can add an explicit test for this.
OK for the kind user interface for max_offset. 
I agree that a fov_mask is better than a simple offset cut. I think that it is better to use it to determine the `MapCoord` at which one wants to compute the IRF value rather than computing the value for all positions and then forcing it to 0 outside the valid range defined by the mask.
I'll make a PR for this today.

Resolving patch:
diff --git a/gammapy/cube/make.py b/gammapy/cube/make.py
--- a/gammapy/cube/make.py
+++ b/gammapy/cube/make.py
@@ -32,6 +32,9 @@ class MapMaker(object):
     """
 
     def __init__(self, geom, offset_max, cutout_mode="trim"):
+        if geom.is_image:
+            raise ValueError('MapMaker only works with geom with an energy axis')
+
         self.geom = geom
         self.offset_max = Angle(offset_max)
 


Test patch:
diff --git a/gammapy/cube/tests/test_make.py b/gammapy/cube/tests/test_make.py
--- a/gammapy/cube/tests/test_make.py
+++ b/gammapy/cube/tests/test_make.py
@@ -19,31 +19,46 @@ def obs_list():
     return data_store.obs_list(obs_id)
 
 
-@pytest.fixture(scope='session')
-def geom():
-    skydir = SkyCoord(266.41681663, -29.00782497, unit="deg")
-    energy_axis = MapAxis.from_edges([0.1, 0.5, 1.5, 3.0, 10.],
-                                     name='energy', unit='TeV', interp='log')
-    return WcsGeom.create(binsz=0.1 * u.deg, skydir=skydir, width=15.0, axes=[energy_axis])
+def geom(ebounds):
+    skydir = SkyCoord(0, -1, unit="deg", frame='galactic')
+    energy_axis = MapAxis.from_edges(ebounds, name='energy', unit='TeV', interp='log')
+    return WcsGeom.create(binsz=0.5 * u.deg, skydir=skydir, width=(10, 5),
+                          coordsys='GAL', axes=[energy_axis])
 
 
 @requires_data('gammapy-extra')
 @pytest.mark.parametrize("pars", [
     {
+        # Default, normal test case
+        'geom': geom(ebounds=[0.1, 1, 10]),
+        'mode': 'trim',
+        'counts': 34366,
+        'exposure': 3.99815e+11,
+        'background': 34366,
+    },
+    {
+        # Test single energy bin
+        'geom': geom(ebounds=[0.1, 10]),
         'mode': 'trim',
-        'counts': 107214,
-        'exposure': 9.582158e+13,
-        'background': 107214.016,
+        'counts': 34366,
+        'exposure': 1.16866e+11,
+        'background': 34366,
     },
     {
+        # Test strict mode
+        'geom': geom(ebounds=[0.1, 1, 10]),
         'mode': 'strict',
-        'counts': 53486,
-        'exposure': 4.794064e+13,
-        'background': 53486,
+        'counts': 21981,
+        'exposure': 2.592941e+11,
+        'background': 21981,
     },
 ])
-def test_map_maker(pars, obs_list, geom):
-    maker = MapMaker(geom, '6 deg', cutout_mode=pars['mode'])
+def test_map_maker(pars, obs_list):
+    maker = MapMaker(
+        geom=pars['geom'],
+        offset_max='2 deg',
+        cutout_mode=pars['mode'],
+    )
     maps = maker.run(obs_list)
 
     counts = maps['counts_map']