|
4 | 4 | from pathlib import Path |
5 | 5 |
|
6 | 6 | import astropy.units as u |
| 7 | +from astropy.coordinates import AltAz |
7 | 8 | from astropy.visualization import quantity_support |
8 | | -from astroplan import FixedTarget |
| 9 | +from astroplan import FixedTarget, time_grid_from_range |
9 | 10 | from astroplan.plots import plot_sky_24hr, plot_altitude |
10 | 11 | import matplotlib.pyplot as plt |
11 | 12 | import numpy as np |
12 | 13 | import seaborn as sns |
13 | 14 |
|
| 15 | +from gammapy.maps import MapAxis |
| 16 | + |
14 | 17 | from .core import observed_flux, get_horizon_stereo_profile |
15 | 18 | from .spectral import crab_nebula_spectrum |
16 | 19 | from iact_estimator import HORIZON_PROFILE_M1, HORIZON_PROFILE_M2 |
|
22 | 25 | "plot_altitude_airmass", |
23 | 26 | "plot_exposure", |
24 | 27 | "plot_rates", |
| 28 | + "plot_gammapy_sed", |
| 29 | + "plot_irf_source_pointing", |
25 | 30 | ] |
26 | 31 |
|
27 | 32 | logger = logging.getLogger(__name__) |
@@ -404,3 +409,135 @@ def plot_rates(performance_data, title=None, ax=None): |
404 | 409 | ax.set_xscale("log") |
405 | 410 | ax.set_yscale("log") |
406 | 411 | return ax |
| 412 | + |
| 413 | + |
| 414 | +def plot_gammapy_sed( |
| 415 | + observation_livetime, |
| 416 | + flux_points, |
| 417 | + assumed_spectral_model, |
| 418 | + plot_ts_profiles=True, |
| 419 | + energy_flux_unit="TeV cm-2 s-1", |
| 420 | + source_name=None, |
| 421 | + save=True, |
| 422 | + output_path=None, |
| 423 | + **kwargs, |
| 424 | +): |
| 425 | + """Plot an SED with gammapy shoowing the significance per bin. |
| 426 | +
|
| 427 | + Optionally plot TS profiles. |
| 428 | +
|
| 429 | + Parameters |
| 430 | + ========== |
| 431 | + observation_livetime : `astropy.units.Quantity` |
| 432 | + flux_points : `~gammapy.estimators.FluxPoints` |
| 433 | + assumed_spectral_model : `~gammapy.modeling.models.SpectralModel` |
| 434 | + plot_ts_profiles=True, |
| 435 | + energy_flux_unit : str, default="TeV cm-2 s-1" |
| 436 | + source_name : str, default=None |
| 437 | + savefig : bool, default=True |
| 438 | + output_path : str or `pathlib.Path`, default=None |
| 439 | + kwargs : |
| 440 | + Keyword arguments for `~matplotlib.axis.Axis.grid()` |
| 441 | + and `~matplotlib.figure.Figure.savefig()`. |
| 442 | + """ |
| 443 | + |
| 444 | + grid_kwargs = kwargs.get("grid", {}) |
| 445 | + savefig_kwargs = kwargs.get("savefig", {}) |
| 446 | + seaborn_kwargs = kwargs.get("seaborn", {}) |
| 447 | + |
| 448 | + figsize = set_context_with_scaled_figsize( |
| 449 | + context=seaborn_kwargs["context"], |
| 450 | + base_figure_size=plt.rcParams["figure.figsize"], |
| 451 | + ) |
| 452 | + fig, ax = plt.subplots(figsize=figsize, layout="constrained") |
| 453 | + |
| 454 | + sed_type = "e2dnde" |
| 455 | + energy_flux_unit = u.Unit(energy_flux_unit) |
| 456 | + |
| 457 | + ax.yaxis.units = energy_flux_unit |
| 458 | + |
| 459 | + flux_points_table = flux_points.to_table( |
| 460 | + sed_type="e2dnde", format="gadf-sed", formatted=True |
| 461 | + ) |
| 462 | + |
| 463 | + energy_axis = MapAxis.from_energy_edges( |
| 464 | + np.concatenate( |
| 465 | + ( |
| 466 | + flux_points_table["e_min"].data, |
| 467 | + np.array([flux_points_table["e_max"][-1]]), |
| 468 | + ) |
| 469 | + ) |
| 470 | + * flux_points_table["e_min"].unit |
| 471 | + ) |
| 472 | + |
| 473 | + assumed_spectral_model.plot( |
| 474 | + [energy_axis.edges.min().value, energy_axis.edges.max().value] |
| 475 | + * energy_axis.edges.unit, |
| 476 | + ax=ax, |
| 477 | + sed_type=sed_type, |
| 478 | + label="Assumed spectral model", |
| 479 | + color="green", |
| 480 | + ) |
| 481 | + |
| 482 | + flux_points.plot( |
| 483 | + ax=ax, |
| 484 | + sed_type=sed_type, |
| 485 | + color="darkorange", |
| 486 | + label=f"Estimated observation ({observation_livetime})", |
| 487 | + ) |
| 488 | + |
| 489 | + if plot_ts_profiles: |
| 490 | + flux_points.plot_ts_profiles(ax=ax, sed_type=sed_type, cmap="Blues") |
| 491 | + |
| 492 | + for flux_point in flux_points_table: |
| 493 | + ax.annotate( |
| 494 | + rf"{flux_point['sqrt_ts']:.1f} $\sigma$", |
| 495 | + xy=( |
| 496 | + flux_point["e_min"], |
| 497 | + ( |
| 498 | + flux_point["e2dnde"] * flux_point.table["e2dnde"].unit |
| 499 | + + 3 * flux_point["e2dnde_err"] * flux_point.table["e2dnde_err"].unit |
| 500 | + ).to_value(energy_flux_unit), |
| 501 | + ), |
| 502 | + rotation=45, |
| 503 | + ) |
| 504 | + |
| 505 | + ax.grid(**grid_kwargs) |
| 506 | + |
| 507 | + ax.set_ylabel( |
| 508 | + rf"$E^{2}\dfrac{{dN}}{{dE}}\quad$({energy_flux_unit.to_string('latex',fraction=False)})" |
| 509 | + ) |
| 510 | + ax.legend() |
| 511 | + |
| 512 | + if save: |
| 513 | + output_path = output_path if output_path is not None else Path.cwd() |
| 514 | + fig.savefig( |
| 515 | + output_path / f"{source_name or ''}_gammapy_sed.{savefig_kwargs['format']}", |
| 516 | + **savefig_kwargs, |
| 517 | + ) |
| 518 | + |
| 519 | + |
| 520 | +def plot_irf_source_pointing(target_source, observation, ax=None, **kwargs): |
| 521 | + ax = plt.gca() if ax else None |
| 522 | + |
| 523 | + observation_times = time_grid_from_range( |
| 524 | + [observation.tstart, observation.tstop], time_resolution=1 * u.h |
| 525 | + ) |
| 526 | + |
| 527 | + altaz_pointings = observation.get_pointing_altaz(observation_times) |
| 528 | + target_source_altaz = target_source.coord.transform_to( |
| 529 | + AltAz( |
| 530 | + obstime=observation_times, location=observation.observatory_earth_location |
| 531 | + ) |
| 532 | + ) |
| 533 | + |
| 534 | + ax.plot(altaz_pointings.az, altaz_pointings.alt, label="IRFs pointing") |
| 535 | + ax.plot(target_source_altaz.az, target_source_altaz.alt, label="Target source") |
| 536 | + |
| 537 | + ax.set_ylabel("Zenith angle (deg)") |
| 538 | + ax.set_xlabel("Azimuth angle (deg)") |
| 539 | + ax.grid() |
| 540 | + |
| 541 | + ax.legend() |
| 542 | + |
| 543 | + return ax |
0 commit comments