Repository: gammapy/gammapy

Issue:
make_theta_squared_table uses the same off position for each observation
**Gammapy version**

0.18.2

**Bug description**

If no `position_off` is given to `gammapy.makers.utils.make_theta_squared_table`, one is created by mirroring the source position at the pointing position. 

This is only done for the first observation due to how the check is implemented:

```
for obs in observation:
    if position_off is None:
        position_off = ...
``` 
after the first observation, position_off is not None, so all observations use the off position calculated from the first observation.
This yields wrong results when multiple observations with different pointing positions are used (as is usually the case with wobble observations).

**Expected behavior**

Create a new off position for each observation. 

**To Reproduce**

This is using a toy mc approach to trigger this bug:

```python
import numpy as np
import matplotlib.pyplot as plt

import astropy.units as u
from astropy.time import Time
from astropy.table import Table, vstack
from astropy.coordinates import SkyCoord, SkyOffsetFrame


from gammapy.makers.utils import make_theta_squared_table
from gammapy.maps import MapAxis
from gammapy.data import Observation, Observations, EventList, GTI
from gammapy.visualization import plot_theta_squared_table

source = SkyCoord.from_name('Crab')
offset_source = SkyOffsetFrame(origin=source)

observations = []

for offset in [0, 180]:
    
    pointing = source.directional_offset_by(offset * u.deg, 0.4 * u.deg)

    d_ra, d_dec = rng.normal(0, 0.1, (2, 1000)) * u.deg
    reco_radec =  SkyCoord(lat=d_dec, lon=d_ra, frame=offset_source).transform_to('icrs')

    signal = Table()
    signal['RA'] = reco_radec.ra
    signal['DEC'] = reco_radec.dec


    d_ra, d_dec = rng.normal(0, 0.5, (2, 100000)) * u.deg

    offset_pointing = SkyOffsetFrame(origin=pointing)
    reco_radec =  SkyCoord(lat=d_dec, lon=d_ra, frame=offset_pointing).transform_to('icrs')
    background = Table()
    background['RA'] = reco_radec.ra
    background['DEC'] = reco_radec.dec


    events = vstack([signal, background])
    events['TIME'] = Time.now() + np.linspace(0, 10, len(events)) * u.s
    
    obs = Observation(
        obs_id=1,
        events=EventList(events),
        gti=GTI.from_time_intervals([(events['TIME'].min(), events['TIME'].max())]),
        obs_info={'RA_PNT': pointing.ra, 'DEC_PNT': pointing.dec, 'DEADC': 1.0}
    )
    observations.append(obs)



theta_bins = MapAxis.from_edges(np.linspace(0, 0.5, 51) * u.deg**2, name='theta2')
table = make_theta_squared_table(observations, theta_bins, position=source) 

plt.figure()
plot_theta_squared_table(table)
plt.show()
```

Result on current master:

![theta_master](https://user-images.githubusercontent.com/5488440/119689871-81881000-be49-11eb-8f04-52b50223515e.png)


Result with the fix:

![theta_fixed](https://user-images.githubusercontent.com/5488440/119689914-8a78e180-be49-11eb-981f-368e5fa8ed2f.png)



Discussion:

Resolving patch:
diff --git a/gammapy/makers/utils.py b/gammapy/makers/utils.py
--- a/gammapy/makers/utils.py
+++ b/gammapy/makers/utils.py
@@ -396,12 +396,13 @@ def make_theta_squared_table(
     alpha_tot = np.zeros(len(table))
     livetime_tot = 0
 
+    create_off = position_off is None
     for observation in observations:
         separation = position.separation(observation.events.radec)
         counts, _ = np.histogram(separation ** 2, theta_squared_axis.edges)
         table["counts"] += counts
 
-        if not position_off:
+        if create_off:
             # Estimate the position of the mirror position
             pos_angle = observation.pointing_radec.position_angle(position)
             sep_angle = observation.pointing_radec.separation(position)


Test patch:
diff --git a/gammapy/makers/tests/test_utils.py b/gammapy/makers/tests/test_utils.py
--- a/gammapy/makers/tests/test_utils.py
+++ b/gammapy/makers/tests/test_utils.py
@@ -307,27 +307,28 @@ def test_make_edisp_kernel_map():
 
 class TestTheta2Table:
     def setup_class(self):
-        table = Table()
-        table["RA"] = [0.0, 0.0, 0.0, 0.0, 10.0] * u.deg
-        table["DEC"] = [0.0, 0.05, 0.9, 10.0, 10.0] * u.deg
-        table["ENERGY"] = [1.0, 1.0, 1.5, 1.5, 10.0] * u.TeV
-        table["OFFSET"] = [0.1, 0.1, 0.5, 1.0, 1.5] * u.deg
-
-        table.meta["RA_PNT"] = 0 * u.deg
-        table.meta["DEC_PNT"] = 0.5 * u.deg
-
-        meta_obs = dict()
-        meta_obs["RA_PNT"] = 0 * u.deg
-        meta_obs["DEC_PNT"] = 0.5 * u.deg
-        meta_obs["DEADC"] = 1
-
-        meta = time_ref_to_dict("2010-01-01")
-        gti_table = Table({"START": [1], "STOP": [3]}, meta=meta)
-        gti = GTI(gti_table)
-
-        self.observation = Observation(
-            events=EventList(table), obs_info=meta_obs, gti=gti
-        )
+        self.observations = []
+        for sign in [-1, 1]:
+            events = Table()
+            events["RA"] = [0.0, 0.0, 0.0, 0.0, 10.0] * u.deg
+            events["DEC"] = sign * ([0.0, 0.05, 0.9, 10.0, 10.0] * u.deg)
+            events["ENERGY"] = [1.0, 1.0, 1.5, 1.5, 10.0] * u.TeV
+            events["OFFSET"] = [0.1, 0.1, 0.5, 1.0, 1.5] * u.deg
+
+
+            obs_info = dict(
+                RA_PNT=0 * u.deg,
+                DEC_PNT=sign * 0.5 * u.deg,
+                DEADC=1,
+            )
+            events.meta.update(obs_info)
+            meta = time_ref_to_dict("2010-01-01")
+            gti_table = Table({"START": [1], "STOP": [3]}, meta=meta)
+            gti = GTI(gti_table)
+
+            self.observations.append(Observation(
+                events=EventList(events), obs_info=obs_info, gti=gti
+            ))
 
     def test_make_theta_squared_table(self):
         # pointing position: (0,0.5) degree in ra/dec
@@ -336,7 +337,7 @@ def test_make_theta_squared_table(self):
         position = SkyCoord(ra=0, dec=0, unit="deg", frame="icrs")
         axis = MapAxis.from_bounds(0, 0.2, nbin=4, interp="lin", unit="deg2")
         theta2_table = make_theta_squared_table(
-            observations=[self.observation], position=position, theta_squared_axis=axis
+            observations=[self.observations[0]], position=position, theta_squared_axis=axis
         )
         theta2_lo = [0, 0.05, 0.1, 0.15]
         theta2_hi = [0.05, 0.1, 0.15, 0.2]
@@ -360,7 +361,7 @@ def test_make_theta_squared_table(self):
         # Taking the off position as the on one
         off_position = position
         theta2_table2 = make_theta_squared_table(
-            observations=[self.observation],
+            observations=[self.observations[0]],
             position=position,
             theta_squared_axis=axis,
             position_off=off_position,
@@ -370,7 +371,7 @@ def test_make_theta_squared_table(self):
 
         # Test for two observations, here identical
         theta2_table_two_obs = make_theta_squared_table(
-            observations=[self.observation, self.observation],
+            observations=self.observations,
             position=position,
             theta_squared_axis=axis,
         )