Visualization¶
All plots are interactive Plotly figures. Call fig.show() in
a notebook, fig.write_html("plot.html") to save, or let
generate_report embed them for you.
ECG waveform comparison¶
plot_ecg_comparison
draws one recording from the real set and the recording at the same index from every
synthetic set on ECG graph paper (5 mm major / 1 mm minor grid), with an optional 1 mV
calibration pulse.
from ecgen_eval.visualization import plot_ecg_comparison
fig = plot_ecg_comparison(
real=real,
synthetics=[synth_a, synth_b],
sample_index=0,
leads=["I", "II", "V1", "V5"], # default: all leads
mode="overlay",
add_calibration_pulse=True,
)
fig.show()
mode |
Layout |
|---|---|
overlay |
One row per lead; real and synthetic traces drawn on top of each other |
sidebyside |
One row per lead, one column per dataset |
grid |
Compact rows = leads × columns = datasets view (currently the same layout as sidebyside) |
Synthetic recordings are not paired with real ones, so sample_index simply picks the
i-th recording of each set.
Graph-paper figures¶
make_ecg_paper_figure
returns an empty Plotly subplot grid styled as ECG paper, for building custom layouts:
from ecgen_eval.visualization import make_ecg_paper_figure
fig = make_ecg_paper_figure(n_rows=3, n_cols=1, row_labels=["I", "II", "III"], fs=500)
Metric plots¶
These functions take a mapping {dataset_label: MetricResult} for one metric.
from ecgen_eval.visualization import (
plot_per_lead_bar, plot_metric_violin, plot_psd_overlay, plot_radar_chart,
)
mmd = AVAILABLE_METRICS["mmd"]()
res = {s.label: mmd.compute(real.data, s.data, real.lead_names) for s in [synth_a, synth_b]}
plot_per_lead_bar(res, metric_name="MMD").show()
| Function | Shows | Requires |
|---|---|---|
plot_per_lead_bar |
Grouped bar chart: leads × datasets | any per-lead metric |
plot_metric_violin |
Distribution of pairwise distances per lead | dtw (extra["per_lead_all_distances"]) |
plot_psd_overlay |
Mean normalised PSD, real vs synthetic, per lead | psd results |
plot_radar_chart |
One polygon per dataset across metrics, normalised so the outer edge is best | {label: {metric: score}} |
scores = {
"Model A": {"mmd": 0.01, "fd": 3.2, "psnr": 18.0},
"Model B": {"mmd": 0.03, "fd": 5.1, "psnr": 15.5},
}
plot_radar_chart(scores, higher_is_better={"mmd": False, "fd": False, "psnr": True}).show()
Clinical feature plots¶
Functions in ecgen_eval.visualization.feature_plots compare
clinical feature tables (summary_df) between real and synthetic data.
Rows with NaN in the plotted columns are dropped.
from ecgen_eval.visualization.feature_plots import (
plot_feature_2d, plot_feature_pair_grid, plot_feature_2d_multi,
plot_feature_3d, plot_feature_triple_grid,
)
# Scatter + 2D density contours for one pair
plot_feature_2d(summ_real, summ_synth, "rr_mean_ms", "hr_bpm", synth_label="ECGEN-FM").show()
# Grid of the default pairs
plot_feature_pair_grid(summ_real, summ_synth).show()
# Several synthetic models in one 2D plot
plot_feature_2d_multi(summ_real, [summ_a, summ_b], "qrs_duration_ms", "qt_interval_ms",
synth_labels=["A", "B"]).show()
# Interactive, rotatable 3D scatter
plot_feature_3d(summ_real, summ_synth, "rr_mean_ms", "hr_bpm", "qrs_duration_ms").show()
# Grid of the default triples
plot_feature_triple_grid(summ_real, summ_synth).show()
Default pairs and triples (filtered to available columns):
| x | y |
|---|---|
rr_mean_ms |
hr_bpm |
qrs_duration_ms |
qt_interval_ms |
st_deviation_mv |
t_amplitude_mv |
rr_mean_ms |
hrv_sdnn_ms |
| x | y | z |
|---|---|---|
rr_mean_ms |
hr_bpm |
qrs_duration_ms |
pr_interval_ms |
qt_interval_ms |
hrv_sdnn_ms |
st_deviation_mv |
t_amplitude_mv |
qrs_duration_ms |
Pass your own lists via pairs= / triples=.