Source code for visualization.plot_ray_tracing_psf

"""Shared PSF plotting helpers for ray-tracing workflows."""

from pathlib import Path

import astropy.units as u

from simtools.visualization import visualize
from simtools.visualization.matplotlib_backend import pyplot as plt


[docs] def create_cumulative_psf_figure( data, radius_key, cumulative_key, containment_radius_cm, psf_diameter_cm=None, **plot_kwargs ): """ Create a cumulative PSF plot. Parameters ---------- data : numpy.ndarray Structured array with radius and cumulative PSF columns. radius_key, cumulative_key : str Column names of the radius and cumulative PSF values. containment_radius_cm : float Containment marker radius in cm. psf_diameter_cm : float, optional PSF diameter marker in cm. **plot_kwargs Passed to ``Axes.plot``. Returns ------- tuple Figure and axes. """ fig, ax = plt.subplots(constrained_layout=True) ax.set_xlabel("Radius (cm)") ax.set_ylabel("Contained light %") ax.plot(data[radius_key], data[cumulative_key], **plot_kwargs) ax.axvline(x=containment_radius_cm, color="b", linestyle="--", linewidth=1) if psf_diameter_cm is not None: ax.axvline(x=psf_diameter_cm / 2.0, color="r", linestyle="--", linewidth=1) return fig, ax
[docs] def create_psf_image_figure( data, containment_radius, center, psf_kwargs=None, use_current_axes=False, **hist_kwargs ): """ Create a 2D histogram plot of a PSF image with the containment circle. Parameters ---------- data : numpy.ndarray Structured array with ``X`` and ``Y`` columns. containment_radius : float Radius of the containment circle, in the same unit as the data. center : tuple Center of the PSF circle, in the same unit as the data. psf_kwargs : dict, optional Keyword arguments passed to ``matplotlib.patches.Circle``. use_current_axes : bool Draw on the current axes instead of a new figure. **hist_kwargs Passed to ``visualize.plot_hist_2d`` (``image_range`` is an alias of ``range``). Returns ------- tuple Figure and axes. """ if "image_range" in hist_kwargs: hist_kwargs["range"] = hist_kwargs.pop("image_range") fig = visualize.plot_hist_2d(data, ax=plt.gca() if use_current_axes else None, **hist_kwargs) ax = fig.gca() ax.set_xlabel("X Position (cm)") ax.set_ylabel("Y Position (cm)") ax.add_artist(plt.Circle(center, containment_radius, **(psf_kwargs or {}))) for draw_line in (ax.axhline, ax.axvline): draw_line(0, color="k", linestyle="--", zorder=3, linewidth=0.5) return fig, ax
[docs] def create_annotated_psf_image_figure( data, off_x, off_y, psf, containment_radius, image_range, bins=150, cmap=None, psf_kwargs=None, telescope_name=None, ): """ Create a PSF image figure annotated with offset and PSF information. Parameters ---------- data : numpy.ndarray Structured array with ``X`` and ``Y`` columns, in the display unit. off_x, off_y : float Off-axis components in degrees. psf : astropy.units.Quantity PSF diameter; its unit sets the axis labels and annotation. containment_radius : astropy.units.Quantity Containment radius in the same unit as ``psf``. image_range : list Range passed to ``Axes.hist2d``. bins : int Number of histogram bins. cmap : matplotlib colormap, optional Colormap passed to ``Axes.hist2d``. psf_kwargs : dict, optional Keyword arguments passed to ``matplotlib.patches.Circle``. telescope_name : str, optional Telescope name shown in the annotation box. Returns ------- matplotlib.figure.Figure Created figure. """ unit_str = psf.unit.to_string() psf_fmt = ".4f" if psf.unit == u.deg else ".2f" fig, _ = plt.subplots(figsize=(8, 6), tight_layout=True) _, ax = create_psf_image_figure( data, containment_radius=containment_radius.to(psf.unit).value, center=(0, 0), use_current_axes=True, image_range=image_range, bins=bins, cmap=cmap, rasterized=True, psf_kwargs=psf_kwargs, ) ax.set_xlabel(f"X Position ({unit_str})") ax.set_ylabel(f"Y Position ({unit_str})") offset_text = f"Offset: ({off_x:+.2f} deg, {off_y:+.2f} deg)" psf_text = f"PSF: {psf.value:{psf_fmt}} {unit_str}" text = "\n".join(filter(None, [telescope_name, offset_text, psf_text])) ax.text( 0.02, 0.98, text, transform=ax.transAxes, verticalalignment="top", bbox={"boxstyle": "round", "facecolor": "white", "alpha": 0.8}, fontsize=10, family="monospace", ) return fig
[docs] def save_and_close_figure(fig, file_name): """Save a figure to an exact path and close it.""" file_name = Path(file_name) visualize.save_figure( fig, file_name.with_suffix(""), figure_format=[file_name.suffix[1:]], close=True )