Starting with hrtfpykit.plots¶
hrtfpykit.plots provides figures for inspecting loaded HRTF objects and the metrics derived from them. This tutorial shows the full public plotting API: source grids, plane grids, HRIR amplitude, HRTF magnitude, energy time curves, spectrum planes, ITD and ILD cues, comparison curves, source grid difference maps, HRTF difference metrics, and spherical harmonic reconstruction plots.
If the notebook environment is not ready yet, start with Set Up. The opening cells prepare two measured SONICOM SOFA files with the SONICOM dataset class and load them with load_hrtf. Both files use the same measured source grid and sample rate, so the comparison and difference examples evaluate the same directions across two subjects.
The examples move from inspection to comparison. First, the tutorial controls figure display and saving. Then it shows each single HRTF plot, the comparison plots with legend and line styling, signed and absolute difference maps, the general compare_hrtf_difference interface for LSD, RMSE, MAE, and NRMSE, and the spherical harmonic diagnostics used with sht and sht_inverse.
Download two SONICOM HRTF files¶
Comparison functions need at least two loaded HRTF objects. A SONICOM dataset object prepares two measured SimpleFreeFieldHRIR SOFA files: P0001_FreeFieldComp_44kHz.sofa and P0002_FreeFieldComp_44kHz.sofa.
The dataset root is relative, so the files are stored under datasets/sonicom inside the folder where the notebook is run. The selected download variant is measured, 44.1 kHz, FreeFieldComp, matching the default SONICOM HRTF variant used by hrtfpykit.
from pathlib import Path
from hrtfpykit.datasets import SONICOM
# Define the local dataset root.
root = Path("datasets/sonicom")
# Keep this tutorial small: use only P0001 and P0002.
selected_subject_ids = ("P0001", "P0002")
# Download only the measured 44.1 kHz FreeFieldComp HRTF resources for P0001 and P0002.
SONICOM(
root=root,
download=True,
download_resources="hrtf",
download_hrtf_variant={
"type": "measured",
"sample_rate": 44100,
"version": "FreeFieldComp",
},
download_subject_ids=selected_subject_ids,
subject_ids=selected_subject_ids,
verify_checksum=True,
)
# Build paths to the downloaded SOFA files.
pp1_path = root / "P0001" / "HRTF" / "HRTF" / "44kHz" / "P0001_FreeFieldComp_44kHz.sofa"
pp2_path = root / "P0002" / "HRTF" / "HRTF" / "44kHz" / "P0002_FreeFieldComp_44kHz.sofa"
# Stop early if either expected file is not available.
for path in (pp1_path, pp2_path):
if not path.exists():
raise FileNotFoundError(f"Expected SOFA file was not found: {path}")
print(pp1_path)
print(pp2_path)
Load HRTFs for plotting¶
hrtfpykit.plots functions operate on loaded HRTF objects. The two SONICOM files use the same measured source grid and sample rate, which makes them suitable for comparison plots and difference plots that require matching positions.
from hrtfpykit.hrtf import load_hrtf
# Load each measured SOFA file as an HRTF object.
hrtf_p0001 = load_hrtf(pp1_path)
hrtf_p0002 = load_hrtf(pp2_path)
# Keep the comparison objects and labels together.
comparison_hrtfs = [hrtf_p0001, hrtf_p0002]
comparison_labels = ["SONICOM P0001", "SONICOM P0002"]
line_styles = ["-", "--"]
print("P0001 IR shape:", hrtf_p0001.IR.values.shape)
print("P0002 IR shape:", hrtf_p0002.IR.values.shape)
print("P0001 TF shape:", hrtf_p0001.TF.values.shape)
print("P0002 TF shape:", hrtf_p0002.TF.values.shape)
Import plotting functions¶
The tutorial imports every public function exported by hrtfpykit.plots. The examples below use each plot at least once, then spend extra time on comparison controls and difference maps because those are the plots most users tune after the first inspection pass.
import numpy as np
from hrtfpykit.hrtf import sht, sht_error, sht_inverse
from hrtfpykit.plots import (
compare_absolute_ild,
compare_absolute_itd,
compare_amplitude,
compare_magnitude,
compare_ild,
compare_ild_difference,
compare_itd,
compare_itd_difference,
compare_hrtf_difference,
plot_absolute_ild,
plot_absolute_itd,
plot_amplitude,
plot_elevation_spectrum,
plot_etc,
plot_etc_plane,
plot_magnitude,
plot_plane_grid,
plot_ild,
plot_ild_fd,
plot_itd,
plot_source_grid,
plot_spectrum_plane,
sht_reconstruction_comparison,
sht_reconstruction_error,
)
Control figure display and text¶
Plot functions display their figure by default. Passing show=False creates the figure and returns it without opening a window. That is the right mode for scripts, notebooks with custom display order, tests, saved reports, and command line tools. The show_titles, show_labels, and show_legends flags control generated text without changing the plotted data.
Notebook examples in this tutorial use show=False inside each plot call and one plt.show() at the end of the cell. This avoids duplicate output from the notebook display hook, which can render a returned figure a second time when it is the last expression in a cell.
import matplotlib.pyplot as plt
# Create a controlled comparison figure without displaying it immediately.
controlled_figure = compare_magnitude(
comparison_hrtfs,
positions="front",
ear="left",
x_axis="log",
unit="db",
reference="max",
legends=comparison_labels,
line_colors=["tab:blue", "tab:orange"],
line_styles=line_styles,
freq_max=16000.0,
show=False,
show_titles=False,
)
# Display the controlled figure when the cell is ready.
plt.show()
Save figures¶
Because plot functions return the created figure, saving a plot does not require reopening or rerunning it. In scripts and reports, pass show=False, save the current figure with plt.savefig(...), and close the returned figure when it is no longer needed.
# Save a figure generated without immediate display.
output_dir = Path("plot_outputs")
output_dir.mkdir(exist_ok=True)
saved_figure = compare_hrtf_difference(
hrtf_p0001,
hrtf_p0002,
metric="lsd",
ear="left",
azimuth_range_mode="-180-180",
show=False,
show_titles=False,
)
saved_path = output_dir / "p0001-p0002-lsd-map.png"
plt.savefig(saved_path, dpi=160, bbox_inches="tight")
plt.close(saved_figure)
print(saved_path)
Plot source grids and spatial planes¶
plot_source_grid visualizes the current source grid of one loaded HRTF object. plot_plane_grid highlights canonical horizontal, median, or frontal planes. These functions read the current Sources state, so selected HRTF objects plot their selected grid rather than the original full grid.
# Plot the full source grid attached to one loaded HRTF.
plot_source_grid(hrtf_p0001, show=False)
# Plot the source grid with canonical spatial planes highlighted.
plot_plane_grid(
hrtf_p0001,
plane=["horizontal", "median", "frontal"],
show=False,
)
plt.show()
Plot amplitude and magnitude responses¶
plot_amplitude shows time domain HRIR waveforms. plot_magnitude shows frequency domain HRTF magnitudes. Both functions use the current IR and TF state of the HRTF object passed as the first argument.
plot_amplitude(
hrtf_p0001,
positions=["front", "left"],
ear="both",
x_axis="samples",
show=False,
)
plot_magnitude(
hrtf_p0001,
positions=["front", "left"],
ear="left",
x_axis="log",
reference="max",
show=False,
)
plt.show()
Plot energy time curves¶
plot_etc shows sample wise HRIR level in decibels for selected source positions. plot_etc_plane shows the same time domain level view as a heatmap over a measured horizontal or median plane. These plots reflect selections, transforms, padding, gain changes, and sample rate changes already applied to the HRTF object.
plot_etc(
hrtf_p0001,
positions=["front", "left"],
ear="both",
x_axis="samples",
reference="max",
show=False,
)
plot_etc_plane(
hrtf_p0001,
plane="horizontal",
plane_angle=0.0,
ear="left",
x_axis="samples",
reference="max",
show=False,
)
plt.show()
Plot spectral planes¶
plot_spectrum_plane shows HRTF magnitude over a measured horizontal or median plane. plot_elevation_spectrum shows how the magnitude response changes across elevation for a fixed azimuth slice.
plot_spectrum_plane(
hrtf_p0001,
plane="horizontal",
plane_angle=0.0,
ear="left",
x_axis="linear",
show=False,
)
plot_elevation_spectrum(
hrtf_p0001,
azimuth="front",
ear="left",
show=False,
)
plt.show()
Plot ITD and ILD cues¶
Cue plots expose binaural timing and level differences around the source grid. plot_itd and plot_ild show signed curves over a horizontal plane. plot_absolute_itd shows absolute ITD in polar form, and plot_absolute_ild shows unsigned broad band ILD in polar form. plot_ild_fd shows frequency dependent ILD over a measured plane.
plot_itd(hrtf_p0001, plane_angle=0.0, show=False)
plot_absolute_itd(hrtf_p0001, plane_angle=0.0, show=False)
plot_ild(hrtf_p0001, plane_angle=0.0, show=False)
plot_absolute_ild(hrtf_p0001, plane_angle=0.0, show=False)
plot_ild_fd(
hrtf_p0001,
plane="horizontal",
plane_angle=0.0,
freq_max=16000.0,
show=False,
)
plt.show()
Compare amplitude responses¶
compare_amplitude overlays HRIR waveforms from multiple HRTF objects at the same requested source direction. This is the time domain view, so it is the most direct comparison for arrival timing, onset shape, windowing, padding, and waveform changes before moving to frequency domain plots.
The example compares the left ear at front and uses sample indices on the x axis. Named position queries are resolved on each HRTF independently; when the resolved real positions differ, hrtfpykit emits a warning so the comparison is not silently treated as perfectly source aligned.
# Compare front-direction HRIR waveforms for the left ear.
compare_amplitude(
comparison_hrtfs,
positions="front",
ear="left",
x_axis="samples",
legends=comparison_labels,
line_styles=line_styles,
show=False,
)
plt.show()
Compare magnitude responses¶
compare_magnitude overlays HRTF magnitude spectra from multiple HRTF objects. This is the frequency domain view for spectral notches, broad band gain differences, high frequency structure, and subject variation at selected directions.
The example compares the left ear at front and back, uses a logarithmic frequency axis, normalizes dB values with reference="max", and limits the plot to 16 kHz. The next section uses the same plot to show styling controls shared by comparison figures.
# Compare left-ear HRTF magnitudes at two named directions.
compare_magnitude(
comparison_hrtfs,
positions=["front", "back"],
ear="left",
x_axis="log",
unit="db",
reference="max",
legends=comparison_labels,
line_styles=line_styles,
freq_max=16000.0,
show=False,
)
plt.show()
Style comparison lines¶
Comparison curve plots expose legends, line_colors, line_styles, legend_location, and legend_bbox_to_anchor. These controls are useful when a figure compares measured subjects, processing variants, or model outputs and the labels must stay readable outside the default style.
# Apply explicit labels, colors, line styles, and legend placement.
compare_magnitude(
comparison_hrtfs,
positions="front",
ear="left",
x_axis="log",
unit="db",
reference="max",
legends=["Reference P0001", "Compared P0002"],
line_colors=["#0057B8", "#D55E00"],
line_styles=["-", "--"],
legend_location="lower left",
freq_max=16000.0,
show=False,
)
plt.show()
Compare absolute and signed ITD cues¶
ITD comparison plots describe left right timing cues around the horizontal plane. compare_absolute_itd shows ITD magnitude in polar form. compare_itd keeps the sign and plots ITD against azimuth.
Use the absolute plot to compare cue strength without side information. Use the signed plot when the direction of the left right delay matters.
# Compare absolute ITD magnitude on the horizontal plane.
compare_absolute_itd(
comparison_hrtfs,
plane_angle=0.0,
legends=comparison_labels,
line_styles=line_styles,
show=False,
)
# Compare signed ITD across azimuth.
compare_itd(
comparison_hrtfs,
plane_angle=0.0,
legends=comparison_labels,
line_styles=line_styles,
show=False,
)
plt.show()
Compare absolute and signed broad band ILD cues¶
Broad band ILD comparison plots describe left right level cues around the horizontal plane. compare_absolute_ild shows ILD magnitude in polar form. compare_ild keeps the sign and plots ILD against azimuth.
Use the absolute plot to compare level cue strength. Use the signed plot when the side of the level advantage matters.
# Compare unsigned broad-band ILD on the horizontal plane.
compare_absolute_ild(
comparison_hrtfs,
plane_angle=0.0,
legends=comparison_labels,
line_styles=line_styles,
show=False,
)
# Compare signed ILD across azimuth.
compare_ild(
comparison_hrtfs,
plane_angle=0.0,
legends=comparison_labels,
line_styles=line_styles,
show=False,
)
plt.show()
Compare signed and absolute difference maps¶
compare_itd_difference and compare_ild_difference compare a reference HRTF against one or more HRTFs and draw one value per source position. absolute=True maps the size of the error. absolute=False preserves the sign of compared - reference.
The first two calls below compare ITD differences in samples. The second two calls compare broad band ILD differences in dB. The examples use both heatmap and scatter renderers so the two source map modes are visible in the same section.
# ITD difference magnitude in samples.
compare_itd_difference(
hrtf_p0001,
hrtf_p0002,
output="samples",
absolute=True,
azimuth_range_mode="-180-180",
plot_type="heatmap",
colormap="viridis",
show=False,
)
# Signed ITD difference in samples: compared - reference.
compare_itd_difference(
hrtf_p0001,
hrtf_p0002,
output="samples",
absolute=False,
azimuth_range_mode="-180-180",
plot_type="scatter",
colormap="coolwarm",
show=False,
)
# Broad band ILD difference magnitude in dB.
compare_ild_difference(
hrtf_p0001,
hrtf_p0002,
absolute=True,
azimuth_range_mode="-180-180",
plot_type="heatmap",
colormap="magma",
show=False,
)
# Signed broad band ILD difference in dB: compared - reference.
compare_ild_difference(
hrtf_p0001,
hrtf_p0002,
absolute=False,
azimuth_range_mode="-180-180",
plot_type="scatter",
colormap="coolwarm",
show=False,
)
plt.show()
Compare HRTF difference metrics over the source grid¶
compare_hrtf_difference is the general source map for HRTF difference metrics. It calls hrtf_difference and plots one value per source position. metric="lsd" compares TF magnitude in dB. metric="rmse" and metric="mae" compare HRIR amplitude error. metric="nrmse" compares HRIR error normalized by the reference energy and reports dB.
The first example uses LSD over a selected frequency band. The following examples show RMSE, MAE, and NRMSE on the same source grid. Use ear="left" or ear="right" for one ear, or ear="both" to reduce both ears into one source map.
# LSD over a selected frequency band.
compare_hrtf_difference(
hrtf_p0001,
hrtf_p0002,
metric="lsd",
ear="left",
frequency_bands=(3000.0, 8000.0),
azimuth_range_mode="-180-180",
plot_type="heatmap",
colormap="viridis",
show=False,
)
# HRIR RMSE in amplitude units.
compare_hrtf_difference(
hrtf_p0001,
hrtf_p0002,
metric="rmse",
ear="left",
azimuth_range_mode="-180-180",
plot_type="scatter",
colormap="plasma",
show=False,
)
# HRIR MAE in amplitude units.
compare_hrtf_difference(
hrtf_p0001,
hrtf_p0002,
metric="mae",
ear="left",
azimuth_range_mode="-180-180",
plot_type="scatter",
colormap="cividis",
show=False,
)
# Reference-normalized HRIR error in dB.
compare_hrtf_difference(
hrtf_p0001,
hrtf_p0002,
metric="nrmse",
ear="left",
azimuth_range_mode="-180-180",
plot_type="heatmap",
colormap="magma",
show=False,
)
plt.show()
Prepare spherical harmonic reconstruction data¶
The spherical harmonic plotting functions diagnose reconstruction quality after an SHT workflow. Before plotting, compute spherical harmonic coefficients with sht, reconstruct magnitudes on the original source grid with sht_inverse, and compute summary errors with sht_error.
This preparation cell creates the data used by the next two plotting cells. The reconstruction contains magnitude only; it does not reconstruct phase and it does not create a new complex HRTF object.
# Build a low-order spherical-harmonic magnitude representation for both ears.
sh = sht(hrtf_p0001, sh_order=4, ear="both")
reconstructed_magnitude = sht_inverse(sh)
# Compare reconstructed magnitudes with the original linear magnitudes.
original_magnitude = np.abs(hrtf_p0001.TF.values[:, 0:2, :])
abs_err, rel_err, rms_err, max_err = sht_error(
original_magnitude=original_magnitude,
reconstructed_magnitude=reconstructed_magnitude,
magnitude="db",
reference="max",
)
print("SH coefficients shape:", sh.C.shape)
print("Reconstructed magnitude shape:", reconstructed_magnitude.shape)
print("Global RMS reconstruction error (dB):", f"{rms_err:.2f}")
print("Maximum reconstruction error (dB):", f"{max_err:.2f}")
Compare original and reconstructed spectra¶
sht_reconstruction_comparison overlays the original HRTF magnitude and the reconstructed magnitude for one source direction and ear. It is useful when a global error value is not enough and the frequency detail at a specific direction needs to be inspected directly.
# Overlay original and reconstructed spectra at one direction.
sht_reconstruction_comparison(
hrtf_p0001,
reconstructed_magnitude,
position="front",
ear="left",
x_axis="log",
unit="db",
reference="max",
freq_max=16000.0,
show=False,
)
plt.show()
Plot spherical harmonic reconstruction error¶
sht_reconstruction_error plots original - reconstructed for one source direction and ear. This is the direct view of where the selected SH order loses or overestimates spectral detail across frequency.
# Plot the point-wise reconstruction error for the same direction.
sht_reconstruction_error(
hrtf_p0001,
reconstructed_magnitude,
position="front",
ear="left",
x_axis="log",
magnitude="db",
reference="max",
freq_max=16000.0,
show=False,
)
plt.show()