Starting with hrtfpykit.plots

hrtfpykit.plots contains plotting functions called directly from the plotting module. These functions support comparison workflows and spherical-harmonic diagnostics: they take loaded HRTF objects and create Matplotlib figures for overlaid HRIR/HRTF curves, ITD and ILD cue comparisons, spatial difference maps, LSD visualizations, and SHT reconstruction quality. Single-object plotting methods such as plot_magnitude() and plot_source_grid() belong to the HRTF object and are covered in Starting with hrtfpykit.hrtf.

Notebook setup is covered once in Set Up. The opening cells prepare two measured SONICOM SOFA files, P0001_FreeFieldComp_44kHz.sofa and P0002_FreeFieldComp_44kHz.sofa, with the SONICOM dataset class. Those files are loaded as HRTF objects and used for time-domain amplitude, frequency-domain magnitude, absolute and signed interaural cues, spatial ITD and ILD differences, full-grid LSD, plane-restricted LSD, and spherical-harmonic reconstruction error.

By the end, you should understand which plots are direct hrtfpykit.plots functions, how they differ from built-in methods on an HRTF object, why list-based comparison plots accept 2 to 5 HRTFs, why pairwise difference plots compare exactly two HRTFs, and how position queries, ear selection, frequency ranges, display control, figure titles, and SHT reconstruction outputs determine what each figure shows.

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")

# Exclude all SONICOM subjects except P0001 and P0002.
exclude_subject_ids = tuple(f"P{i:04d}" for i in range(3, 401))

# 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",
    },
    exclude_subject_ids=exclude_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 rest of the tutorial uses one plotting function or one tightly related pair of functions per code cell. Importing the plotting functions once keeps the following examples focused on the plotting call itself, so each cell is easier to run, edit, and inspect independently.

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_ild_curve,
    compare_ild_difference,
    compare_itd_curve,
    compare_itd_difference,
    compare_lsd,
    compare_lsd_plane,
    compare_magnitude,
    sht_reconstruction_comparison,
    sht_reconstruction_error,
)

Control figure display and titles

Before moving through the acoustic examples, it is useful to separate plotting control from plot interpretation. Most functions in hrtfpykit.plots display their Matplotlib figure immediately. Passing show=False creates and configures the figure but leaves display control to the caller. That is useful when a notebook cell creates several figures, when a script saves figures to disk, or when a publication figure is assembled outside the plotting function.

Comparison plots also expose presentation controls. legends names each HRTF, line_colors and line_styles style each subject consistently, and titles=False removes generated titles. Suppressing titles is useful for scientific papers and reports where the figure caption should describe the panel instead of text inside the axes. List-based comparison plots accept 2 to 5 HRTF objects; pairwise difference plots compare exactly two HRTFs because they compute one difference field.

The short example below demonstrates figure control only. The main plotting examples that follow use the normal direct plotting behavior.

import matplotlib.pyplot as plt

# Create a controlled comparison figure without displaying it immediately.
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,
    titles=False,
)

# Display the controlled figure when the cell is ready.
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 useful for inspecting arrival timing, onset shape, early reflections, padding, and waveform differences before moving to frequency-domain interpretation.

The example below compares the left-ear waveform for the resolved front position and uses sample indices on the x-axis. Keep this cell small on purpose: users can change the ear, position query, or x-axis and immediately inspect one figure without rerunning unrelated plots.

# 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,
)

Compare magnitude responses

compare_magnitude overlays HRTF magnitude spectra from multiple HRTF objects. This is the frequency-domain view, so it is useful for inspecting spectral notches, broadband gain differences, high-frequency structure, and subject-to-subject variation at specific source 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. Named position queries are resolved on each HRTF independently; if the resolved real source positions do not match, hrtfpykit warns because the comparison is no longer perfectly source-aligned.

# 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,
)

Compare ITD cue curves

ITD plots describe left-right timing differences around the horizontal plane. They are useful for checking whether two subjects or processing outputs preserve similar timing cues, especially around lateral directions where ITD usually changes strongly.

This cell keeps the ITD diagnostics together: compare_absolute_itd shows cue magnitude on a polar axis, while compare_itd_curve keeps the sign of the ITD across azimuth. The requested elevation_angle=0.0 selects the nearest measured horizontal plane in each HRTF.

# Compare absolute ITD magnitude on the horizontal plane.
compare_absolute_itd(
    comparison_hrtfs,
    elevation_angle=0.0,
    legends=comparison_labels,
    line_styles=line_styles,
)

# Compare signed ITD across azimuth.
compare_itd_curve(
    comparison_hrtfs,
    elevation_angle=0.0,
    legends=comparison_labels,
    line_styles=line_styles,
)

Compare ILD cue curves

ILD plots describe left-right level differences around the horizontal plane. They are useful for inspecting directional level cues, shadowing behavior, and subject-to-subject differences that are not visible from one single magnitude curve.

This cell keeps the ILD diagnostics together: compare_absolute_ild shows absolute cue magnitude on a polar axis, while compare_ild_curve preserves the cue sign across azimuth.

# Compare absolute ILD magnitude on the horizontal plane.
compare_absolute_ild(
    comparison_hrtfs,
    elevation_angle=0.0,
    legends=comparison_labels,
    line_styles=line_styles,
)

# Compare signed ILD across azimuth.
compare_ild_curve(
    comparison_hrtfs,
    elevation_angle=0.0,
    legends=comparison_labels,
    line_styles=line_styles,
)

Plot ITD difference over the source grid

compare_itd_difference compares exactly two HRTF objects and maps the signed timing difference at every source position. This is useful when a processing step, model output, or second subject should be inspected spatially rather than summarized by one global value.

The example below reports the difference in samples and uses azimuth_range_mode="-180-180" so front-facing directions are easier to read around zero azimuth.

# Plot signed ITD difference over the source grid.
compare_itd_difference(
    hrtf_p0001,
    hrtf_p0002,
    output="samples",
    azimuth_range_mode="-180-180",
    colormap="viridis",
)

Plot ILD difference over the source grid

compare_ild_difference maps broad-band or frequency-dependent level differences between two HRTFs over the source grid. It helps identify where one HRTF has stronger or weaker binaural level cues than another.

The example below uses broad-band ILD in decibels and keeps the same azimuth convention as the ITD difference map, so the two spatial diagnostics are easier to compare.

# Plot broad-band ILD difference over the source grid.
compare_ild_difference(
    hrtf_p0001,
    hrtf_p0002,
    mode="broad-band",
    output="db",
    azimuth_range_mode="-180-180",
    colormap="magma",
)

Plot LSD over the source grid

compare_lsd computes log-spectral distortion between two HRTFs and plots one spectral-distance value per source position. This is useful for finding where two HRTFs differ most in spectral shape, without inspecting every frequency bin manually.

The example below compares the left ear and keeps the result as a spatial map. A high-value region means the two HRTFs are spectrally less similar at those source directions.

# Plot one frequency-averaged LSD value per source position.
compare_lsd(
    hrtf_p0001,
    hrtf_p0002,
    ear="left",
    azimuth_range_mode="-180-180",
    colormap="viridis",
)

Plot LSD on a spatial-frequency plane

compare_lsd_plane keeps the frequency axis instead of reducing LSD to one value per source position. The result is a heatmap where one axis is frequency, the other axis is a measured spatial plane, and color shows spectral distance.

The example below uses the horizontal plane for the left ear, a logarithmic frequency axis, and an upper frequency limit of 16 kHz. This view is useful when the spatial map shows a difference but the frequency range responsible for that difference is still unclear.

# Plot LSD as a horizontal-plane frequency-angle heatmap.
compare_lsd_plane(
    hrtf_p0001,
    hrtf_p0002,
    plane="horizontal",
    ear="left",
    elevation=0.0,
    x_axis="log",
    freq_max=16000.0,
    colormap="viridis",
)

Prepare spherical-harmonic reconstruction data

The spherical-harmonic plotting functions diagnose SHT reconstruction quality. Before plotting, compute a spherical-harmonic representation 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 SH-reconstructed spectra

sht_reconstruction_comparison overlays the original HRTF magnitude and the SH-reconstructed magnitude for one source direction and ear. This is useful for seeing whether the chosen SH order preserves the spectral shape at a specific direction, instead of relying only on global error metrics.

# 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,
)

Plot SH reconstruction error

sht_reconstruction_error plots the point-wise reconstruction error for one source direction and ear. This is the most direct view when the question is where the SH approximation loses 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,
)