Repository: gammapy/gammapy

Issue:
Bad conversion of migration matrix in E_est bins with `to_edisp_kernel`
**Gammapy version**
`0.18.3.dev2343+g7c504d473`

**Bug description**
When the E_est vs E_true histogram is built strange trails appear in the migration matrix.
It can already seen by eye in two different versions of the docs
https://docs.gammapy.org/0.18.2/irf/edisp.html#as-a-function-of-true-energy-gadf-ogip-rmf
https://docs.gammapy.org/dev/irf/edisp.html#as-a-function-of-true-energy-gadf-ogip-rmf
the dense narrow part of the distribution below 100 GeV in 0.18.2 disappears and these ugly trails appear in the dev version.

**Expected behavior**
These trails should not be there, since the E_est vs E_true migration is used during the spectrum fit it completely messes up the spectrum extraction: events are predicted where they should not be.

**To Reproduce**
The snippet shows the effect is not dependent on the binning chosen
```python
from gammapy.irf import EnergyDispersion2D
from gammapy.maps import MapAxis
from astropy.coordinates import Angle
import matplotlib.pyplot as plt

edisp = EnergyDispersion2D.read(
    "$GAMMAPY_DATA/hess-dl3-dr1/data/hess_dl3_dr1_obs_id_020136.fits.gz", hdu="EDISP"
)

# get the edisp kernel, use the default axes within the IRF tables
offset = Angle("0.5 deg")
edisp_kernel = edisp.to_edisp_kernel(offset=offset)
edisp_kernel.peek()
plt.show()

# let us make a very broad binning to see if the effect disappear
n_broad_bins = 5
energy = MapAxis.from_energy_bounds(
    0.1, 100, nbin=n_broad_bins, per_decade=True, unit="TeV"
)
energy_true = MapAxis.from_energy_bounds(
    0.1, 100, nbin=n_broad_bins, per_decade=True, unit="TeV", name="energy_true"
)
edisp_kernel_broad = edisp.to_edisp_kernel(
    offset=offset, energy=energy.edges, energy_true=energy_true.edges
)
edisp_kernel_broad.peek()
plt.show()

# let us make a very fine binning to see if the effect disappear
n_fine_bins = 25
energy = MapAxis.from_energy_bounds(
    0.1, 100, nbin=n_fine_bins, per_decade=True, unit="TeV"
)
energy_true = MapAxis.from_energy_bounds(
    0.1, 100, nbin=n_fine_bins, per_decade=True, unit="TeV", name="energy_true"
)
edisp_kernel_fine = edisp.to_edisp_kernel(
    offset=offset, energy=energy.edges, energy_true=energy_true.edges
)
edisp_kernel_fine.peek()
plt.show()
```
![edisp_default](https://user-images.githubusercontent.com/26409711/141136818-b0efbed8-8009-4b8f-b687-ef4bcc116aca.png)

![edisp_broad](https://user-images.githubusercontent.com/26409711/141136808-b5e000cf-616d-4bf8-a426-d102e68b7dc6.png)

![edisp_fine](https://user-images.githubusercontent.com/26409711/141136821-08c31079-f4ac-4bac-830f-5edc6fde561d.png)



Discussion:

Resolving patch:
diff --git a/gammapy/irf/edisp/core.py b/gammapy/irf/edisp/core.py
--- a/gammapy/irf/edisp/core.py
+++ b/gammapy/irf/edisp/core.py
@@ -1,11 +1,10 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 import numpy as np
 import scipy.special
-from astropy.coordinates import Angle
-from astropy.units import Quantity
+from astropy.coordinates import Angle, SkyCoord
+from astropy import units as u
 from astropy.visualization import quantity_support
-from gammapy.maps import MapAxis, MapAxes
-from .kernel import EDispKernel
+from gammapy.maps import MapAxis, MapAxes, RegionGeom
 from ..core import IRF
 
 __all__ = ["EnergyDispersion2D"]
@@ -123,6 +122,7 @@ def to_edisp_kernel(self, offset, energy_true=None, energy=None):
         edisp : `~gammapy.irf.EDispKernel`
             Energy dispersion matrix
         """
+        from gammapy.makers.utils import make_edisp_kernel_map
         offset = Angle(offset)
 
         # TODO: expect directly MapAxis here?
@@ -138,22 +138,15 @@ def to_edisp_kernel(self, offset, energy_true=None, energy=None):
                 energy_true, name="energy_true",
             )
 
-        axes = MapAxes([energy_axis_true, energy_axis])
-        coords = axes.get_coord(mode="edges", axis_name="energy")
+        pointing = SkyCoord("0d", "0d")
 
-        # migration value of energy bounds
-        migra = coords["energy"] / coords["energy_true"]
-
-        values = self.integral(
-            axis_name="migra",
-            offset=offset,
-            energy_true=coords["energy_true"],
-            migra=migra,
+        center = pointing.directional_offset_by(position_angle=0 * u.deg, separation=offset)
+        geom = RegionGeom.create(
+            region=center, axes=[energy_axis, energy_axis_true]
         )
 
-        data = np.diff(values)
-
-        return EDispKernel(axes=axes, data=data.to_value(""),)
+        edisp = make_edisp_kernel_map(geom=geom, edisp=self, pointing=pointing)
+        return edisp.get_edisp_kernel()
 
     def normalize(self):
         """Normalise energy dispersion"""
@@ -190,9 +183,9 @@ def plot_migration(
             offset = np.atleast_1d(Angle(offset))
 
         if energy_true is None:
-            energy_true = Quantity([0.1, 1, 10], "TeV")
+            energy_true = u.Quantity([0.1, 1, 10], "TeV")
         else:
-            energy_true = np.atleast_1d(Quantity(energy_true))
+            energy_true = np.atleast_1d(u.Quantity(energy_true))
 
         migra = self.axes["migra"]
 
diff --git a/gammapy/irf/edisp/map.py b/gammapy/irf/edisp/map.py
--- a/gammapy/irf/edisp/map.py
+++ b/gammapy/irf/edisp/map.py
@@ -107,36 +107,9 @@ def get_edisp_kernel(self, position=None, energy_axis=None):
         edisp : `~gammapy.irf.EnergyDispersion`
             the energy dispersion (i.e. rmf object)
         """
-        if position is None:
-            position = self.edisp_map.geom.center_skydir
-
-        if position.size != 1:
-            raise ValueError(
-                "EnergyDispersion can be extracted at one single position only."
-            )
-
-        position = self._get_nearest_valid_position(position)
-        energy_axis_true = self.edisp_map.geom.axes["energy_true"]
-
-        axes = MapAxes([energy_axis_true, energy_axis])
-
-        coords = axes.get_coord(mode="edges", axis_name="energy")
-
-        # migration value of energy bounds
-        migra = coords["energy"] / coords["energy_true"]
-
-        coords = {
-            "skycoord": position,
-            "energy_true": coords["energy_true"],
-            "migra": migra,
-        }
-
-        values = self.edisp_map.integral(axis_name="migra", coords=coords)
-        data = np.diff(np.clip(values, 0, np.inf))
-
-        return EDispKernel(
-            axes=axes, data=data.to_value("")
-        )
+        edisp_map = self.to_region_nd_map(region=position)
+        edisp_kernel_map = edisp_map.to_edisp_kernel_map(energy_axis=energy_axis)
+        return edisp_kernel_map.get_edisp_kernel()
 
     def to_edisp_kernel_map(self, energy_axis):
         """Convert to map with edisp kernels


Test patch: