Source code for FIREQ_PLOTTER.plotting.plot_3d_heatmap
"""Interactive 3D heatmap of the experiment data."""
import json
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from .common import TIME_MULTIPLIER, add_sliders, get_var_names, load_exported_dataframe
[docs]
def _plot_3d_heatmap(
exp_dir: str,
plot_magnitude: bool = True,
plot_phase: bool = False,
plot_real: bool = False,
plot_imag: bool = False,
save: bool = False,
) -> None:
"""Interactive heatmap.
accumulated:
x = variable 0
y = variable 1
raw/decimated:
x = time
y = variable 0
Remaining variables become sliders.
:param exp_dir: experiment directory.
:type exp_dir: str
:param plot_magnitude: plot the magnitude.
:type plot_magnitude: bool
:param plot_phase: plot the phase.
:type plot_phase: bool
:param plot_real: plot the real part.
:type plot_real: bool
:param plot_imag: plot the imaginary part.
:type plot_imag: bool
:param save: also save the figure as a_figure.png.
:type save: bool
"""
exp_path = Path(exp_dir)
dataframe = load_exported_dataframe(exp_path)
if dataframe is None:
return
with open(exp_path / "config.json") as f:
config = json.load(f)
output_type = config["sys_config"]["/axisAcquisitionIP_0"]["$output_type"]
time_mult = TIME_MULTIPLIER.get(output_type, 1)
var_names = get_var_names(dataframe)
if output_type in ("raw", "decimated"):
if not var_names:
print(f"No sweep variables in experiment {exp_path.name}; nothing to plot.")
return
x_name = "time"
y_name = var_names[0]
slider_vars = var_names[1:]
else:
if len(var_names) < 2:
print(f"Accumulated experiment {exp_path.name} needs at least two sweep variables.")
return
x_name = var_names[0]
y_name = var_names[1]
slider_vars = var_names[2:]
var_values = {
name: np.linspace(
config["variables"][name]["start"],
config["variables"][name]["stop"],
config["variables"][name]["num"],
)
for name in var_names
}
mode = (
"magnitude"
if plot_magnitude
else "phase"
if plot_phase
else "real"
if plot_real
else "imag"
)
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(figsize=(8, 6))
plt.subplots_adjust(bottom=0.08 * max(1, len(slider_vars) + 1))
img = ax.imshow(
np.zeros((2, 2)),
origin="lower",
aspect="auto",
interpolation="nearest",
)
cbar = plt.colorbar(img, ax=ax)
cbar.set_label(mode)
def update(_: object = None) -> None:
"""Redraw the heatmap with the current slider selections.
:param _: slider value (unused).
:type _: object
"""
df = dataframe
for var in slider_vars:
idx = int(sliders[var].val)
df = df[df.index.get_level_values(var) == idx]
if df.empty:
return
if output_type in ("raw", "decimated"):
heat = (
df["value"]
.unstack("time")
.reindex(range(len(var_values[y_name])))
)
x = heat.columns.to_numpy() * time_mult
y = var_values[y_name]
else:
heat = (
df["value"]
.droplevel("time")
.unstack(y_name)
.reindex(range(len(var_values[x_name])))
)
heat = heat.T
x = var_values[x_name]
y = var_values[y_name]
data = heat.to_numpy()
if mode == "magnitude":
data = np.abs(data)
cmap = "viridis"
elif mode == "phase":
data = np.angle(data)
cmap = "twilight"
elif mode == "real":
data = np.real(data)
cmap = "RdBu_r"
else:
data = np.imag(data)
cmap = "RdBu_r"
img.set_data(data)
img.set_cmap(cmap)
img.set_extent([x[0], x[-1], y[0], y[-1]])
img.set_clim(np.nanmin(data), np.nanmax(data))
ax.set_xlabel(x_name)
ax.set_ylabel(y_name)
fig.canvas.draw_idle()
sliders = add_sliders(slider_vars, var_values, update)
update()
if save:
plt.savefig(exp_path / "a_figure.png")
plt.show()