Flux Profile Estimation#

Learn how to estimate flux profiles on a Fermi-LAT dataset.

Prerequisites#

Knowledge of 3D data reduction and datasets used in Gammapy, see for instance the first analysis tutorial.

Context#

A useful tool to study and compare the spatial distribution of flux in images and data cubes is the measurement of flux profiles. Flux profiles can show spatial correlations of gamma-ray data with e.g. gas maps or other type of gamma-ray data. Most commonly flux profiles are measured along some preferred coordinate axis, either radially distance from a source of interest, along longitude and latitude coordinate axes or along the path defined by two spatial coordinates.

Proposed Approach#

Flux profile estimation essentially works by estimating flux points for a set of predefined spatially connected regions. For radial flux profiles the shape of the regions are annuli with a common center, for linear profiles it’s typically a rectangular shape.

We will work on a pre-computed MapDataset of Fermi-LAT data, use SkyRegion to define the structure of the bins of the flux profile and run the flux profile extraction using the FluxProfileEstimator

import numpy as np
from astropy import units as u
from astropy.coordinates import SkyCoord

# %matplotlib inline
import matplotlib.pyplot as plt

Setup#

from IPython.display import display
from gammapy.datasets import MapDataset
from gammapy.estimators import FluxPoints, FluxProfileEstimator
from gammapy.maps import RegionGeom
from gammapy.modeling.models import PowerLawSpectralModel

Check setup#

from gammapy.utils.check import check_tutorials_setup
from gammapy.utils.regions import (
    make_concentric_annulus_sky_regions,
    make_orthogonal_rectangle_sky_regions,
)

check_tutorials_setup()
System:

        python_executable      : /Users/mregeard/Workspace/dev/code/gammapy/gammapy/.tox/build_docs/bin/python
        python_version         : 3.9.18
        machine                : x86_64
        system                 : Darwin


Gammapy package:

        version                : 1.1.dev2518+gc7608209d.d20231208
        path                   : /Users/mregeard/Workspace/dev/code/gammapy/gammapy/.tox/build_docs/lib/python3.9/site-packages/gammapy


Other packages:

        numpy                  : 1.26.1
        scipy                  : 1.11.3
        astropy                : 5.2.2
        regions                : 0.7
        click                  : 8.1.7
        yaml                   : 6.0.1
        IPython                : 8.16.1
        jupyterlab             : not installed
        matplotlib             : 3.8.0
        pandas                 : not installed
        healpy                 : 1.16.6
        iminuit                : 2.24.0
        sherpa                 : 4.16.0
        naima                  : 0.10.0
        emcee                  : 3.1.4
        corner                 : 2.2.2
        ray                    : 2.7.1


Gammapy environment variables:

        GAMMAPY_DATA           : /Users/mregeard/Workspace/dev/code/gammapy/gammapy-data

Read and Introduce Data#

dataset = MapDataset.read(
    "$GAMMAPY_DATA/fermi-3fhl-gc/fermi-3fhl-gc.fits.gz", name="fermi-dataset"
)

This is what the counts image we will work with looks like:

flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:83: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

There are 400x200 pixels in the dataset and 11 energy bins between 10 GeV and 2 TeV:

WcsNDMap

        geom  : WcsGeom
        axes  : ['lon', 'lat', 'energy']
        shape : (400, 200, 11)
        ndim  : 3
        unit  :
        dtype : >i4

Profile Estimation#

Configuration#

We start by defining a list of spatially connected regions along the galactic longitude axis. For this there is a helper function make_orthogonal_rectangle_sky_regions. The individual region bins for the profile have a height of 3 deg and in total there are 31 bins. Its starts from lon = 10 deg and goes to lon = 350 deg. In addition, we have to specify the wcs to take into account possible projections effects on the region definition:

regions = make_orthogonal_rectangle_sky_regions(
    start_pos=SkyCoord("10d", "0d", frame="galactic"),
    end_pos=SkyCoord("350d", "0d", frame="galactic"),
    wcs=counts_image.geom.wcs,
    height="3 deg",
    nbin=51,
)

We can use the RegionGeom object to illustrate the regions on top of the counts image:

geom = RegionGeom.create(region=regions)
ax = counts_image.smooth("0.1 deg").plot(stretch="sqrt")
geom.plot_region(ax=ax, color="w")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/.tox/build_docs/lib/python3.9/site-packages/regions/shapes/rectangle.py:208: UserWarning: Setting the 'color' property will override the edgecolor or facecolor properties.
  return Rectangle(xy=xy, width=width, height=height,
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:127: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

Next we create the FluxProfileEstimator. For the estimation of the flux profile we assume a spectral model with a power-law shape and an index of 2.3

flux_profile_estimator = FluxProfileEstimator(
    regions=regions,
    spectrum=PowerLawSpectralModel(index=2.3),
    energy_edges=[10, 2000] * u.GeV,
    selection_optional=["ul"],
)

We can see the full configuration by printing the estimator object:

FluxProfileEstimator
--------------------

  energy_edges           : [  10. 2000.] GeV
  fit                    : <gammapy.modeling.fit.Fit object at 0x14d976940>
  n_jobs                 : None
  n_sigma                : 1
  n_sigma_ul             : 2
  norm                   : Parameter(name='norm', value=1.0, factor=1.0, scale=1.0, unit=Unit(dimensionless), min=nan, max=nan, frozen=False, prior=None, id=0x14d976130)
  null_value             : 0
  parallel_backend       : None
  reoptimize             : False
  selection_optional     : ['ul']
  source                 : 0
  spectrum               : PowerLawSpectralModel
  sum_over_energy_groups : False

Run Estimation#

Now we can run the profile estimation and explore the results:

FluxPoints
----------

  geom                   : RegionGeom
  axes                   : ['lon', 'lat', 'energy', 'projected-distance']
  shape                  : (1, 1, 1, 51)
  quantities             : ['norm', 'norm_err', 'norm_ul', 'ts', 'npred', 'npred_excess', 'stat', 'counts', 'success']
  ref. model             : pl
  n_sigma                : 1
  n_sigma_ul             : 2
  sqrt_ts_threshold_ul   : 2
  sed type init          : likelihood

We can see the flux profile is represented by a FluxPoints object with a projected-distance axis, which defines the main axis the flux profile is measured along. The lon and lat axes can be ignored.

Plotting Results#

Let us directly plot the result using plot:

ax = profile.plot(sed_type="dnde")
ax.set_yscale("linear")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:176: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

Based on the spectral model we specified above we can also plot in any other sed type, e.g. energy flux and define a different threshold when to plot upper limits:

flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:190: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

We can also plot any other quantity of interest, that is defined on the FluxPoints result object. E.g. the predicted total counts, background counts and excess counts:

quantities = ["npred", "npred_excess", "npred_background"]

fig, ax = plt.subplots()

for quantity in quantities:
    profile[quantity].plot(ax=ax, label=quantity.title())

ax.set_ylabel("Counts")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:207: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

Serialisation and I/O#

The profile can be serialised using write, given a specific format:

profile.write(
    filename="flux_profile_fermi.fits",
    format="profile",
    overwrite=True,
    sed_type="dnde",
)

profile_new = FluxPoints.read(filename="flux_profile_fermi.fits", format="profile")

ax = profile_new.plot()
ax.set_yscale("linear")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:229: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

The profile can be serialised to a Table object using:

table = profile.to_table(format="profile", formatted=True)
display(table)
       x_min              x_max              x_ref           e_ref        e_min     ...    stat   is_ul counts success
        deg                deg                deg             keV          keV      ...
------------------- ------------------ ------------------ ------------ ------------ ... --------- ----- ------ -------
-0.1960784313725492 0.1960784313725492                0.0 70710678.119 10000000.000 ...     0.000 False    0.0   False
 0.1960784313725492 0.5882352941176467  0.392156862745098 70710678.119 10000000.000 ...     0.000 False    0.0   False
 0.5882352941176467 0.9803921568627443 0.7843137254901955 70710678.119 10000000.000 ...  -818.848  True  163.0    True
 0.9803921568627443 1.3725490196078436  1.176470588235294 70710678.119 10000000.000 ... -3013.446 False  448.0    True
 1.3725490196078436  1.764705882352942 1.5686274509803928 70710678.119 10000000.000 ... -4348.966 False  599.0    True
  1.764705882352942   2.15686274509804  1.960784313725491 70710678.119 10000000.000 ... -2218.623 False  354.0    True
   2.15686274509804  2.549019607843138 2.3529411764705888 70710678.119 10000000.000 ... -2452.717 False  367.0    True
  2.549019607843138  2.941176470588236  2.745098039215687 70710678.119 10000000.000 ... -2174.005 False  339.0    True
  2.941176470588236 3.3333333333333344  3.137254901960785 70710678.119 10000000.000 ... -3887.531 False  531.0    True
 3.3333333333333344  3.725490196078432  3.529411764705883 70710678.119 10000000.000 ... -3183.973 False  458.0    True
  3.725490196078432  4.117647058823529 3.9215686274509807 70710678.119 10000000.000 ... -4201.048 False  566.0    True
  4.117647058823529  4.509803921568627  4.313725490196078 70710678.119 10000000.000 ... -3040.782 False  448.0    True
  4.509803921568627  4.901960784313727 4.7058823529411775 70710678.119 10000000.000 ... -2313.852 False  356.0    True
  4.901960784313727  5.294117647058824  5.098039215686276 70710678.119 10000000.000 ... -1979.035  True  315.0    True
  5.294117647058824  5.686274509803923 5.4901960784313735 70710678.119 10000000.000 ... -1643.463  True  272.0    True
  5.686274509803923   6.07843137254902  5.882352941176471 70710678.119 10000000.000 ... -1692.050  True  282.0    True
   6.07843137254902  6.470588235294118  6.274509803921569 70710678.119 10000000.000 ... -2034.334  True  319.0    True
  6.470588235294118  6.862745098039216  6.666666666666667 70710678.119 10000000.000 ... -1931.193  True  311.0    True
  6.862745098039216  7.254901960784315  7.058823529411765 70710678.119 10000000.000 ... -1861.277  True  303.0    True
  7.254901960784315  7.647058823529413  7.450980392156864 70710678.119 10000000.000 ... -1577.587  True  263.0    True
  7.647058823529413  8.039215686274511  7.843137254901962 70710678.119 10000000.000 ... -1754.528  True  288.0    True
  8.039215686274511   8.43137254901961  8.235294117647062 70710678.119 10000000.000 ... -2098.681  True  328.0    True
                ...                ...                ...          ...          ... ...       ...   ...    ...     ...
 10.784313725490232 11.176470588235311 10.980392156862772 70710678.119 10000000.000 ... -3224.239 False  473.0    True
 11.176470588235311 11.568627450980387 11.372549019607849 70710678.119 10000000.000 ... -3256.816 False  471.0    True
 11.568627450980387 11.960784313725515 11.764705882352951 70710678.119 10000000.000 ... -3129.927 False  453.0    True
 11.960784313725515 12.352941176470619 12.156862745098067 70710678.119 10000000.000 ... -2782.182  True  408.0    True
 12.352941176470619 12.745098039215696 12.549019607843157 70710678.119 10000000.000 ... -2877.351 False  425.0    True
 12.745098039215696 13.137254901960773 12.941176470588236 70710678.119 10000000.000 ... -2705.136 False  412.0    True
 13.137254901960773 13.529411764705875 13.333333333333325 70710678.119 10000000.000 ... -3216.032 False  458.0    True
 13.529411764705875 13.921568627451002  13.72549019607844 70710678.119 10000000.000 ... -2403.516 False  373.0    True
 13.921568627451002 14.313725490196079  14.11764705882354 70710678.119 10000000.000 ... -2526.278 False  384.0    True
 14.313725490196079 14.705882352941181  14.50980392156863 70710678.119 10000000.000 ... -1936.261  True  313.0    True
 14.705882352941181 15.098039215686308 14.901960784313744 70710678.119 10000000.000 ... -2178.012  True  357.0    True
 15.098039215686308 15.490196078431385 15.294117647058847 70710678.119 10000000.000 ... -1914.901  True  311.0    True
 15.490196078431385 15.882352941176464 15.686274509803924 70710678.119 10000000.000 ... -2016.119  True  327.0    True
 15.882352941176464 16.274509803921593 16.078431372549026 70710678.119 10000000.000 ... -1957.576 False  321.0    True
 16.274509803921593 16.666666666666693 16.470588235294144 70710678.119 10000000.000 ... -2767.621 False  421.0    True
 16.666666666666693  17.05882352941177 16.862745098039234 70710678.119 10000000.000 ... -2830.574 False  431.0    True
  17.05882352941177   17.4509803921569 17.254901960784338 70710678.119 10000000.000 ... -2356.744  True  374.0    True
   17.4509803921569 17.843137254902004 17.647058823529452 70710678.119 10000000.000 ... -2463.364  True  370.0    True
 17.843137254902004  18.23529411764708  18.03921568627454 70710678.119 10000000.000 ... -2803.387 False  410.0    True
  18.23529411764708 18.627450980392155 18.431372549019617 70710678.119 10000000.000 ... -2186.572  True  336.0    True
 18.627450980392155  19.01960784313726 18.823529411764707 70710678.119 10000000.000 ...  -877.032  True  172.0    True
  19.01960784313726 19.411764705882383  19.21568627450982 70710678.119 10000000.000 ...     0.000 False    0.0   False
 19.411764705882383 19.803921568627494  19.60784313725494 70710678.119 10000000.000 ...     0.000 False    0.0   False
Length = 51 rows

No we can also estimate a radial profile starting from the Galactic center:

regions = make_concentric_annulus_sky_regions(
    center=SkyCoord("0d", "0d", frame="galactic"),
    radius_max="1.5 deg",
    nbin=11,
)

Again we first illustrate the regions:

geom = RegionGeom.create(region=regions)
gc_image = counts_image.cutout(
    position=SkyCoord("0d", "0d", frame="galactic"), width=3 * u.deg
)
ax = gc_image.smooth("0.1 deg").plot(stretch="sqrt")
geom.plot_region(ax=ax, color="w")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/.tox/build_docs/lib/python3.9/site-packages/regions/core/compound.py:160: UserWarning: Setting the 'color' property will override the edgecolor or facecolor properties.
  patch = mpatches.PathPatch(path, **mpl_kwargs)
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:262: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

This time we define two energy bins and include the fit statistic profile in the computation:

flux_profile_estimator = FluxProfileEstimator(
    regions=regions,
    spectrum=PowerLawSpectralModel(index=2.3),
    energy_edges=[10, 100, 2000] * u.GeV,
    selection_optional=["ul", "scan"],
)

The configuration of the fit statistic profile is done throught the norm parameter:

flux_profile_estimator.norm.scan_values = np.linspace(-1, 5, 11)

Now we can run the estimator,

and plot the result:

profile.plot(axis_name="projected-distance", sed_type="flux")
plt.show()
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:290: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

However because of the powerlaw spectrum the flux at high energies is much lower. To extract the profile at high energies only we can use:

profile_high = profile.slice_by_idx({"energy": slice(1, 2)})
plt.show()

And now plot the points together with the likelihood profiles:

fig, ax = plt.subplots()
profile_high.plot(ax=ax, sed_type="eflux", color="tab:orange")
profile_high.plot_ts_profiles(ax=ax, sed_type="eflux")
ax.set_yscale("linear")
plt.show()

# sphinx_gallery_thumbnail_number = 2
flux profiles
/Users/mregeard/Workspace/dev/code/gammapy/gammapy/examples/tutorials/analysis-3d/flux_profiles.py:310: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

Total running time of the script: ( 0 minutes 10.087 seconds)

Gallery generated by Sphinx-Gallery