from typing import List, Tuple, Optional, Dict, Any
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np
from matplotlib.patches import Polygon, Rectangle, ConnectionPatch
from qumas.MicrolensingMaps.micro_maps_tracks_func import _sample_profile_along_line_pixels,_sample_profile_along_line_bilinear_pixels
[docs]
def map_plot(mag_map_2d, cmap='RdYlBu', vmin=-1.5, vmax=0.5,
cbar_side="right",
remove_labels=True,
cbar_label=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
save="", **kwargs):
text = kwargs.get("text")
fig, ax = plt.subplots(1, 1, figsize=(20, 10))
# Plot image
im = ax.imshow(mag_map_2d, cmap=cmap, extent=[-2, 2, -2, 2],
vmin=vmin, vmax=vmax)
# Create divider attached to the main axis
divider = make_axes_locatable(ax)
# Append colorbar axis on chosen side
cax = divider.append_axes(cbar_side, size="6%", pad=0.35)
# Create colorbar with extend="min"
cbar = fig.colorbar(im, cax=cax, extend="min")
# Tick + label placement consistent with side
cbar.ax.tick_params(labelsize=30)
cbar.set_label(cbar_label, fontsize=30)
cbar.ax.yaxis.set_label_position(cbar_side)
cbar.ax.yaxis.set_ticks_position(cbar_side)
# Optional annotation inside main axis
if text:
ax.text(0.05, 0.95, text, transform=ax.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
# Axis labels/ticks
if remove_labels:
ax.set_xticks([])
ax.set_yticks([])
else:
ax.set_xlabel("x [pixels]", fontsize=30)
ax.set_ylabel("y [pixels]", fontsize=30)
ax.tick_params(axis='both', which='major', labelsize=10)
if save:
plt.savefig(f"{save}.jpg", bbox_inches='tight')
#plt.show()
[docs]
def map_pmf_plot(mag_map,label="",label_color_bar =r"$-2.5 \, \log(\mu/\langle \mu \rangle)$", vmin=-1.5, vmax=0.5,text_right_plot="Right plot",bins_limit=3,
num_bins=100,cmap='RdYlBu',**kwargs):
#rf"{name} {component} $\ast$ {factor} rs $\alpha = {alpha.split('_')[1]}$ mean={mean:.3f}"
name_file = kwargs.get("name_file", None)
fig, (ax2, ax1) = plt.subplots(1, 2, figsize=(27, 10))
bins = np.linspace(-bins_limit, bins_limit, num_bins + 1)
counts, bin_edges = np.histogram(mag_map.ravel(), bins=bins)
pmf = counts / counts.sum()
pmf_plos = np.r_[pmf, pmf[-1]]
mean_p = np.sum(bin_edges * pmf_plos)
bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
expected_pmf = sum(pmf * bin_centers)
im1 = ax1.step(bin_edges,pmf_plos, alpha=0.7, linestyle="-", label=label,c="C0")
ax1.axvline(expected_pmf,c="k",ls="--",label="Expected PMF")
ax1.axvline(mean_p,c="r",ls="--",label="Mean")
ax1.legend(loc="best", fontsize=12)
ax1.legend(loc="best", fontsize=12)
ax1.set_xlabel(r"$-2.5 \, \log(\mu/\langle \mu \rangle)$", fontsize=30)
ax1.set_ylabel('Density(PMF)', fontsize=30)
ax1.tick_params(axis='both', which='major', labelsize=20)
ax1.set_xlim(-bins_limit,bins_limit)
im2 = ax2.imshow(mag_map, cmap=cmap,extent=[-2, 2, -2, 2], vmin=vmin, vmax=vmax)
ax2.set_xticks([])
ax2.set_yticks([])
ax2.text(0.05, 0.95, text_right_plot, transform=ax2.transAxes, fontsize=30, fontweight='bold', va='top', ha='left',bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
# Create a colorbar for the mirrored plot (positioned on the left side)
cbar2 = fig.colorbar(im2, ax=ax2, fraction=0.046, pad=0.04)
cbar2.ax.tick_params(labelsize=30)
cbar2.ax.set_position([ax2.get_position().x0-0.1*ax2.get_position().x0, 0.15, 0.02, 0.73])
cbar2.ax.yaxis.set_ticks_position('left')
cbar2.ax.yaxis.set_label_position('left')
cbar2.set_label(label_color_bar, fontsize=30, labelpad=20)
trixy = np.array([[0, 0], [1, 0], [0.5, -0.05]])
bluest_color = im2.cmap(im2.norm(vmin))
patch2 = Polygon(trixy, transform=cbar2.ax.transAxes, clip_on=False,edgecolor='k', linewidth=0.7, facecolor=bluest_color,zorder=4, snap=True)
cbar2.ax.add_patch(patch2)
if name_file:
plt.savefig(f"{name_file}.pdf", bbox_inches='tight')
plt.show()
[docs]
def compare_maps_plot(
mag_map_left, mag_map_right,
vmin=-1.5, vmax=0.5,
label_color_bar=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
cmap='RdYlBu',
plot_sep=0.05, # <--- NEW hyperparameter for horizontal spacing
**kwargs
):
"""
Compare two magnification maps side by side with auto-sized colorbars,
placed using make_axes_locatable and showing an 'under' triangle via extend='min'.
Parameters
----------
plot_sep : float, default=0.05
Horizontal separation between the left and right plots (wspace in subplots_adjust).
Notes
-----
The code assumes that the two maps are normalized, but if this is not
the case, you can change `label_color_bar`.
"""
text_left_plot = kwargs.get("text_left_plot", "Left plot")
text_right_plot = kwargs.get("text_right_plot", "Right plot")
name_file = kwargs.get("name_file", None)
# Optional tweaks for the colorbar layout
cbar_size = kwargs.get("cbar_size", "6%") # width of the colorbar relative to axes bbox
cbar_pad = kwargs.get("cbar_pad", 0.25) # gap between axes and colorbar, in inches
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
plt.subplots_adjust(wspace=plot_sep) # <-- use hyperparameter here
# --- Left image ---
im1 = ax1.imshow(mag_map_left, cmap=cmap, extent=[-2, 2, -2, 2],
vmin=vmin, vmax=vmax)
vmin_locked, vmax_locked = im1.get_clim()
ax1.set_xticks([])
ax1.set_yticks([])
ax1.text(0.05, 0.95, text_left_plot, transform=ax1.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
# Colorbar on the LEFT of ax1
div1 = make_axes_locatable(ax1)
cax1 = div1.append_axes("left", size=cbar_size, pad=cbar_pad)
cbar1 = fig.colorbar(im1, cax=cax1, extend="min", orientation="vertical")
cbar1.ax.tick_params(labelsize=30)
cbar1.set_label(label_color_bar, fontsize=30)
cbar1.ax.yaxis.set_ticks_position("left")
cbar1.ax.yaxis.set_label_position("left")
# --- Right image ---
im2 = ax2.imshow(mag_map_right, cmap=cmap, extent=[-2, 2, -2, 2],
vmin=vmin_locked, vmax=vmax_locked)
ax2.set_xticks([])
ax2.set_yticks([])
ax2.text(0.05, 0.95, text_right_plot, transform=ax2.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
# Colorbar on the RIGHT of ax2
div2 = make_axes_locatable(ax2)
cax2 = div2.append_axes("right", size=cbar_size, pad=cbar_pad)
cbar2 = fig.colorbar(im2, cax=cax2, extend="min", orientation="vertical")
cbar2.ax.tick_params(labelsize=30)
cbar2.set_label(label_color_bar, fontsize=30)
cbar2.ax.yaxis.set_ticks_position("right")
cbar2.ax.yaxis.set_label_position("right")
if name_file:
plt.savefig(f"{name_file}.pdf", bbox_inches='tight')
plt.show()
[docs]
def map_plot_with_zoom(
mag_map_2d,
cmap='RdYlBu',
vmin=-1.5, vmax=0.5,
cbar_side="right",
remove_labels=True,
cbar_label=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
save="",
*,
# Zoom controls
extent=(-2, 2, -2, 2),
zoom_box=None,
zoom_center=None,
zoom_size=(1.0, 1.0),
zoom_axes_position="right",
share_colorbar=True,
connect_boxes=True,
zoom_label="Zoom",
main_label=None,
label_fontsize=30,
fig_size=(20, 10),
wspace=0.08, hspace=0.1,
interpolation="nearest"
):
"""
Show the full map and an enlarged view of a selected box.
Colorbars are placed with make_axes_locatable so their height
matches the axes exactly.
"""
if zoom_box is not None:
(x0, x1), (y0, y1) = zoom_box
else:
xc, yc = zoom_center if zoom_center is not None else (0.5*(extent[0]+extent[1])+0.75, 0.5*(extent[2]+extent[3]))
w, h = zoom_size
x0, x1 = xc - 0.5*w, xc + 0.5*w
y0, y1 = yc - 0.5*h, yc + 0.5*h
if zoom_axes_position == "bottom":
fig, (ax_main, ax_zoom) = plt.subplots(2, 1, figsize=fig_size)
plt.subplots_adjust(hspace=hspace)
else:
fig, (ax_main, ax_zoom) = plt.subplots(1, 2, figsize=fig_size)
plt.subplots_adjust(wspace=wspace)
im_main = ax_main.imshow(
mag_map_2d, cmap=cmap, extent=extent,
vmin=vmin, vmax=vmax,
interpolation=interpolation, origin="upper"
)
rect = Rectangle((x0, y0), x1-x0, y1-y0,fill=False, edgecolor="k", linewidth=2)
ax_main.add_patch(rect)
if remove_labels:
ax_main.set_xticks([]); ax_main.set_yticks([])
else:
ax_main.set_xlabel("x [pixels]", fontsize=label_fontsize)
ax_main.set_ylabel("y [pixels]", fontsize=label_fontsize)
if main_label:
ax_main.text(0.05, 0.95, main_label, transform=ax_main.transAxes,
fontsize=label_fontsize, fontweight='bold',
va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
im_zoom = ax_zoom.imshow(mag_map_2d, cmap=cmap, extent=extent,vmin=vmin, vmax=vmax,interpolation=interpolation, origin="upper",aspect="auto")
ax_zoom.set_xlim(x0, x1); ax_zoom.set_ylim(y0, y1)
if remove_labels:
ax_zoom.set_xticks([]); ax_zoom.set_yticks([])
else:
ax_zoom.set_xlabel("x [pixels]", fontsize=label_fontsize)
ax_zoom.set_ylabel("y [pixels]", fontsize=label_fontsize)
if zoom_label:
ax_zoom.text(0.05, 0.95, zoom_label, transform=ax_zoom.transAxes,
fontsize=label_fontsize, fontweight='bold',
va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
if connect_boxes and zoom_axes_position == "right":
for (cx, cy) in [(x0,y0),(x0,y1),(x1,y0),(x1,y1)]:
con = ConnectionPatch(xyA=(0,0), coordsA=ax_zoom.transAxes,
xyB=(cx,cy), coordsB=ax_main.transData,
linewidth=1.5, color="k", alpha=0.6)
fig.add_artist(con)
if share_colorbar:
div = make_axes_locatable(ax_zoom)
cax = div.append_axes(cbar_side, size="6%", pad=0.25)
cbar = fig.colorbar(im_zoom, cax=cax, extend="min", orientation="vertical")
cbar.ax.tick_params(labelsize=label_fontsize)
cbar.set_label(cbar_label, fontsize=label_fontsize)
cbar.ax.yaxis.set_ticks_position(cbar_side)
cbar.ax.yaxis.set_label_position(cbar_side)
else:
for ax, im in [(ax_main, im_main), (ax_zoom, im_zoom)]:
div = make_axes_locatable(ax)
cax = div.append_axes(cbar_side, size="6%", pad=0.25)
cbar = fig.colorbar(im, cax=cax, extend="min", orientation="vertical")
cbar.ax.tick_params(labelsize=label_fontsize)
cbar.set_label(cbar_label, fontsize=label_fontsize)
cbar.ax.yaxis.set_ticks_position(cbar_side)
cbar.ax.yaxis.set_label_position(cbar_side)
if save:
plt.savefig(f"{save}.pdf", bbox_inches='tight')
plt.show()
[docs]
def compare_maps_pmf(
mag_map_left, mag_map_right,
vmin=-1.5, vmax=0.5,
label_color_bar=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
bins_limit=3, num_bins=100,
cmap="RdYlBu",
plot_sep=0.2,
**kwargs
):
"""
Compare two magnification maps side by side with their Probability Mass Functions (PMF).
Parameters
----------
mag_map_left, mag_map_right : 2D arrays
Magnification maps to compare.
vmin, vmax : float, optional
Color scale limits for both maps. Defaults are -1.5 and 0.5.
label_color_bar : str, optional
Label for the colorbars.
bins_limit : float, optional
Range for PMF histogram edges (in log μ). Default = 3.
num_bins : int, optional
Number of histogram bins for PMF calculation. Default = 100.
cmap : str, optional
Colormap to use for the images. Default = 'RdYlBu'.
plot_sep : float, optional
Horizontal separation between panels (same as wspace in subplots_adjust).
Other Parameters
----------------
text_left_plot, text_right_plot : str, optional
Titles for each map panel.
cbar_size, cbar_pad : str or float, optional
Size and padding of the colorbars (used in make_axes_locatable).
save : str, optional
If provided, saves the figure to this path (with .pdf extension if not included).
"""
text_left_plot = kwargs.get("text_left_plot", "Left plot")
text_right_plot = kwargs.get("text_right_plot", "Right plot")
name_file = kwargs.get("save", None)
cbar_size = kwargs.get("cbar_size", "6%")
cbar_pad = kwargs.get("cbar_pad", 0.25)
# --- Create figure: 3 panels of equal width ---
fig, (ax1, ax3, ax2) = plt.subplots(
1, 3, figsize=(30, 10),
gridspec_kw={'width_ratios': [1, 1, 1]}
)
plt.subplots_adjust(wspace=plot_sep)
# --- Left map ---
im1 = ax1.imshow(mag_map_left, cmap=cmap, extent=[-2, 2, -2, 2],
vmin=vmin, vmax=vmax)
vmin_locked, vmax_locked = im1.get_clim()
ax1.set_xticks([]); ax1.set_yticks([])
ax1.text(0.05, 0.95, text_left_plot, transform=ax1.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
div1 = make_axes_locatable(ax1)
cax1 = div1.append_axes("left", size=cbar_size, pad=cbar_pad)
cbar1 = fig.colorbar(im1, cax=cax1, extend="min", orientation="vertical")
cbar1.ax.tick_params(labelsize=25)
cbar1.set_label(label_color_bar, fontsize=28)
cbar1.ax.yaxis.set_ticks_position("left")
cbar1.ax.yaxis.set_label_position("left")
# --- Right map ---
im2 = ax2.imshow(mag_map_right, cmap=cmap, extent=[-2, 2, -2, 2],
vmin=vmin_locked, vmax=vmax_locked)
ax2.set_xticks([]); ax2.set_yticks([])
ax2.text(0.05, 0.95, text_right_plot, transform=ax2.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
div2 = make_axes_locatable(ax2)
cax2 = div2.append_axes("right", size=cbar_size, pad=cbar_pad)
cbar2 = fig.colorbar(im2, cax=cax2, extend="min", orientation="vertical")
cbar2.ax.tick_params(labelsize=25)
cbar2.set_label(label_color_bar, fontsize=28)
cbar2.ax.yaxis.set_ticks_position("right")
cbar2.ax.yaxis.set_label_position("right")
# --- Middle PMF (equal width to maps) ---
for label, data, color in zip(
[text_left_plot, text_right_plot],
[mag_map_left, mag_map_right],
["C0", "C1"]
):
bins = np.linspace(-bins_limit, bins_limit, num_bins + 1)
counts, _ = np.histogram(data.ravel(), bins=bins)
pmf = counts / counts.sum()
bin_centers = 0.5 * (bins[:-1] + bins[1:])
ax3.step(bin_centers, pmf, where='mid', lw=3,
alpha=0.9, label=f"PMF ({label})", color=color)
ax3.legend(loc="best", fontsize=22)
ax3.set_xlabel(r"$-2.5 \, \log(\mu/\langle \mu \rangle)$", fontsize=30)
ax3.set_ylabel("Density (PMF)", fontsize=30)
ax3.tick_params(axis='both', which='major', labelsize=22)
ax3.set_xlim(-bins_limit, bins_limit)
ax3.grid(True, alpha=0.3)
# --- Optional save ---
if name_file:
#if not name_file.endswith(".pdf"):
# name_file += ".pdf"
plt.savefig(name_file, bbox_inches="tight")
plt.show()
[docs]
class CompareMapsPMF:
"""
Compare two magnification maps side by side with their Probability Mass Functions (PMF).
Example
-------
cmp = CompareMapsPMF()
cmp.plot(mag_map_left, mag_map_right, text_left_plot="Model A", text_right_plot="Model B")
"""
def __init__(self,
vmin=-1.5,
vmax=0.5,
label_color_bar=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
bins_limit=3,
num_bins=100,
cmap="RdYlBu",
plot_sep=0.2):
self.vmin = vmin
self.vmax = vmax
self.label_color_bar = label_color_bar
self.bins_limit = bins_limit
self.num_bins = num_bins
self.cmap = cmap
self.plot_sep = plot_sep
[docs]
def plot(self, mag_map_left, mag_map_right, **kwargs):
text_left_plot = kwargs.get("text_left_plot", "Left map")
text_right_plot = kwargs.get("text_right_plot", "Right map")
name_file = kwargs.get("save", None)
cbar_size = kwargs.get("cbar_size", "6%")
cbar_pad = kwargs.get("cbar_pad", 0.25)
# --- Create figure: 3 panels (map–PMF–map)
fig, (ax1, ax3, ax2) = plt.subplots(
1, 3, figsize=(30, 10),
gridspec_kw={'width_ratios': [1, 1, 1]}
)
plt.subplots_adjust(wspace=self.plot_sep)
# --- Left map ---
im1 = ax1.imshow(mag_map_left, cmap=self.cmap, extent=[-2, 2, -2, 2],
vmin=self.vmin, vmax=self.vmax)
vmin_locked, vmax_locked = im1.get_clim()
ax1.set_xticks([]); ax1.set_yticks([])
ax1.text(0.05, 0.95, text_left_plot, transform=ax1.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
div1 = make_axes_locatable(ax1)
cax1 = div1.append_axes("left", size=cbar_size, pad=cbar_pad)
cbar1 = fig.colorbar(im1, cax=cax1, extend="min", orientation="vertical")
cbar1.ax.tick_params(labelsize=25)
cbar1.set_label(self.label_color_bar, fontsize=28)
cbar1.ax.yaxis.set_ticks_position("left")
cbar1.ax.yaxis.set_label_position("left")
# --- Right map ---
im2 = ax2.imshow(mag_map_right, cmap=self.cmap, extent=[-2, 2, -2, 2],
vmin=vmin_locked, vmax=vmax_locked)
ax2.set_xticks([]); ax2.set_yticks([])
ax2.text(0.05, 0.95, text_right_plot, transform=ax2.transAxes,
fontsize=30, fontweight='bold', va='top', ha='left',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
div2 = make_axes_locatable(ax2)
cax2 = div2.append_axes("right", size=cbar_size, pad=cbar_pad)
cbar2 = fig.colorbar(im2, cax=cax2, extend="min", orientation="vertical")
cbar2.ax.tick_params(labelsize=25)
cbar2.set_label(self.label_color_bar, fontsize=28)
cbar2.ax.yaxis.set_ticks_position("right")
cbar2.ax.yaxis.set_label_position("right")
# --- Middle PMF (square aspect & same width) ---
for label, data, color in zip(
[text_left_plot, text_right_plot],
[mag_map_left, mag_map_right],
["C0", "C1"]
):
bins = np.linspace(-self.bins_limit, self.bins_limit, self.num_bins + 1)
counts, _ = np.histogram(data.ravel(), bins=bins)
pmf = counts / counts.sum()
bin_centers = 0.5 * (bins[:-1] + bins[1:])
ax3.step(bin_centers, pmf, where='mid', lw=3,
alpha=0.9, label=f"PMF ({label})", color=color)
# Make the PMF panel square and match map widths
div3 = make_axes_locatable(ax3)
cax3 = div3.append_axes("right", size=cbar_size, pad=cbar_pad)
cax3.set_visible(False)
ax3.set_box_aspect(1)
# Also ensure maps are square
ax1.set_box_aspect(1)
ax2.set_box_aspect(1)
ax3.legend(loc="best", fontsize=22)
ax3.set_xlabel(r"$-2.5 \, \log(\mu/\langle \mu \rangle)$", fontsize=30)
ax3.set_ylabel("Density (PMF)", fontsize=30)
ax3.tick_params(axis='both', which='major', labelsize=22)
ax3.set_xlim(-self.bins_limit, self.bins_limit)
ax3.grid(True, alpha=0.3)
# --- Optional save ---
if name_file:
plt.savefig(name_file, bbox_inches="tight")
plt.show()
[docs]
def map_plot_with_profiles(
mag_map_2d: np.ndarray,
p0: Optional[Tuple[float, float]] = None,
p1: Optional[Tuple[float, float]] = None,
*,
lines: Optional[List[Tuple[Tuple[float, float], Tuple[float, float]]]] = None,
labels: Optional[List[str]] = None,
line_kwargs: Optional[Dict[str, Any]] = None,
per_line_kwargs: Optional[List[Dict[str, Any]]] = None,
profile_mode: str = "pixels", # <- default now uses pixel-index x-axis
cmap='RdYlBu',
vmin=-1.5, vmax=0.5,
cbar_side="left",
remove_labels=True,
cbar_label=r"$-2.5 \, \log(\mu/\langle \mu \rangle)$",
pix_L = None,
veff = None,
extent=(-2, 2, -2, 2),
lines_pix = None,
save: str = ""
):
"""
Plot a 2D map and multiple line profiles on the right.
profile_mode:
- "bilinear_pixels": bilinear sampling, exactly one sample per crossed pixel; x = pixel index
- "pixels": discrete Bresenham pixels (nearest); x = pixel index
"""
if lines is None:
if p0 is None or p1 is None:
raise ValueError("Provide either (p0, p1) or lines=[(p0,p1), ...].")
lines = [(p0, p1)]
n_lines = len(lines)
if labels is not None and len(labels) != n_lines:
raise ValueError("labels length must match number of lines.")
if per_line_kwargs is not None and len(per_line_kwargs) != n_lines:
raise ValueError("per_line_kwargs length must match number of lines.")
if line_kwargs is None:
line_kwargs = dict(lw=4, alpha=0.95)
fig = plt.figure(figsize=(28, 10))
gs = GridSpec(nrows=1, ncols=2, width_ratios=[1.0, 2.0], wspace=0.01, figure=fig)
ax = fig.add_subplot(gs[0, 0])
ax.text(
0.0, 1.05, fr"{mag_map_2d.shape[0]}$\times${mag_map_2d.shape[0]}pix", # (x,y) in axes fraction
transform=ax.transAxes, # <-- use axes coords, not data coords
ha='left', va='top', # align to the right/top
fontsize=30, color='black', fontweight='bold'
)
ax_prof = fig.add_subplot(gs[0, 1])
# Image
im = ax.imshow(
mag_map_2d, cmap=cmap, vmin=vmin, vmax=vmax,
extent=[extent[0], extent[1], extent[2], extent[3]],
origin="upper", aspect="auto"
)
divider = make_axes_locatable(ax)
cax = divider.append_axes(cbar_side, size="6%", pad=0.18)
cbar = fig.colorbar(im, cax=cax, extend="min")
cbar.ax.tick_params(labelsize=30)
cbar.ax.yaxis.set_label_position(cbar_side)
cbar.ax.yaxis.set_ticks_position(cbar_side)
cbar.ax.invert_yaxis()
if remove_labels:
ax.set_xticks([]); ax.set_yticks([])
else:
ax.set_xlabel("x", fontsize=30)
ax.set_ylabel("y", fontsize=30)
ax.tick_params(axis='both', which='major', labelsize=18)
# Lines & profiles
profiles = []
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
exclude = {'#1f77b4', '#d62728'}
filtered_cycle = [c for c in color_cycle if c.lower() not in exclude]
T = []
xlabel = r"t [days]"
if not pix_L or not veff:
xlabel = "Number of pixels"
pix_L = 1
veff = 1
for idx, (q0, q1) in enumerate(lines):
kw = dict(line_kwargs)
if per_line_kwargs and per_line_kwargs[idx]:
kw.update(per_line_kwargs[idx])
if 'color' not in kw and filtered_cycle:
kw['color'] = filtered_cycle[idx % len(filtered_cycle)]
ax.plot([q0[0], q1[0]], [q0[1], q1[1]], **kw)
ax.scatter([q0[0], q1[0]], [q0[1], q1[1]], s=60, color=kw.get("color", "k"), zorder=3)
rr = None
cc = None
center_pix = None
if profile_mode == "pixels":
xaxis, vals, rr, cc = _sample_profile_along_line_pixels(mag_map_2d, q0, q1, extent)
center_pix = lines_pix[idx]
elif profile_mode == "bilinear_pixels":
xaxis, vals, Npixels = _sample_profile_along_line_bilinear_pixels(mag_map_2d, q0, q1, extent)
# elif profile_mode == "other":
# xaxis, vals, Npixels = _sample_from_index(mag_map_2d, q0, q1)
else:
raise ValueError("profile_mode must be 'bilinear_pixels' or 'pixels'.")
#print(xaxis.shape)
label = (labels[idx] if labels else f"Line {idx+1}")
ax_prof.plot(xaxis*pix_L/veff, vals, label=label, **{k:v for k,v in kw.items() if k != 'alpha'})
T.append(xaxis*pix_L/veff)
# add a second x-axis at the top for pixels
profiles.append({'p0': q0, 'p1': q1, 'x_pix': xaxis, 'values': vals, 'label': label,"rr":rr,"cc":cc,"center_pix":center_pix})
ax_prof.set_xlabel(xlabel, fontsize=30)
ax_prof.yaxis.set_label_position("right")
ax_prof.yaxis.set_ticks_position("right")
ax_prof.invert_yaxis()
ax_prof.set_xlim(0,np.max(T))
ax_prof.set_ylabel(cbar_label, fontsize=40)
ax_prof.tick_params(axis='both', which='major', labelsize=30)
ax_prof.grid(True, alpha=0.3)
def ld_to_pix(ld):
return ld * veff / pix_L # inverse of what you used in plot
def pix_to_ld(pix):
return pix * pix_L / veff
secax = ax_prof.secondary_xaxis(
'top', functions=(ld_to_pix, pix_to_ld)
)
secax.set_xlabel("Number of pixels", fontsize=30)
secax.set_xlim(ax_prof.get_xlim()) # keep limits consistent
secax.tick_params(axis='both', which='major', labelsize=30)
# ax_prof.legend(loc="best", fontsize=16, frameon=True)
fig.subplots_adjust(left=0.04, right=0.985, top=0.98, bottom=0.08, wspace=0.04)
if save:
fig.savefig(f"{save}.pdf", dpi=200, bbox_inches="tight")
return fig, (ax, ax_prof), profiles
[docs]
def map_plot_with_profiles_pixels(
mag_map_2d: np.ndarray,
*,
lines_pix: Optional[List[Tuple[Tuple[int,int], Tuple[int,int]]]] = None,
p0: Optional[Tuple[float,float]] = None,
p1: Optional[Tuple[float,float]] = None,
extent=(-2,2,-2,2),
profile_mode: str = "pixels", # default stays pixel-index x-axis
**kwargs
):
"""If lines_pix is given, convert to data coords then call map_plot_with_profiles."""
H, W = mag_map_2d.shape
xmin, xmax, ymin, ymax = extent
if lines_pix:
lines = []
for (r0,c0), (r1,c1) in lines_pix:
x0 = xmin + (c0 / (W-1)) * (xmax - xmin)
x1 = xmin + (c1 / (W-1)) * (xmax - xmin)
y0 = ymax - (r0 / (H-1)) * (ymax - ymin)
y1 = ymax - (r1 / (H-1)) * (ymax - ymin)
lines.append(((x0, y0), (x1, y1)))
return map_plot_with_profiles(
mag_map_2d, lines=lines, extent=extent, profile_mode=profile_mode,lines_pix=lines_pix, **kwargs
)
return map_plot_with_profiles(
mag_map_2d, p0=p0, p1=p1, extent=extent, profile_mode=profile_mode, **kwargs
)