We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Label issue in background rate figures
**Gammapy version**
0.18.2

**Bug description**
The label of the background rate figure seems wrong.
See the documentation, cell 24, bottom left figure:
https://docs.gammapy.org/0.18.2/tutorials/cta.html?highlight=cta#IRFs

The labels are all identical `offset = 0.2 deg`.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gammapy/irf/background.py`
Content:
```
1 # Licensed under a 3-clause BSD style license - see LICENSE.rst
2 import logging
3 import numpy as np
4 import astropy.units as u
5 from gammapy.maps import MapAxes, MapAxis
6 from gammapy.utils.integrate import trapz_loglog
7 from .core import IRF
8 
9 __all__ = ["Background3D", "Background2D"]
10 
11 log = logging.getLogger(__name__)
12 
13 
14 class BackgroundIRF(IRF):
15     """Background IRF base class"""
16     default_interp_kwargs = dict(
17         bounds_error=False, fill_value=None, values_scale="log"
18     )
19     """Default Interpolation kwargs to extrapolate."""
20 
21     @classmethod
22     def from_table(cls, table, format="gadf-dl3"):
23         """Read from `~astropy.table.Table`.
24 
25         Parameters
26         ----------
27         table : `~astropy.table.Table`
28             Table with background data
29         format : {"gadf-dl3"}
30             Format specification
31 
32         Returns
33         -------
34         bkg : `Background2D` or `Background2D`
35             Background IRF class.
36         """
37         axes = MapAxes.from_table(table, format=format)[cls.required_axes]
38         
39         # Spec says key should be "BKG", but there are files around
40         # (e.g. CTA 1DC) that use "BGD". For now we support both
41         if "BKG" in table.colnames:
42             bkg_name = "BKG"
43         elif "BGD" in table.colnames:
44             bkg_name = "BGD"
45         else:
46             raise ValueError('Invalid column names. Need "BKG" or "BGD".')
47 
48         data = table[bkg_name].quantity[0].T
49 
50         if data.unit == "" or isinstance(data.unit, u.UnrecognizedUnit):
51             data = u.Quantity(data.value, "s-1 MeV-1 sr-1", copy=False)
52             log.warning(
53                 "Invalid unit found in background table! Assuming (s-1 MeV-1 sr-1)"
54             )
55 
56         # TODO: The present HESS and CTA backgroundfits files
57         #  have a reverse order (lon, lat, E) than recommened in GADF(E, lat, lon)
58         #  For now, we suport both.
59 
60         if axes.shape == axes.shape[::-1]:
61             log.error("Ambiguous axes order in Background fits files!")
62 
63         if np.shape(data) != axes.shape:
64             log.debug("Transposing background table on read")
65             data = data.transpose()
66 
67         return cls(
68             axes=axes,
69             data=data.value,
70             meta=table.meta,
71             unit=data.unit
72         )
73 
74 
75 class Background3D(BackgroundIRF):
76     """Background 3D.
77 
78     Data format specification: :ref:`gadf:bkg_3d`
79 
80     Parameters
81     ----------
82     axes : list of `MapAxis` or `MapAxes` object
83         Required data axes: ["energy", "fov_lon", "fov_lat"] in the given order.
84     data : `~np.ndarray`
85         Data array.
86     unit : str or `~astropy.units.Unit`
87         Data unit usuually ``s^-1 MeV^-1 sr^-1``
88     meta : dict
89         Meta data
90 
91     Examples
92     --------
93     Here's an example you can use to learn about this class:
94 
95     >>> from gammapy.irf import Background3D
96     >>> filename = '$GAMMAPY_DATA/cta-1dc/caldb/data/cta/1dc/bcf/South_z20_50h/irf_file.fits'
97     >>> bkg_3d = Background3D.read(filename, hdu='BACKGROUND')
98     >>> print(bkg_3d)
99     Background3D
100     ------------
101     <BLANKLINE>
102       axes  : ['energy', 'fov_lon', 'fov_lat']
103       shape : (21, 36, 36)
104       ndim  : 3
105       unit  : 1 / (MeV s sr)
106       dtype : >f4
107     <BLANKLINE>
108 
109     """
110     tag = "bkg_3d"
111     required_axes = ["energy", "fov_lon", "fov_lat"]
112 
113     def to_2d(self):
114         """Convert to `Background2D`.
115 
116         This takes the values at Y = 0 and X >= 0.
117         """
118         # TODO: this is incorrect as it misses the Jacobian?
119         idx_lon = self.axes["fov_lon"].coord_to_idx(0 * u.deg)[0]
120         idx_lat = self.axes["fov_lat"].coord_to_idx(0 * u.deg)[0]
121         data = self.quantity[:, idx_lon:, idx_lat].copy()
122 
123         offset = self.axes["fov_lon"].edges[idx_lon:]
124         offset_axis = MapAxis.from_edges(offset, name="offset")
125 
126         return Background2D(
127             axes=[self.axes["energy"], offset_axis], data=data.value, unit=data.unit
128         )
129 
130     def peek(self, figsize=(10, 8)):
131         return self.to_2d().peek(figsize)
132 
133 
134 class Background2D(BackgroundIRF):
135     """Background 2D.
136 
137     Data format specification: :ref:`gadf:bkg_2d`
138 
139     Parameters
140     ----------
141     axes : list of `MapAxis` or `MapAxes` object
142         Required data axes: ["energy", "offset"] in the given order.
143     data : `~np.ndarray`
144         Data array.
145     unit : str or `~astropy.units.Unit`
146         Data unit usually ``s^-1 MeV^-1 sr^-1``
147     meta : dict
148         Meta data
149     """
150 
151     tag = "bkg_2d"
152     required_axes = ["energy", "offset"]
153     default_interp_kwargs = dict(bounds_error=False, fill_value=None)
154     """Default Interpolation kwargs."""
155 
156     def plot(self, ax=None, add_cbar=True, **kwargs):
157         """Plot energy offset dependence of the background model.
158         """
159         import matplotlib.pyplot as plt
160         from matplotlib.colors import LogNorm
161 
162         ax = plt.gca() if ax is None else ax
163 
164         x = self.axes["energy"].edges.to_value("TeV")
165         y = self.axes["offset"].edges.to_value("deg")
166         z = self.quantity.T.value
167 
168         kwargs.setdefault("cmap", "GnBu")
169         kwargs.setdefault("edgecolors", "face")
170 
171         caxes = ax.pcolormesh(x, y, z, norm=LogNorm(), **kwargs)
172         ax.set_xscale("log")
173         ax.set_ylabel(f"Offset (deg)")
174         ax.set_xlabel(f"Energy (TeV)")
175 
176         xmin, xmax = x.min(), x.max()
177         ax.set_xlim(xmin, xmax)
178 
179         if add_cbar:
180             label = f"Background rate ({self.unit})"
181             ax.figure.colorbar(caxes, ax=ax, label=label)
182 
183     def plot_offset_dependence(self, ax=None, energy=None, **kwargs):
184         """Plot background rate versus offset for a given energy.
185 
186         Parameters
187         ----------
188         ax : `~matplotlib.axes.Axes`, optional
189             Axis
190         energy : `~astropy.units.Quantity`
191             Energy
192 
193         Returns
194         -------
195         ax : `~matplotlib.axes.Axes`
196             Axis
197         """
198         import matplotlib.pyplot as plt
199 
200         ax = plt.gca() if ax is None else ax
201 
202         if energy is None:
203             energy_axis = self.axes["energy"]
204             e_min, e_max = np.log10(energy_axis.center.value[[0, -1]])
205             energy = np.logspace(e_min, e_max, 4) * energy_axis.unit
206 
207         offset = self.axes["offset"].center
208 
209         for ee in energy:
210             bkg = self.evaluate(offset=offset, energy=ee)
211             if np.isnan(bkg).all():
212                 continue
213             label = f"energy = {ee:.1f}"
214             ax.plot(offset, bkg.value, label=label, **kwargs)
215 
216         ax.set_xlabel(f"Offset ({self.axes['offset'].unit})")
217         ax.set_ylabel(f"Background rate ({self.unit})")
218         ax.set_yscale("log")
219         ax.legend(loc="upper right")
220         return ax
221 
222     def plot_energy_dependence(self, ax=None, offset=None, **kwargs):
223         """Plot background rate versus energy for a given offset.
224 
225         Parameters
226         ----------
227         ax : `~matplotlib.axes.Axes`, optional
228             Axis
229         offset : `~astropy.coordinates.Angle`
230             Offset
231         kwargs : dict
232             Forwarded tp plt.plot()
233 
234         Returns
235         -------
236         ax : `~matplotlib.axes.Axes`
237             Axis
238         """
239         import matplotlib.pyplot as plt
240 
241         ax = plt.gca() if ax is None else ax
242 
243         if offset is None:
244             offset_axis = self.axes["offset"]
245             off_min, off_max = offset_axis.center.value[[0, -1]]
246             offset = np.linspace(off_min, off_max, 4) * offset_axis.unit
247 
248         energy = self.axes["energy"].center
249 
250         for off in offset:
251             bkg = self.evaluate(offset=off, energy=energy)
252             kwargs.setdefault("label", f"offset = {off:.1f}")
253             ax.plot(energy, bkg.value, **kwargs)
254 
255         ax.set_xscale("log")
256         ax.set_yscale("log")
257         ax.set_xlabel(f"Energy [{energy.unit}]")
258         ax.set_ylabel(f"Background rate ({self.unit})")
259         ax.set_xlim(min(energy.value), max(energy.value))
260         ax.legend(loc="best")
261 
262         return ax
263 
264     def plot_spectrum(self, ax=None, **kwargs):
265         """Plot angle integrated background rate versus energy.
266 
267         Parameters
268         ----------
269         ax : `~matplotlib.axes.Axes`, optional
270             Axis
271         kwargs : dict
272             Forwarded tp plt.plot()
273 
274         Returns
275         -------
276         ax : `~matplotlib.axes.Axes`
277             Axis
278         """
279         import matplotlib.pyplot as plt
280 
281         ax = plt.gca() if ax is None else ax
282         offset = self.axes["offset"].edges
283         energy = self.axes["energy"].center
284 
285         bkg = []
286         for ee in energy:
287             data = self.evaluate(offset=offset, energy=ee)
288             val = np.nansum(trapz_loglog(data, offset, axis=0))
289             bkg.append(val.value)
290 
291         ax.plot(energy, bkg, label="integrated spectrum", **kwargs)
292 
293         unit = self.unit * offset.unit ** 2
294 
295         ax.set_xscale("log")
296         ax.set_yscale("log")
297         ax.set_xlabel(f"Energy [{energy.unit}]")
298         ax.set_ylabel(f"Background rate ({unit})")
299         ax.set_xlim(min(energy.value), max(energy.value))
300         ax.legend(loc="best")
301         return ax
302 
303     def peek(self, figsize=(10, 8)):
304         """Quick-look summary plots."""
305         import matplotlib.pyplot as plt
306 
307         fig, axes = plt.subplots(nrows=2, ncols=2, figsize=figsize)
308         self.plot(ax=axes[1][1])
309         self.plot_offset_dependence(ax=axes[0][0])
310         self.plot_energy_dependence(ax=axes[1][0])
311         self.plot_spectrum(ax=axes[0][1])
312         plt.tight_layout()
313
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gammapy/irf/background.py b/gammapy/irf/background.py
--- a/gammapy/irf/background.py
+++ b/gammapy/irf/background.py
@@ -13,6 +13,7 @@
 
 class BackgroundIRF(IRF):
     """Background IRF base class"""
+
     default_interp_kwargs = dict(
         bounds_error=False, fill_value=None, values_scale="log"
     )
@@ -35,7 +36,7 @@
             Background IRF class.
         """
         axes = MapAxes.from_table(table, format=format)[cls.required_axes]
-        
+
         # Spec says key should be "BKG", but there are files around
         # (e.g. CTA 1DC) that use "BGD". For now we support both
         if "BKG" in table.colnames:
@@ -64,12 +65,7 @@
             log.debug("Transposing background table on read")
             data = data.transpose()
 
-        return cls(
-            axes=axes,
-            data=data.value,
-            meta=table.meta,
-            unit=data.unit
-        )
+        return cls(axes=axes, data=data.value, meta=table.meta, unit=data.unit)
 
 
 class Background3D(BackgroundIRF):
@@ -107,6 +103,7 @@
     <BLANKLINE>
 
     """
+
     tag = "bkg_3d"
     required_axes = ["energy", "fov_lon", "fov_lat"]
 
@@ -249,8 +246,8 @@
 
         for off in offset:
             bkg = self.evaluate(offset=off, energy=energy)
-            kwargs.setdefault("label", f"offset = {off:.1f}")
-            ax.plot(energy, bkg.value, **kwargs)
+            label = f"offset = {off:.2f}"
+            ax.plot(energy, bkg.value, label=label, **kwargs)
 
         ax.set_xscale("log")
         ax.set_yscale("log")

{"golden_diff": "diff --git a/gammapy/irf/background.py b/gammapy/irf/background.py\n--- a/gammapy/irf/background.py\n+++ b/gammapy/irf/background.py\n@@ -13,6 +13,7 @@\n \n class BackgroundIRF(IRF):\n     \"\"\"Background IRF base class\"\"\"\n+\n     default_interp_kwargs = dict(\n         bounds_error=False, fill_value=None, values_scale=\"log\"\n     )\n@@ -35,7 +36,7 @@\n             Background IRF class.\n         \"\"\"\n         axes = MapAxes.from_table(table, format=format)[cls.required_axes]\n-        \n+\n         # Spec says key should be \"BKG\", but there are files around\n         # (e.g. CTA 1DC) that use \"BGD\". For now we support both\n         if \"BKG\" in table.colnames:\n@@ -64,12 +65,7 @@\n             log.debug(\"Transposing background table on read\")\n             data = data.transpose()\n \n-        return cls(\n-            axes=axes,\n-            data=data.value,\n-            meta=table.meta,\n-            unit=data.unit\n-        )\n+        return cls(axes=axes, data=data.value, meta=table.meta, unit=data.unit)\n \n \n class Background3D(BackgroundIRF):\n@@ -107,6 +103,7 @@\n     <BLANKLINE>\n \n     \"\"\"\n+\n     tag = \"bkg_3d\"\n     required_axes = [\"energy\", \"fov_lon\", \"fov_lat\"]\n \n@@ -249,8 +246,8 @@\n \n         for off in offset:\n             bkg = self.evaluate(offset=off, energy=energy)\n-            kwargs.setdefault(\"label\", f\"offset = {off:.1f}\")\n-            ax.plot(energy, bkg.value, **kwargs)\n+            label = f\"offset = {off:.2f}\"\n+            ax.plot(energy, bkg.value, label=label, **kwargs)\n \n         ax.set_xscale(\"log\")\n         ax.set_yscale(\"log\")\n", "issue": "Label issue in background rate figures\n**Gammapy version**\r\n0.18.2\r\n\r\n**Bug description**\r\nThe label of the background rate figure seems wrong.\r\nSee the documentation, cell 24, bottom left figure:\r\nhttps://docs.gammapy.org/0.18.2/tutorials/cta.html?highlight=cta#IRFs\r\n\r\nThe labels are all identical `offset = 0.2 deg`.\r\n\r\n\n", "before_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport logging\nimport numpy as np\nimport astropy.units as u\nfrom gammapy.maps import MapAxes, MapAxis\nfrom gammapy.utils.integrate import trapz_loglog\nfrom .core import IRF\n\n__all__ = [\"Background3D\", \"Background2D\"]\n\nlog = logging.getLogger(__name__)\n\n\nclass BackgroundIRF(IRF):\n    \"\"\"Background IRF base class\"\"\"\n    default_interp_kwargs = dict(\n        bounds_error=False, fill_value=None, values_scale=\"log\"\n    )\n    \"\"\"Default Interpolation kwargs to extrapolate.\"\"\"\n\n    @classmethod\n    def from_table(cls, table, format=\"gadf-dl3\"):\n        \"\"\"Read from `~astropy.table.Table`.\n\n        Parameters\n        ----------\n        table : `~astropy.table.Table`\n            Table with background data\n        format : {\"gadf-dl3\"}\n            Format specification\n\n        Returns\n        -------\n        bkg : `Background2D` or `Background2D`\n            Background IRF class.\n        \"\"\"\n        axes = MapAxes.from_table(table, format=format)[cls.required_axes]\n        \n        # Spec says key should be \"BKG\", but there are files around\n        # (e.g. CTA 1DC) that use \"BGD\". For now we support both\n        if \"BKG\" in table.colnames:\n            bkg_name = \"BKG\"\n        elif \"BGD\" in table.colnames:\n            bkg_name = \"BGD\"\n        else:\n            raise ValueError('Invalid column names. Need \"BKG\" or \"BGD\".')\n\n        data = table[bkg_name].quantity[0].T\n\n        if data.unit == \"\" or isinstance(data.unit, u.UnrecognizedUnit):\n            data = u.Quantity(data.value, \"s-1 MeV-1 sr-1\", copy=False)\n            log.warning(\n                \"Invalid unit found in background table! Assuming (s-1 MeV-1 sr-1)\"\n            )\n\n        # TODO: The present HESS and CTA backgroundfits files\n        #  have a reverse order (lon, lat, E) than recommened in GADF(E, lat, lon)\n        #  For now, we suport both.\n\n        if axes.shape == axes.shape[::-1]:\n            log.error(\"Ambiguous axes order in Background fits files!\")\n\n        if np.shape(data) != axes.shape:\n            log.debug(\"Transposing background table on read\")\n            data = data.transpose()\n\n        return cls(\n            axes=axes,\n            data=data.value,\n            meta=table.meta,\n            unit=data.unit\n        )\n\n\nclass Background3D(BackgroundIRF):\n    \"\"\"Background 3D.\n\n    Data format specification: :ref:`gadf:bkg_3d`\n\n    Parameters\n    ----------\n    axes : list of `MapAxis` or `MapAxes` object\n        Required data axes: [\"energy\", \"fov_lon\", \"fov_lat\"] in the given order.\n    data : `~np.ndarray`\n        Data array.\n    unit : str or `~astropy.units.Unit`\n        Data unit usuually ``s^-1 MeV^-1 sr^-1``\n    meta : dict\n        Meta data\n\n    Examples\n    --------\n    Here's an example you can use to learn about this class:\n\n    >>> from gammapy.irf import Background3D\n    >>> filename = '$GAMMAPY_DATA/cta-1dc/caldb/data/cta/1dc/bcf/South_z20_50h/irf_file.fits'\n    >>> bkg_3d = Background3D.read(filename, hdu='BACKGROUND')\n    >>> print(bkg_3d)\n    Background3D\n    ------------\n    <BLANKLINE>\n      axes  : ['energy', 'fov_lon', 'fov_lat']\n      shape : (21, 36, 36)\n      ndim  : 3\n      unit  : 1 / (MeV s sr)\n      dtype : >f4\n    <BLANKLINE>\n\n    \"\"\"\n    tag = \"bkg_3d\"\n    required_axes = [\"energy\", \"fov_lon\", \"fov_lat\"]\n\n    def to_2d(self):\n        \"\"\"Convert to `Background2D`.\n\n        This takes the values at Y = 0 and X >= 0.\n        \"\"\"\n        # TODO: this is incorrect as it misses the Jacobian?\n        idx_lon = self.axes[\"fov_lon\"].coord_to_idx(0 * u.deg)[0]\n        idx_lat = self.axes[\"fov_lat\"].coord_to_idx(0 * u.deg)[0]\n        data = self.quantity[:, idx_lon:, idx_lat].copy()\n\n        offset = self.axes[\"fov_lon\"].edges[idx_lon:]\n        offset_axis = MapAxis.from_edges(offset, name=\"offset\")\n\n        return Background2D(\n            axes=[self.axes[\"energy\"], offset_axis], data=data.value, unit=data.unit\n        )\n\n    def peek(self, figsize=(10, 8)):\n        return self.to_2d().peek(figsize)\n\n\nclass Background2D(BackgroundIRF):\n    \"\"\"Background 2D.\n\n    Data format specification: :ref:`gadf:bkg_2d`\n\n    Parameters\n    ----------\n    axes : list of `MapAxis` or `MapAxes` object\n        Required data axes: [\"energy\", \"offset\"] in the given order.\n    data : `~np.ndarray`\n        Data array.\n    unit : str or `~astropy.units.Unit`\n        Data unit usually ``s^-1 MeV^-1 sr^-1``\n    meta : dict\n        Meta data\n    \"\"\"\n\n    tag = \"bkg_2d\"\n    required_axes = [\"energy\", \"offset\"]\n    default_interp_kwargs = dict(bounds_error=False, fill_value=None)\n    \"\"\"Default Interpolation kwargs.\"\"\"\n\n    def plot(self, ax=None, add_cbar=True, **kwargs):\n        \"\"\"Plot energy offset dependence of the background model.\n        \"\"\"\n        import matplotlib.pyplot as plt\n        from matplotlib.colors import LogNorm\n\n        ax = plt.gca() if ax is None else ax\n\n        x = self.axes[\"energy\"].edges.to_value(\"TeV\")\n        y = self.axes[\"offset\"].edges.to_value(\"deg\")\n        z = self.quantity.T.value\n\n        kwargs.setdefault(\"cmap\", \"GnBu\")\n        kwargs.setdefault(\"edgecolors\", \"face\")\n\n        caxes = ax.pcolormesh(x, y, z, norm=LogNorm(), **kwargs)\n        ax.set_xscale(\"log\")\n        ax.set_ylabel(f\"Offset (deg)\")\n        ax.set_xlabel(f\"Energy (TeV)\")\n\n        xmin, xmax = x.min(), x.max()\n        ax.set_xlim(xmin, xmax)\n\n        if add_cbar:\n            label = f\"Background rate ({self.unit})\"\n            ax.figure.colorbar(caxes, ax=ax, label=label)\n\n    def plot_offset_dependence(self, ax=None, energy=None, **kwargs):\n        \"\"\"Plot background rate versus offset for a given energy.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        energy : `~astropy.units.Quantity`\n            Energy\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n\n        if energy is None:\n            energy_axis = self.axes[\"energy\"]\n            e_min, e_max = np.log10(energy_axis.center.value[[0, -1]])\n            energy = np.logspace(e_min, e_max, 4) * energy_axis.unit\n\n        offset = self.axes[\"offset\"].center\n\n        for ee in energy:\n            bkg = self.evaluate(offset=offset, energy=ee)\n            if np.isnan(bkg).all():\n                continue\n            label = f\"energy = {ee:.1f}\"\n            ax.plot(offset, bkg.value, label=label, **kwargs)\n\n        ax.set_xlabel(f\"Offset ({self.axes['offset'].unit})\")\n        ax.set_ylabel(f\"Background rate ({self.unit})\")\n        ax.set_yscale(\"log\")\n        ax.legend(loc=\"upper right\")\n        return ax\n\n    def plot_energy_dependence(self, ax=None, offset=None, **kwargs):\n        \"\"\"Plot background rate versus energy for a given offset.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        offset : `~astropy.coordinates.Angle`\n            Offset\n        kwargs : dict\n            Forwarded tp plt.plot()\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n\n        if offset is None:\n            offset_axis = self.axes[\"offset\"]\n            off_min, off_max = offset_axis.center.value[[0, -1]]\n            offset = np.linspace(off_min, off_max, 4) * offset_axis.unit\n\n        energy = self.axes[\"energy\"].center\n\n        for off in offset:\n            bkg = self.evaluate(offset=off, energy=energy)\n            kwargs.setdefault(\"label\", f\"offset = {off:.1f}\")\n            ax.plot(energy, bkg.value, **kwargs)\n\n        ax.set_xscale(\"log\")\n        ax.set_yscale(\"log\")\n        ax.set_xlabel(f\"Energy [{energy.unit}]\")\n        ax.set_ylabel(f\"Background rate ({self.unit})\")\n        ax.set_xlim(min(energy.value), max(energy.value))\n        ax.legend(loc=\"best\")\n\n        return ax\n\n    def plot_spectrum(self, ax=None, **kwargs):\n        \"\"\"Plot angle integrated background rate versus energy.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        kwargs : dict\n            Forwarded tp plt.plot()\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n        offset = self.axes[\"offset\"].edges\n        energy = self.axes[\"energy\"].center\n\n        bkg = []\n        for ee in energy:\n            data = self.evaluate(offset=offset, energy=ee)\n            val = np.nansum(trapz_loglog(data, offset, axis=0))\n            bkg.append(val.value)\n\n        ax.plot(energy, bkg, label=\"integrated spectrum\", **kwargs)\n\n        unit = self.unit * offset.unit ** 2\n\n        ax.set_xscale(\"log\")\n        ax.set_yscale(\"log\")\n        ax.set_xlabel(f\"Energy [{energy.unit}]\")\n        ax.set_ylabel(f\"Background rate ({unit})\")\n        ax.set_xlim(min(energy.value), max(energy.value))\n        ax.legend(loc=\"best\")\n        return ax\n\n    def peek(self, figsize=(10, 8)):\n        \"\"\"Quick-look summary plots.\"\"\"\n        import matplotlib.pyplot as plt\n\n        fig, axes = plt.subplots(nrows=2, ncols=2, figsize=figsize)\n        self.plot(ax=axes[1][1])\n        self.plot_offset_dependence(ax=axes[0][0])\n        self.plot_energy_dependence(ax=axes[1][0])\n        self.plot_spectrum(ax=axes[0][1])\n        plt.tight_layout()\n", "path": "gammapy/irf/background.py"}], "after_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport logging\nimport numpy as np\nimport astropy.units as u\nfrom gammapy.maps import MapAxes, MapAxis\nfrom gammapy.utils.integrate import trapz_loglog\nfrom .core import IRF\n\n__all__ = [\"Background3D\", \"Background2D\"]\n\nlog = logging.getLogger(__name__)\n\n\nclass BackgroundIRF(IRF):\n    \"\"\"Background IRF base class\"\"\"\n\n    default_interp_kwargs = dict(\n        bounds_error=False, fill_value=None, values_scale=\"log\"\n    )\n    \"\"\"Default Interpolation kwargs to extrapolate.\"\"\"\n\n    @classmethod\n    def from_table(cls, table, format=\"gadf-dl3\"):\n        \"\"\"Read from `~astropy.table.Table`.\n\n        Parameters\n        ----------\n        table : `~astropy.table.Table`\n            Table with background data\n        format : {\"gadf-dl3\"}\n            Format specification\n\n        Returns\n        -------\n        bkg : `Background2D` or `Background2D`\n            Background IRF class.\n        \"\"\"\n        axes = MapAxes.from_table(table, format=format)[cls.required_axes]\n\n        # Spec says key should be \"BKG\", but there are files around\n        # (e.g. CTA 1DC) that use \"BGD\". For now we support both\n        if \"BKG\" in table.colnames:\n            bkg_name = \"BKG\"\n        elif \"BGD\" in table.colnames:\n            bkg_name = \"BGD\"\n        else:\n            raise ValueError('Invalid column names. Need \"BKG\" or \"BGD\".')\n\n        data = table[bkg_name].quantity[0].T\n\n        if data.unit == \"\" or isinstance(data.unit, u.UnrecognizedUnit):\n            data = u.Quantity(data.value, \"s-1 MeV-1 sr-1\", copy=False)\n            log.warning(\n                \"Invalid unit found in background table! Assuming (s-1 MeV-1 sr-1)\"\n            )\n\n        # TODO: The present HESS and CTA backgroundfits files\n        #  have a reverse order (lon, lat, E) than recommened in GADF(E, lat, lon)\n        #  For now, we suport both.\n\n        if axes.shape == axes.shape[::-1]:\n            log.error(\"Ambiguous axes order in Background fits files!\")\n\n        if np.shape(data) != axes.shape:\n            log.debug(\"Transposing background table on read\")\n            data = data.transpose()\n\n        return cls(axes=axes, data=data.value, meta=table.meta, unit=data.unit)\n\n\nclass Background3D(BackgroundIRF):\n    \"\"\"Background 3D.\n\n    Data format specification: :ref:`gadf:bkg_3d`\n\n    Parameters\n    ----------\n    axes : list of `MapAxis` or `MapAxes` object\n        Required data axes: [\"energy\", \"fov_lon\", \"fov_lat\"] in the given order.\n    data : `~np.ndarray`\n        Data array.\n    unit : str or `~astropy.units.Unit`\n        Data unit usuually ``s^-1 MeV^-1 sr^-1``\n    meta : dict\n        Meta data\n\n    Examples\n    --------\n    Here's an example you can use to learn about this class:\n\n    >>> from gammapy.irf import Background3D\n    >>> filename = '$GAMMAPY_DATA/cta-1dc/caldb/data/cta/1dc/bcf/South_z20_50h/irf_file.fits'\n    >>> bkg_3d = Background3D.read(filename, hdu='BACKGROUND')\n    >>> print(bkg_3d)\n    Background3D\n    ------------\n    <BLANKLINE>\n      axes  : ['energy', 'fov_lon', 'fov_lat']\n      shape : (21, 36, 36)\n      ndim  : 3\n      unit  : 1 / (MeV s sr)\n      dtype : >f4\n    <BLANKLINE>\n\n    \"\"\"\n\n    tag = \"bkg_3d\"\n    required_axes = [\"energy\", \"fov_lon\", \"fov_lat\"]\n\n    def to_2d(self):\n        \"\"\"Convert to `Background2D`.\n\n        This takes the values at Y = 0 and X >= 0.\n        \"\"\"\n        # TODO: this is incorrect as it misses the Jacobian?\n        idx_lon = self.axes[\"fov_lon\"].coord_to_idx(0 * u.deg)[0]\n        idx_lat = self.axes[\"fov_lat\"].coord_to_idx(0 * u.deg)[0]\n        data = self.quantity[:, idx_lon:, idx_lat].copy()\n\n        offset = self.axes[\"fov_lon\"].edges[idx_lon:]\n        offset_axis = MapAxis.from_edges(offset, name=\"offset\")\n\n        return Background2D(\n            axes=[self.axes[\"energy\"], offset_axis], data=data.value, unit=data.unit\n        )\n\n    def peek(self, figsize=(10, 8)):\n        return self.to_2d().peek(figsize)\n\n\nclass Background2D(BackgroundIRF):\n    \"\"\"Background 2D.\n\n    Data format specification: :ref:`gadf:bkg_2d`\n\n    Parameters\n    ----------\n    axes : list of `MapAxis` or `MapAxes` object\n        Required data axes: [\"energy\", \"offset\"] in the given order.\n    data : `~np.ndarray`\n        Data array.\n    unit : str or `~astropy.units.Unit`\n        Data unit usually ``s^-1 MeV^-1 sr^-1``\n    meta : dict\n        Meta data\n    \"\"\"\n\n    tag = \"bkg_2d\"\n    required_axes = [\"energy\", \"offset\"]\n    default_interp_kwargs = dict(bounds_error=False, fill_value=None)\n    \"\"\"Default Interpolation kwargs.\"\"\"\n\n    def plot(self, ax=None, add_cbar=True, **kwargs):\n        \"\"\"Plot energy offset dependence of the background model.\n        \"\"\"\n        import matplotlib.pyplot as plt\n        from matplotlib.colors import LogNorm\n\n        ax = plt.gca() if ax is None else ax\n\n        x = self.axes[\"energy\"].edges.to_value(\"TeV\")\n        y = self.axes[\"offset\"].edges.to_value(\"deg\")\n        z = self.quantity.T.value\n\n        kwargs.setdefault(\"cmap\", \"GnBu\")\n        kwargs.setdefault(\"edgecolors\", \"face\")\n\n        caxes = ax.pcolormesh(x, y, z, norm=LogNorm(), **kwargs)\n        ax.set_xscale(\"log\")\n        ax.set_ylabel(f\"Offset (deg)\")\n        ax.set_xlabel(f\"Energy (TeV)\")\n\n        xmin, xmax = x.min(), x.max()\n        ax.set_xlim(xmin, xmax)\n\n        if add_cbar:\n            label = f\"Background rate ({self.unit})\"\n            ax.figure.colorbar(caxes, ax=ax, label=label)\n\n    def plot_offset_dependence(self, ax=None, energy=None, **kwargs):\n        \"\"\"Plot background rate versus offset for a given energy.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        energy : `~astropy.units.Quantity`\n            Energy\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n\n        if energy is None:\n            energy_axis = self.axes[\"energy\"]\n            e_min, e_max = np.log10(energy_axis.center.value[[0, -1]])\n            energy = np.logspace(e_min, e_max, 4) * energy_axis.unit\n\n        offset = self.axes[\"offset\"].center\n\n        for ee in energy:\n            bkg = self.evaluate(offset=offset, energy=ee)\n            if np.isnan(bkg).all():\n                continue\n            label = f\"energy = {ee:.1f}\"\n            ax.plot(offset, bkg.value, label=label, **kwargs)\n\n        ax.set_xlabel(f\"Offset ({self.axes['offset'].unit})\")\n        ax.set_ylabel(f\"Background rate ({self.unit})\")\n        ax.set_yscale(\"log\")\n        ax.legend(loc=\"upper right\")\n        return ax\n\n    def plot_energy_dependence(self, ax=None, offset=None, **kwargs):\n        \"\"\"Plot background rate versus energy for a given offset.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        offset : `~astropy.coordinates.Angle`\n            Offset\n        kwargs : dict\n            Forwarded tp plt.plot()\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n\n        if offset is None:\n            offset_axis = self.axes[\"offset\"]\n            off_min, off_max = offset_axis.center.value[[0, -1]]\n            offset = np.linspace(off_min, off_max, 4) * offset_axis.unit\n\n        energy = self.axes[\"energy\"].center\n\n        for off in offset:\n            bkg = self.evaluate(offset=off, energy=energy)\n            label = f\"offset = {off:.2f}\"\n            ax.plot(energy, bkg.value, label=label, **kwargs)\n\n        ax.set_xscale(\"log\")\n        ax.set_yscale(\"log\")\n        ax.set_xlabel(f\"Energy [{energy.unit}]\")\n        ax.set_ylabel(f\"Background rate ({self.unit})\")\n        ax.set_xlim(min(energy.value), max(energy.value))\n        ax.legend(loc=\"best\")\n\n        return ax\n\n    def plot_spectrum(self, ax=None, **kwargs):\n        \"\"\"Plot angle integrated background rate versus energy.\n\n        Parameters\n        ----------\n        ax : `~matplotlib.axes.Axes`, optional\n            Axis\n        kwargs : dict\n            Forwarded tp plt.plot()\n\n        Returns\n        -------\n        ax : `~matplotlib.axes.Axes`\n            Axis\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        ax = plt.gca() if ax is None else ax\n        offset = self.axes[\"offset\"].edges\n        energy = self.axes[\"energy\"].center\n\n        bkg = []\n        for ee in energy:\n            data = self.evaluate(offset=offset, energy=ee)\n            val = np.nansum(trapz_loglog(data, offset, axis=0))\n            bkg.append(val.value)\n\n        ax.plot(energy, bkg, label=\"integrated spectrum\", **kwargs)\n\n        unit = self.unit * offset.unit ** 2\n\n        ax.set_xscale(\"log\")\n        ax.set_yscale(\"log\")\n        ax.set_xlabel(f\"Energy [{energy.unit}]\")\n        ax.set_ylabel(f\"Background rate ({unit})\")\n        ax.set_xlim(min(energy.value), max(energy.value))\n        ax.legend(loc=\"best\")\n        return ax\n\n    def peek(self, figsize=(10, 8)):\n        \"\"\"Quick-look summary plots.\"\"\"\n        import matplotlib.pyplot as plt\n\n        fig, axes = plt.subplots(nrows=2, ncols=2, figsize=figsize)\n        self.plot(ax=axes[1][1])\n        self.plot_offset_dependence(ax=axes[0][0])\n        self.plot_energy_dependence(ax=axes[1][0])\n        self.plot_spectrum(ax=axes[0][1])\n        plt.tight_layout()\n", "path": "gammapy/irf/background.py"}]}