import sys
from typing import Optional
# Check for Python >= 3.12, "# pragma: no cover" tells coverage to
# ignore these lines as the number of accessed lines will be different
# for different Python versions
if sys.version_info >= (3, 12): # pragma: no cover
from typing import Unpack
else: # pragma: no cover
try: # pragma: no cover
from typing_extensions import Unpack
except ImportError: # pragma: no cover
pass
import copy
import lenstronomy.Util.util as util
from lenstronomy.Util import class_creator
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
from lenstronomy.LensModel.lens_model_extensions import LensModelExtensions
from lenstronomy.Data.coord_transforms import Coordinates
from lenstronomy.Plots import plot_util
__all__ = ["TracerPlot"]
[docs]
class TracerPlot(object):
"""Class to plot a single band given the modeling results."""
[docs]
def __init__(
self,
kwargs_data_joint,
kwargs_model,
kwargs_params,
kwargs_likelihood,
fast_caustic=True,
):
"""Initialize the tracer plotting class.
:param kwargs_data_joint: joint data keyword argument list
:type kwargs_data_joint: dict
:param kwargs_model: model keyword argument list for the full multi-band
modeling
:type kwargs_model: dict
:param kwargs_params: keyword argument of keyword argument lists of the
different model components selected for the imaging band, NOT including
linear amplitudes (not required as being overwritten by the param list)
:type kwargs_params: dict
:param kwargs_likelihood: likelihood keyword arguments
:type kwargs_likelihood: dict or None
:param fast_caustic: ; if True, uses fast (but less accurate) caustic
calculation method
:type fast_caustic: bool
"""
multi_band_list = kwargs_data_joint.get("multi_band_list", [])
multi_band_type = kwargs_data_joint.get("multi_band_type", "single-band")
bands_compute = kwargs_data_joint.get("bands_compute", None)
image_likelihood_mask_list = kwargs_likelihood.get(
"image_likelihood_mask_list", None
)
self._tracer_light_model_band = kwargs_model.get("tracer_source_band", 0)
self.image_model = class_creator.create_im_sim(
multi_band_list,
multi_band_type,
kwargs_model,
bands_compute=bands_compute,
image_likelihood_mask_list=None,
band_index=0,
kwargs_pixelbased=None,
linear_solver=True,
)
tracer_likelihood_mask = kwargs_likelihood.get("tracer_likelihood_mask", None)
tracer_data = kwargs_data_joint.get("tracer_data", None)
self.tracerModel = class_creator.create_tracer_model(
tracer_data, kwargs_model, tracer_likelihood_mask=tracer_likelihood_mask
)
tracer_likelihood_mask = self.tracerModel.likelihood_mask
kwargs_params_copy = copy.deepcopy(kwargs_params)
kwargs_params_copy.pop("kwargs_tracer_source", None)
logL, param = self.image_model.likelihood_data_given_model(**kwargs_params_copy)
(
self._kwargs_lens,
self._kwargs_source,
self._kwargs_lens_light,
self._kwargs_ps,
) = self.image_model.update_linear_kwargs(
param,
model_band=self._tracer_light_model_band,
kwargs_lens=kwargs_params.get("kwargs_lens", None),
kwargs_source=kwargs_params.get("kwargs_source", None),
kwargs_lens_light=kwargs_params.get("kwargs_lens_light", None),
kwargs_ps=kwargs_params.get("kwargs_ps", None),
)
self._kwargs_tracer_source = kwargs_params["kwargs_tracer_source"]
self._kwargs_special = kwargs_params.get("kwargs_special", None)
self._kwargs_extinction = kwargs_params.get("kwargs_extinction", None)
self.tracerModel = class_creator.create_tracer_model(
tracer_data, kwargs_model, tracer_likelihood_mask=tracer_likelihood_mask
)
self._coords = self.tracerModel.Data
self._data = self._coords.data
self._model = self.tracerModel.tracer_model(
kwargs_tracer_source=self._kwargs_tracer_source,
kwargs_lens=self._kwargs_lens,
kwargs_source=self._kwargs_source,
kwargs_special=self._kwargs_special,
kwargs_extinction=self._kwargs_extinction,
)
C_D = self._coords.C_D_model(self._model)
self._norm_residuals = (
(self._data - self._model) / np.sqrt(C_D) * tracer_likelihood_mask
)
self.LensModel = self.tracerModel.LensModel
self._lensModelExt = LensModelExtensions(self.LensModel)
self.PointSource = self.tracerModel.PointSource
log_model = np.log10(self._model)
log_model[np.isnan(log_model)] = -5
self._vmin_default = np.nanpercentile(log_model, 1)
self._vmax_default = np.nanpercentile(log_model, 99)
self._data = self._coords.data
self._delta_pix = self._coords.pixel_width
self._frame_size = np.max(self._coords.width)
x_grid, y_grid = self._coords.pixel_coordinates
self._x_grid = util.image2array(x_grid)
self._y_grid = util.image2array(y_grid)
self._x_center, self._y_center = self._coords.center
self._fast_caustic = fast_caustic
self._font_size = 15
@property
def font_size(self):
"""Default font size for all texts in the subplots.
Font size in individual subplots can be adjusted by font_size argument in the
plotting methods. Font size for different text elements can be further fine-
tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and
kwargs_coordinate_arrows arguments in the plotting methods.
"""
return self._font_size
@font_size.setter
def font_size(self, value):
"""Set default font size for all texts in the subplots.
Font size in individual subplots can be adjusted by font_size argument in the
plotting methods. Font size for different text elements can be further fine-
tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and
kwargs_coordinate_arrows arguments in the plotting methods.
"""
self._font_size = value
def _critical_curves(self):
"""Compute and cache critical curves."""
if not hasattr(self, "_ra_crit_list") or not hasattr(self, "_dec_crit_list"):
if self._fast_caustic:
(
self._ra_crit_list,
self._dec_crit_list,
self._ra_caustic_list,
self._dec_caustic_list,
) = self._lensModelExt.critical_curve_caustics(
self._kwargs_lens,
compute_window=self._frame_size,
grid_scale=self._delta_pix,
center_x=self._x_center,
center_y=self._y_center,
)
self._caustic_points_only = False
else:
# only supports individual points due to output of critical_curve_tiling definition
self._caustic_points_only = True
(
self._ra_crit_list,
self._dec_crit_list,
) = self._lensModelExt.critical_curve_tiling(
self._kwargs_lens,
compute_window=self._frame_size,
start_scale=self._delta_pix / 5.0,
max_order=10,
center_x=self._x_center,
center_y=self._y_center,
)
(
self._ra_caustic_list,
self._dec_caustic_list,
) = self.LensModel.ray_shooting(
self._ra_crit_list, self._dec_crit_list, self._kwargs_lens
)
return self._ra_crit_list, self._dec_crit_list
def _caustics(self):
"""Compute and cache caustics."""
if not hasattr(self, "_ra_caustic_list") or not hasattr(
self, "_dec_caustic_list"
):
_, _ = self._critical_curves()
return self._ra_caustic_list, self._dec_caustic_list
[docs]
def data_plot(
self,
ax,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot observed tracer data.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "cubehelix")
vmin = kwargs_matshow.pop("vmin", self._vmin_default)
vmax = kwargs_matshow.pop("vmax", self._vmax_default)
im = ax.matshow(
np.log10(self._data),
origin="lower",
extent=[0, self._frame_size, 0, self._frame_size],
vmin=vmin,
vmax=vmax,
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "w")
kwargs_scale_bar.setdefault("font_size", 15)
kwargs_scale_bar.setdefault("linewidth", 2)
plot_util.show_scale_bar(ax, self._frame_size, **kwargs_scale_bar)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Observed")
kwargs_title.setdefault("color", "w")
kwargs_title.setdefault("backgroundcolor", "k")
kwargs_title.setdefault("font_size", 15)
plot_util.show_title_text(ax, **kwargs_title)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "w")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "w")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_colorbar is not None:
kwargs_colorbar = dict(kwargs_colorbar)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax, orientation="vertical")
kwargs_colorbar.setdefault("label", r"log$_{10}$ flux")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
return ax
[docs]
def model_plot(
self,
ax,
image_names=False,
original_position=True,
image_name_list=None,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot reconstructed tracer model.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param image_names: If True, prints image names
:type image_names: bool
:param label: Label for the colorbar
:type label: str
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param original_position: If True, uses original image positions
:type original_position: bool
:param image_name_list: Names for images
:type image_name_list: list
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "cubehelix")
vmin = kwargs_matshow.pop("vmin", self._vmin_default)
vmax = kwargs_matshow.pop("vmax", self._vmax_default)
im = ax.matshow(
np.log10(self._model),
origin="lower",
vmin=vmin,
vmax=vmax,
extent=[0, self._frame_size, 0, self._frame_size],
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "w")
kwargs_scale_bar.setdefault("font_size", 15)
kwargs_scale_bar.setdefault("linewidth", 2)
plot_util.show_scale_bar(ax, self._frame_size, **kwargs_scale_bar)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Reconstructed")
kwargs_title.setdefault("color", "w")
plot_util.show_title_text(ax, **kwargs_title)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "w")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "w")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_colorbar is not None:
kwargs_colorbar = dict(kwargs_colorbar)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"log$_{10}$ flux")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
# plot_line_set(ax, self._coords, self._ra_caustic_list, self._dec_caustic_list, color='b')
# plot_line_set(ax, self._coords, self._ra_crit_list, self._dec_crit_list, color='r')
if image_names is True:
ra_image, dec_image = self.PointSource.image_position(
self._kwargs_ps,
self._kwargs_lens,
)
plot_util.image_position_plot(
ax,
self._coords,
ra_image,
dec_image,
image_name_list=image_name_list,
)
# source_position_plot(ax, self._coords, self._kwargs_source)
[docs]
def convergence_plot(
self,
ax,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot lensing convergence in the tracer frame.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param label: Label for the colorbar
:type label: str
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: convergence plot in ax instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "gist_heat")
v_min = kwargs_matshow.pop("vmin", None)
v_max = kwargs_matshow.pop("vmax", None)
kappa_result = util.array2image(
self.LensModel.kappa(self._x_grid, self._y_grid, self._kwargs_lens)
)
im = ax.matshow(
np.log10(kappa_result),
origin="lower",
extent=[0, self._frame_size, 0, self._frame_size],
vmin=v_min,
vmax=v_max,
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "w")
kwargs_scale_bar.setdefault("font_size", 15)
kwargs_scale_bar.setdefault("linewidth", 2)
plot_util.show_scale_bar(ax, self._frame_size, **kwargs_scale_bar)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "w")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "w")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Convergence")
kwargs_title.setdefault("color", "w")
kwargs_title.setdefault("backgroundcolor", "k")
kwargs_title.setdefault("font_size", 15)
plot_util.show_title_text(ax, **kwargs_title)
if kwargs_colorbar is not None:
kwargs_colorbar = dict(kwargs_colorbar)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"$\log_{10}\ \kappa$")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
return ax
[docs]
def normalized_residual_plot(
self,
ax,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot normalized residuals between data and model.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param label: label for the color bar
:type label: str
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "RdBu_r")
v_min = kwargs_matshow.pop("vmin", -5)
v_max = kwargs_matshow.pop("vmax", 5)
im = ax.matshow(
self._norm_residuals,
vmin=v_min,
vmax=v_max,
extent=[0, self._frame_size, 0, self._frame_size],
origin="lower",
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "k")
kwargs_scale_bar.setdefault("font_size", 15)
kwargs_scale_bar.setdefault("linewidth", 2)
plot_util.show_scale_bar(ax, self._frame_size, **kwargs_scale_bar)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Normalized Residuals")
kwargs_title.setdefault("color", "k")
kwargs_title.setdefault("backgroundcolor", "w")
kwargs_title.setdefault("font_size", 15)
plot_util.show_title_text(ax, **kwargs_title)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "k")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "k")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_colorbar is not None:
kwargs_colorbar = dict(kwargs_colorbar)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault(
"label", r"(f$_{\rm model}$ - f$_{\rm data}$)/$\sigma$"
)
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
return ax
[docs]
def absolute_residual_plot(
self,
ax,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot absolute residuals between data and model.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param label: label for the color bar
:type label: str
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "RdBu_r")
v_min = kwargs_matshow.pop("vmin", -1)
v_max = kwargs_matshow.pop("vmax", 1)
im = ax.matshow(
self._model - self._data,
vmin=v_min,
vmax=v_max,
extent=[0, self._frame_size, 0, self._frame_size],
origin="lower",
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "k")
kwargs_scale_bar.setdefault("font_size", 15)
kwargs_scale_bar.setdefault("linewidth", 2)
plot_util.show_scale_bar(ax, self._frame_size, **kwargs_scale_bar)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Residuals")
kwargs_title.setdefault("color", "k")
kwargs_title.setdefault("backgroundcolor", "w")
kwargs_title.setdefault("font_size", 15)
plot_util.show_title_text(ax, **kwargs_title)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "k")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "k")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_colorbar is not None:
kwargs_colorbar = dict(kwargs_colorbar)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"(f$_{model}$-f$_{data}$)")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
return ax
[docs]
def source(self, num_pix, delta_pix, center=None, image_orientation=True):
"""Compute tracer source surface brightness on a source grid.
:param num_pix: number of pixels per axes
:type num_pix: int
:param delta_pix: pixel size
:type delta_pix: float
:param image_orientation: If True, uses frame in orientation of the image,
:type image_orientation: bool otherwise in RA-DEC coordinates
:return: 2d surface brightness grid of the reconstructed source and
Coordinates() instance of source grid
"""
if image_orientation is True:
transform_pix2coord = (
self._coords.transform_pix2angle * delta_pix / self._delta_pix
)
x_grid_source, y_grid_source = util.make_grid_transformed(
num_pix, transform_pix2angle=transform_pix2coord
)
ra_at_xy_0, dec_at_xy_0 = x_grid_source[0], y_grid_source[0]
else:
(
x_grid_source,
y_grid_source,
ra_at_xy_0,
dec_at_xy_0,
x_at_radec_0,
y_at_radec_0,
transform_pix2coord,
transform_coord2pix,
) = util.make_grid_with_coordtransform(num_pix, delta_pix)
center_x = 0
center_y = 0
if center is not None:
center_x, center_y = center[0], center[1]
elif len(self._kwargs_tracer_source) > 0:
center_x = self._kwargs_tracer_source[0]["center_x"]
center_y = self._kwargs_tracer_source[0]["center_y"]
x_grid_source += center_x
y_grid_source += center_y
coords_source = Coordinates(
transform_pix2angle=transform_pix2coord,
ra_at_xy_0=ra_at_xy_0 + center_x,
dec_at_xy_0=dec_at_xy_0 + center_y,
)
source = self.tracerModel.tracer_source_class.surface_brightness(
x_grid_source, y_grid_source, self._kwargs_tracer_source
)
source = util.array2image(source)
return source, coords_source
[docs]
def source_plot(
self,
ax,
num_pix,
delta_pix_source,
center=None,
font_size=None,
plot_scale="log",
point_source_position=True,
kwargs_caustics: Optional[plot_util.CausticKwargs] = {},
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot reconstructed tracer source brightness.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param num_pix: number of pixels in plot per axis
:type num_pix: int
:param delta_pix_source: pixel spacing in the source resolution illustrated in
plot
:type delta_pix_source: float
:param center: [center_x, center_y], if specified, uses this as the center
:type center: list or None
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param plot_scale: Log or linear, scale of surface brightness plot
:type plot_scale: str
:param label: Label for the colorbar
:type label: str
:param point_source_position: If True, plots a point at the position of
the point source
:type point_source_position: bool
:param kwargs_caustics: keyword arguments for caustic plotting, see :class:`~lenstronomy.Plots.plot_util.CausticKwargs`. Set to None to exclude this element from the plot. Set to None to exclude this element from the plot.
:type kwargs_caustics: dict
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
d_s = num_pix * delta_pix_source
source, coords_source = self.source(num_pix, delta_pix_source, center=center)
if plot_scale == "log":
kwargs_matshow.setdefault("vmin", self._vmin_default)
kwargs_matshow.setdefault("vmax", self._vmax_default)
v_min = kwargs_matshow.get("vmin", self._vmin_default)
source[source < 10**v_min] = 10 ** (v_min) # to remove weird shadow in plot
source_scale = np.log10(source)
elif plot_scale == "linear":
source_scale = source
else:
raise ValueError(
'variable plot_scale needs to be "log" or "linear", not %s.'
% plot_scale
)
kwargs_matshow.setdefault("cmap", "cubehelix")
im = ax.matshow(
source_scale,
origin="lower",
extent=[0, d_s, 0, d_s],
**kwargs_matshow,
) # source
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"tracer")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
if kwargs_caustics is not None:
kwargs_caustics = dict(kwargs_caustics)
kwargs_caustics.setdefault("color", "k")
ra_caustic_list, dec_caustic_list = self._caustics()
plot_util.plot_line_set(
ax,
coords_source,
ra_caustic_list,
dec_caustic_list,
points_only=self._caustic_points_only,
**kwargs_caustics,
)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 0.1)
kwargs_scale_bar.setdefault("color", "w")
if kwargs_scale_bar.get("scale_size", 0) > 0:
plot_util.show_scale_bar(
ax,
d_s,
**kwargs_scale_bar,
)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "w")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "w")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Reconstructed source")
kwargs_title.setdefault("flipped", False)
kwargs_title.setdefault("font_size", font_size)
kwargs_title.setdefault("color", "w")
kwargs_title.setdefault("backgroundcolor", "k")
plot_util.show_title_text(
ax,
**kwargs_title,
)
if point_source_position is True:
ra_source, dec_source = self.PointSource.source_position(
self._kwargs_ps, self._kwargs_lens
)
plot_util.source_position_plot(ax, coords_source, ra_source, dec_source)
return ax
[docs]
def magnification_plot(
self,
ax,
image_name_list=None,
font_size=None,
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot magnification map in the tracer frame.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param image_name_list: Strings for names of the images in the same
order as the positions
:type image_name_list: list
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param label: Label for the colorbar
:type label: str
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
kwargs_matshow.setdefault("cmap", "RdYlBu_r")
kwargs_matshow.setdefault("vmin", -10)
kwargs_matshow.setdefault("vmax", 10)
kwargs_matshow.setdefault("alpha", 0.5)
mag_result = util.array2image(
self.LensModel.magnification(self._x_grid, self._y_grid, self._kwargs_lens)
)
im = ax.matshow(
mag_result,
origin="lower",
extent=[0, self._frame_size, 0, self._frame_size],
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "k")
if kwargs_scale_bar.get("scale_size", 0) > 0:
plot_util.show_scale_bar(
ax,
self._frame_size,
**kwargs_scale_bar,
)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "k")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "k")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Magnification model")
kwargs_title.setdefault("color", "k")
kwargs_title.setdefault("backgroundcolor", "w")
kwargs_title.setdefault("font_size", font_size)
plot_util.show_title_text(
ax,
**kwargs_title,
)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"$\det\ (\mathsf{A}^{-1})$")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
ra_image, dec_image = self.PointSource.image_position(
self._kwargs_ps, self._kwargs_lens
)
plot_util.image_position_plot(
ax,
self._coords,
ra_image,
dec_image,
color="k",
image_name_list=image_name_list,
)
return ax
[docs]
def deflection_plot(
self,
ax,
axis=0,
image_name_list=None,
font_size=None,
kwargs_caustics: Optional[plot_util.CausticCriticalKwargs] = {},
kwargs_colorbar: Optional[plot_util.ColorBarKwargs] = {},
kwargs_title: Optional[plot_util.TitleKwargs] = {},
kwargs_scale_bar: Optional[plot_util.ScaleBarKwargs] = {},
kwargs_coordinate_arrows: Optional[plot_util.CoordArrowKwargs] = {},
**kwargs_matshow: "Unpack[plot_util.MatshowKwargs]",
):
"""Plot deflection-angle map in the tracer frame.
:param ax: Matplotlib axes instance
:type ax: matplotlib.axes.Axes
:param axis: 0 or 1, specifies the deflection angle axis to be plotted
:type axis: int
:param image_name_list: Strings for names of the images
:type image_name_list: list
:param font_size: Font size to override the class-level default. Font size for different text elements can be further fine-tuned by kwargs_colorbar, kwargs_title, kwargs_scale_bar, and kwargs_coordinate_arrows arguments in the plotting methods.
:type font_size: int
:param label: Label for the colorbar
:type label: str
:param kwargs_title: keyword arguments for the title, see :class:`~lenstronomy.Plots.plot_util.TitleKwargs`. Set to None to exclude this element from the plot.
:type kwargs_title: dict
:param kwargs_scale_bar: keyword arguments for the scale bar, see :class:`~lenstronomy.Plots.plot_util.ScaleBarKwargs`. Set to None to exclude this element from the plot.
:type kwargs_scale_bar: dict
:param kwargs_coordinate_arrows: keyword arguments for coordinate arrows, see :class:`~lenstronomy.Plots.plot_util.CoordArrowKwargs`. Set to None to exclude this element from the plot.
:type kwargs_coordinate_arrows: dict
:param kwargs_matshow: keyword arguments passed to :func:`matplotlib.pyplot.matshow`
:type kwargs_matshow: dict
:return: matplotlib axis instance
"""
if font_size is None:
font_size = self._font_size
alpha1, alpha2 = self.LensModel.alpha(
self._x_grid, self._y_grid, self._kwargs_lens
)
alpha1 = util.array2image(alpha1)
alpha2 = util.array2image(alpha2)
if axis == 0:
alpha = alpha1
else:
alpha = alpha2
kwargs_matshow.setdefault("cmap", "RdYlBu_r")
kwargs_matshow.setdefault("alpha", 0.5)
im = ax.matshow(
alpha,
origin="lower",
extent=[0, self._frame_size, 0, self._frame_size],
**kwargs_matshow,
)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.autoscale(False)
if kwargs_scale_bar is not None:
kwargs_scale_bar = dict(kwargs_scale_bar)
kwargs_scale_bar.setdefault("scale_size", 1.0)
kwargs_scale_bar.setdefault("color", "k")
if kwargs_scale_bar.get("scale_size", 0) > 0:
plot_util.show_scale_bar(
ax,
self._frame_size,
**kwargs_scale_bar,
)
if kwargs_coordinate_arrows is not None:
kwargs_coordinate_arrows = dict(kwargs_coordinate_arrows)
kwargs_coordinate_arrows.setdefault("font_size", font_size)
kwargs_coordinate_arrows.setdefault("arrow_color_north", "k")
kwargs_coordinate_arrows.setdefault("arrow_color_east", "k")
plot_util.show_coordinate_arrows(
ax,
self._frame_size,
self._coords,
**kwargs_coordinate_arrows,
)
if kwargs_title is not None:
kwargs_title = dict(kwargs_title)
kwargs_title.setdefault("text", "Deflection model")
kwargs_title.setdefault("color", "k")
kwargs_title.setdefault("backgroundcolor", "w")
kwargs_title.setdefault("font_size", font_size)
plot_util.show_title_text(
ax,
**kwargs_title,
)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cb = plt.colorbar(im, cax=cax)
kwargs_colorbar.setdefault("label", r"arcsec")
plot_util.show_colorbar(
cb,
font_size=font_size,
**kwargs_colorbar,
)
if kwargs_caustics is not None:
kwargs_caustics = dict(kwargs_caustics)
kwargs_caustics.setdefault("color", "b")
critical_curve_color = kwargs_caustics.pop("critical_curve_color", "r")
ra_crit_list, dec_crit_list = self._critical_curves()
ra_caustic_list, dec_caustic_list = self._caustics()
plot_util.plot_line_set(
ax,
self._coords,
ra_caustic_list,
dec_caustic_list,
points_only=self._caustic_points_only,
**kwargs_caustics,
)
kwargs_caustics.setdefault("color", critical_curve_color)
plot_util.plot_line_set(
ax,
self._coords,
ra_crit_list,
dec_crit_list,
points_only=self._caustic_points_only,
**kwargs_caustics,
)
ra_image, dec_image = self.PointSource.image_position(
self._kwargs_ps, self._kwargs_lens
)
plot_util.image_position_plot(
ax, self._coords, ra_image, dec_image, image_name_list=image_name_list
)
return ax
[docs]
def plot_main(self, kwargs_caustics: Optional[plot_util.CausticKwargs] = None):
"""Print the main plots together in a joint frame.
:kwargs_caustics: keyword arguments for caustic plotting, see :class:`~lenstronomy.Plots.plot_util.CausticKwargs`. Set to None to exclude this element from the plot.
:return:
"""
f, axes = plt.subplots(2, 3, figsize=(16, 8))
self.data_plot(ax=axes[0, 0])
self.model_plot(ax=axes[0, 1], image_names=True)
self.normalized_residual_plot(ax=axes[0, 2])
self.source_plot(
ax=axes[1, 0],
delta_pix_source=0.01,
num_pix=100,
kwargs_caustics=kwargs_caustics,
)
self.convergence_plot(ax=axes[1, 1])
self.magnification_plot(ax=axes[1, 2])
f.tight_layout()
f.subplots_adjust(
left=None, bottom=None, right=None, top=None, wspace=0.0, hspace=0.05
)
return f, axes