Repository: gammapy/gammapy
Pull request #5992 (2025-07-18 13:27:01)

Update the peek function

**Description**

This pull request is associated to the issue #5991. 
The `observtion.peek()` is updated to forced to have square plots. For that, it was needed to update the `utils.add_colorbar()` function, to have a height equal to the plot one, excluding the title height.

**Dear reviewer**
The outputs of the change are here:
<img width="1500" height="1000" alt="Figure_1" src="https://github.com/user-attachments/assets/f9e26480-ecd5-4cd3-a588-7ddc0fda68b4" />
for the VERITAS dataset,

and 
<img width="1500" height="1000" alt="Figure_2" src="https://github.com/user-attachments/assets/9a013ecf-a53b-44d6-a18d-26a895332423" />

for the usual output for a standard data production



Patch:
diff --git a/examples/tutorials/api/irfs.py b/examples/tutorials/api/irfs.py
index 418ff8888..876fb9c03 100644
--- a/examples/tutorials/api/irfs.py
+++ b/examples/tutorials/api/irfs.py
@@ -226,7 +226,8 @@ print(res)
 # sphinx_gallery_thumbnail_number = 1
 aeff_eval = aeff_3d.evaluate(energy_true=[1.0] * u.TeV)
 
-ax = plt.subplot()
+plt.figure(figsize=(9, 9))
+ax = plt.gca()
 with quantity_support():
     caxes = ax.pcolormesh(
         fov_lat_axis.edges, fov_lon_axis.edges, aeff_eval.value.squeeze()
diff --git a/examples/tutorials/starting/analysis_2.py b/examples/tutorials/starting/analysis_2.py
index 3299d7099..351cffe9c 100644
--- a/examples/tutorials/starting/analysis_2.py
+++ b/examples/tutorials/starting/analysis_2.py
@@ -138,6 +138,13 @@ selected_obs_table = data_store.obs_table.select_observations(selection)
 
 observations = data_store.get_observations(selected_obs_table["OBS_ID"])
 
+######################################################################
+# We can have a quick look at the content of each observation with
+# the following instructions:
+#
+
+observations[0].peek()
+print(observations[0])
 
 ######################################################################
 # Preparing reduced datasets geometry
diff --git a/gammapy/data/event_list.py b/gammapy/data/event_list.py
index f6965f8ad..3d613e7cd 100644
--- a/gammapy/data/event_list.py
+++ b/gammapy/data/event_list.py
@@ -853,7 +853,7 @@ class EventList:
             ax_image = fig.add_subplot(gs[0, :], projection=m.geom.wcs)
         else:
             ax_image = fig.add_subplot(gs[0, 0], projection=m.geom.wcs)
-        m.plot(ax=ax_image, stretch="sqrt", vmin=0)
+        m.plot(ax=ax_image, stretch="sqrt", vmin=0, add_cbar=True)
         plt.subplots_adjust(wspace=0.3)
 
     @property
diff --git a/gammapy/data/observations.py b/gammapy/data/observations.py
index aea194e9d..900df2ea9 100644
--- a/gammapy/data/observations.py
+++ b/gammapy/data/observations.py
@@ -111,8 +111,6 @@ class Observation:
     @property
     def bkg(self):
         """Background of the observation."""
-        from gammapy.irf import FoVAlignment
-
         bkg = self._bkg
         # used for backward compatibility of old HESS data
         try:
@@ -465,10 +463,10 @@ class Observation:
         figsize : tuple, optional
             Figure size. Default is (15, 10).
         """
-        plottable_hds = ["events", "aeff", "psf", "edisp", "bkg", "rad_max"]
-
+        plottable_hds = ["events", "aeff", "edisp", "psf", "bkg", "rad_max"]
         plot_hdus = list(set(plottable_hds) & set(self.available_hdus))
         plot_hdus.sort()
+        plot_hdus.insert(0, plot_hdus.pop(plot_hdus.index("events")))
 
         n_irfs = len(plot_hdus)
         nrows = n_irfs // 2
@@ -478,10 +476,12 @@ class Observation:
             nrows=nrows,
             ncols=ncols,
             figsize=figsize,
-            gridspec_kw={"wspace": 0.3, "hspace": 0.3},
+            gridspec_kw={"wspace": 0.55, "hspace": 0.3},
         )
 
         for idx, (ax, name) in enumerate(zip_longest(axes.flat, plot_hdus)):
+            ax.set_box_aspect(1)
+
             if name == "aeff":
                 self.aeff.plot(ax=ax)
                 ax.set_title("Effective area")
@@ -515,6 +515,8 @@ class Observation:
             if name is None:
                 ax.set_visible(False)
 
+        fig.tight_layout()
+
     def select_time(self, time_interval):
         """Select a time interval of the observation.
 
diff --git a/gammapy/irf/effective_area.py b/gammapy/irf/effective_area.py
index 31387c777..cbdd79458 100644
--- a/gammapy/irf/effective_area.py
+++ b/gammapy/irf/effective_area.py
@@ -197,8 +197,10 @@ class EffectiveAreaTable2D(IRF):
         offset.format_plot_yaxis(ax=ax)
 
         if add_cbar:
+            kwargs_colorbar.setdefault("format", "%.1e")
             label = f"Effective Area [{aeff.unit.to_string(UNIT_STRING_FORMAT)}]"
             kwargs_colorbar.setdefault("label", label)
+            kwargs_colorbar.setdefault("labelsize", 7)
             add_colorbar(caxes, ax=ax, axes_loc=axes_loc, **kwargs_colorbar)
 
         return ax
diff --git a/gammapy/visualization/utils.py b/gammapy/visualization/utils.py
index a6fc051f3..2599b0a8d 100644
--- a/gammapy/visualization/utils.py
+++ b/gammapy/visualization/utils.py
@@ -7,7 +7,6 @@ from scipy.stats import norm
 from astropy.visualization import make_lupton_rgb
 import matplotlib.axes as maxes
 import matplotlib.pyplot as plt
-from mpl_toolkits.axes_grid1 import make_axes_locatable
 
 __all__ = [
     "add_colorbar",
@@ -15,6 +14,7 @@ __all__ = [
     "plot_map_rgb",
     "plot_theta_squared_table",
     "plot_distribution",
+    "parse_percentage",
 ]
 
 
@@ -57,8 +57,8 @@ def add_colorbar(img, ax, axes_loc=None, **kwargs):
         from gammapy.visualization import add_colorbar
         import matplotlib.pyplot as plt
         map_ = Map.read("$GAMMAPY_DATA/cta-1dc-gc/cta-1dc-gc.fits.gz")
-        axes_loc = {"position": "right", "size": "2%", "pad": "10%"}
-        kwargs_colorbar = {'label':'Colorbar label'}
+        axes_loc = {"size": "2%", "pad": "10%"}
+        kwargs_colorbar = {'label':'Colorbar label', 'labelsize':6}
 
         # Example outside gammapy
         fig = plt.figure(figsize=(6, 3))
@@ -77,17 +77,42 @@ def add_colorbar(img, ax, axes_loc=None, **kwargs):
     kwargs.setdefault("orientation", "vertical")
 
     axes_loc = axes_loc or {}
-    axes_loc.setdefault("position", "right")
     axes_loc.setdefault("size", "5%")
     axes_loc.setdefault("pad", "2%")
     axes_loc.setdefault("axes_class", maxes.Axes)
 
-    divider = make_axes_locatable(ax)
-    cax = divider.append_axes(**axes_loc)
-    cbar = plt.colorbar(img, cax=cax, **kwargs)
+    csize = parse_percentage(axes_loc["size"])
+    cpad = parse_percentage(axes_loc["pad"])
+    pos = ax.get_position()
+    ax_width = pos.width
+    cbar_pad = cpad * ax_width
+    cbar_width = csize * ax_width
+
+    fig = ax.figure
+    cbar_ax = fig.add_axes(
+        [pos.x1 + cbar_pad, pos.y0, cbar_width, pos.height],
+        axes_class=axes_loc["axes_class"],
+    )
+
+    if "labelsize" in kwargs:
+        fontsize = kwargs["labelsize"]
+        cbar_ax.tick_params(labelsize=fontsize)
+        kwargs.pop("labelsize")
+
+    cbar = fig.colorbar(img, cax=cbar_ax, **kwargs)
     return cbar
 
 
+def parse_percentage(value):
+    """From the input, return the percentage as a float."""
+    if isinstance(value, str) and value.endswith("%"):
+        return float(value.strip("%")) / 100.0
+    elif isinstance(value, (int, float)):
+        return float(value)  # Already absolute
+    else:
+        raise ValueError(f"Invalid value for percentage-based size: {value}")
+
+
 def plot_map_rgb(map_, ax=None, **kwargs):
     """
     Plot RGB image on matplotlib WCS axes.
@@ -331,7 +356,9 @@ def plot_distribution(
             raise ValueError("Map and mask spatial geometry must agree!")
 
         if not mask.is_mask:
-            raise ValueError(f"Mask map must be of boolean type, got {mask.data.dtype} instead.")
+            raise ValueError(
+                f"Mask map must be of boolean type, got {mask.data.dtype} instead."
+            )
 
         cutout_mask.data = np.logical_and(cutout_mask.data, mask.data)
 


Test patch:
diff --git a/gammapy/visualization/tests/test_utils.py b/gammapy/visualization/tests/test_utils.py
index af858f712..60c09edf2 100644
--- a/gammapy/visualization/tests/test_utils.py
+++ b/gammapy/visualization/tests/test_utils.py
@@ -15,6 +15,7 @@ from gammapy.visualization import (
     plot_map_rgb,
     plot_theta_squared_table,
 )
+from gammapy.visualization.utils import parse_percentage
 
 
 @requires_data()
@@ -162,3 +163,14 @@ def test_plot_distribution():
             axes, res = plot_distribution(
                 wcs_map=map_, mask=mask_3, func="norm", kwargs_hist={"bins": 40}
             )
+
+
+def test_parse_percentage():
+    rr = parse_percentage("5%")
+    assert rr == 0.05
+
+    rr = parse_percentage(0.1)
+    assert rr == 0.1
+
+    with pytest.raises(ValueError):
+        parse_percentage("crazy")