"""Various plotting functions."""
import matplotlib.pyplot as plt
import numpy as np
from edges.averaging.averaging import bin_array_unweighted
from .calibrator import Calibrator
from .calobs import CalibrationObservation
from .input_sources import InputSource
from .sparams import ReflectionCoefficient, S11ModelParams
from .spectra import LoadSpectrum
[docs]
def plot_raw_spectrum(
spectrum: np.ndarray | LoadSpectrum,
freq: np.ndarray | None = None,
fig=None,
ax=None,
xlabel: bool = True,
ylabel: bool = True,
**kwargs,
):
"""
Make a plot of the averaged uncalibrated spectrum associated with this load.
Parameters
----------
spectrum
The LoadSpectrum object to plot.
fig : Figure
Optionally, pass a matplotlib figure handle which will be used to plot.
ax : Axis
Optional, pass a matplotlib Axis handle which will be added to.
xlabel : bool
Whether to make an x-axis label.
ylabel : bool
Whether to plot the y-axis label
kwargs :
All other arguments are passed to `plt.subplots()`.
"""
if isinstance(spectrum, LoadSpectrum):
freq = spectrum.freqs
spectrum = spectrum.q.data.squeeze()
else:
assert freq is not None
if fig is None:
fig, ax = plt.subplots(1, 1, **kwargs)
ax.plot(freq, spectrum)
if ylabel:
ax.set_ylabel("$T^*$ [K]")
ax.grid(True)
if xlabel:
ax.set_xlabel("Frequency [MHz]")
[docs]
def plot_raw_spectra(calobs: CalibrationObservation, fig=None, ax=None) -> plt.Figure:
"""
Plot raw uncalibrated spectra for all calibrator sources.
Parameters
----------
fig : :class:`plt.Figure`
A matplotlib figure on which to make the plot. By default creates a new one.
ax : :class:`plt.Axes`
A matplotlib Axes on which to make the plot. By default creates a new one.
Returns
-------
fig : :class:`plt.Figure`
The figure on which the plot was made.
"""
if fig is None and ax is None:
fig, ax = plt.subplots(
len(calobs.loads), 1, sharex=True, gridspec_kw={"hspace": 0.05}
)
for i, (name, load) in enumerate(calobs.loads.items()):
ax[i].plot(load.freqs, load.averaged_q)
ax[i].set_ylabel("$Q$")
ax[i].set_title(name)
ax[i].grid(True)
ax[-1].set_xlabel("Frequency [MHz]")
return fig
[docs]
def plot_s11_residual(
raw_s11: ReflectionCoefficient,
s11_model_params: S11ModelParams,
load_name: str | None = None,
fig=None,
ax=None,
color_abs="C0",
color_diff="g",
label=None,
title=None,
decade_ticks=True,
ylabels=True,
) -> plt.Figure:
"""
Plot the residuals of the S11 model compared to un-smoothed corrected data.
Returns
-------
fig :
Matplotlib Figure handle.
"""
if ax is None or len(ax) != 4:
fig, ax = plt.subplots(
4, 1, sharex=True, gridspec_kw={"hspace": 0.05}, facecolor="w"
)
if fig is None:
fig = ax[0].get_figure()
if decade_ticks:
for axx in ax:
axx.grid(True)
ax[-1].set_xlabel("Frequency [MHz]")
modelled = raw_s11.smoothed(s11_model_params)
fq = raw_s11.freqs
ax[0].plot(fq, 20 * np.log10(np.abs(modelled.s11)), color=color_abs, label=label)
if ylabels:
ax[0].set_ylabel(r"$|S_{11}|$")
ax[1].plot(fq, np.abs(modelled.s11) - np.abs(raw_s11.s11), color_diff)
if ylabels:
ax[1].set_ylabel(r"$\Delta |S_{11}|$")
ax[2].plot(fq, np.unwrap(np.angle(modelled.s11)) * 180 / np.pi, color=color_abs)
if ylabels:
ax[2].set_ylabel(r"$\angle S_{11}$")
ax[3].plot(
fq,
np.unwrap(np.angle(modelled.s11)) - np.unwrap(np.angle(raw_s11.s11)),
color_diff,
)
if ylabels:
ax[3].set_ylabel(r"$\Delta \angle S_{11}$")
lname = load_name or ""
if title is None:
title = f"{lname} Reflection Coefficient Models"
if title:
fig.suptitle(f"{lname} Reflection Coefficient Models", fontsize=14)
if label:
ax[0].legend()
return fig
[docs]
def plot_s11_models(
calobs: CalibrationObservation,
s11_model_params: S11ModelParams,
receiver_model_params: S11ModelParams,
**kwargs,
):
"""
Plot residuals of S11 models for all sources.
Returns
-------
dict:
Each entry has a key of the source name, and the value is a matplotlib fig.
"""
_fig, ax = plt.subplots(
4,
len(calobs.loads) + 1,
figsize=((len(calobs.loads) + 1) * 4, 6),
sharex=True,
gridspec_kw={"hspace": 0.05},
layout="constrained",
)
for i, (name, source) in enumerate(calobs.loads.items()):
plot_s11_residual(
source._raw_s11,
s11_model_params=s11_model_params,
load_name=name,
ax=ax[:, i],
title=False,
**kwargs,
)
ax[0, i].set_title(name)
plot_s11_residual(
calobs._raw_receiver,
s11_model_params=receiver_model_params,
ax=ax[:, -1],
title=False,
**kwargs,
)
ax[0, -1].set_title("Receiver")
return ax
[docs]
def plot_calibrated_temp(
calobs: CalibrationObservation,
calibrator: Calibrator,
load: InputSource | str,
bins: int = 2,
fig=None,
ax=None,
xlabel=True,
ylabel=True,
):
"""
Make a plot of calibrated temperature for a given source.
Parameters
----------
load : :class:`~LoadSpectrum` instance
Source to plot.
bins : int
Number of bins to smooth over (std of Gaussian kernel)
fig : Figure
Optionally provide a matplotlib figure to add to.
ax : Axis
Optionally provide a matplotlib Axis to add to.
xlabel : bool
Whether to write the x-axis label
ylabel : bool
Whether to write the y-axis label
Returns
-------
fig :
The matplotlib figure that was created.
"""
load = calobs._load_str_to_load(load)
if fig is None and ax is None:
fig, ax = plt.subplots(1, 1, facecolor="w")
# binning
temp_calibrated = calibrator.calibrate_load(load)
if bins > 0:
freq_ave_cal = bin_array_unweighted(temp_calibrated, size=bins)
f = bin_array_unweighted(calobs.freqs.to_value("MHz"), size=bins)
else:
freq_ave_cal = temp_calibrated
f = calobs.freqs.to_value("MHz")
freq_ave_cal[np.isinf(freq_ave_cal)] = np.nan
rms = np.sqrt(np.mean((freq_ave_cal - np.mean(freq_ave_cal)) ** 2))
ax.plot(
f,
freq_ave_cal,
label=f"Calibrated {load.name} [RMS = {rms:.3f}]",
)
temp_ave = calobs.source_thermistor_temps.get(load.name, load.temp_ave)
if temp_ave.isscalar:
temp_ave = np.ones(calobs.freqs.size) * temp_ave
ax.plot(
calobs.freqs,
temp_ave,
color="C2",
label="Average thermistor temp",
)
ax.set_ylim([np.nanmin(freq_ave_cal).value, np.nanmax(freq_ave_cal).value])
if xlabel:
ax.set_xlabel("Frequency [MHz]")
if ylabel:
ax.set_ylabel("Temperature [K]")
plt.ticklabel_format(useOffset=False)
ax.grid()
ax.legend()
return plt.gcf()
[docs]
def plot_calibrated_temps(
calobs: CalibrationObservation,
calibrator: Calibrator,
bins: int = 64,
fig=None,
ax=None,
**kwargs,
):
"""
Plot all calibrated temperatures in a single figure.
Parameters
----------
bins : int
Number of bins in the smoothed spectrum
Returns
-------
fig :
Matplotlib figure that was created.
"""
if fig is None or ax is None or len(ax) != len(calobs.loads):
fig, ax = plt.subplots(
len(calobs.loads),
1,
sharex=True,
gridspec_kw={"hspace": 0.05},
figsize=(10, 12),
)
for i, source in enumerate(calobs.loads):
plot_calibrated_temp(
calobs=calobs,
calibrator=calibrator,
load=source,
bins=bins,
fig=fig,
ax=ax[i],
xlabel=i == (len(calobs.loads) - 1),
)
fig.suptitle("Calibrated Temperatures for Calibration Sources", fontsize=15)
return fig
[docs]
def plot_cal_coefficients(calibrator: Calibrator, fig=None, ax=None):
"""
Make a plot of the calibration coefficents, Tsca, Tof, Tunc, Tcos and Tsin.
Parameters
----------
fig : Figure
Optionally pass a matplotlib figure to add to.
ax : Axis
Optionally pass a matplotlib axis to pass to. Must have 5 axes.
"""
if fig is None or ax is None:
fig, ax = plt.subplots(
5, 1, facecolor="w", gridspec_kw={"hspace": 0.05}, figsize=(10, 9)
)
labels = [
"Scale ($C_1$)",
"Offset ($C_2$) [K]",
r"$T_{\rm unc}$ [K]",
r"$T_{\rm cos}$ [K]",
r"$T_{\rm sin}$ [K]",
]
for i, (kind, label) in enumerate(
zip(["Tsca", "Toff", "Tunc", "Tcos", "Tsin"], labels, strict=False)
):
ax[i].plot(calibrator.freqs, getattr(calibrator, kind))
ax[i].set_ylabel(label, fontsize=13)
ax[i].grid()
plt.ticklabel_format(useOffset=False)
if i == 4:
ax[i].set_xlabel("Frequency [MHz]", fontsize=13)
fig.suptitle("Calibration Parameters", fontsize=15)
return fig