Fit the Model to Your Own HRTF¶
This guide shows how to estimate listener-specific model parameters from
measured sound-localisation responses. The output is a dictionary of fitted
noise parameters — sigma_spectral,
sigma_prior, and
sigma_motor — that characterise
that listener’s sensory precision and spatial expectations.
The fitting procedure follows a two-stage profile-likelihood approach (see Likelihood function for the equations):
Stage 1 (lateral only): estimate motor noise \(\sigma_\mathrm{m}\) from lateral responses using ITD and ILD cues, which dominate horizontal localisation and are largely independent of spectral processing.
Stage 2 (full sphere): fix \(\sigma_\mathrm{m}\) and fit \(\sigma_\mathrm{mon}\) (spectral noise) and \(\sigma_\mathrm{prior}\) (prior width) by maximising the full-sphere likelihood.
Note
sigma_itd (0.569) and
sigma_ild (1.0 dB) are fixed
at literature values throughout — they cannot be separated from
\(\kappa_\mathrm{m}\) along the same spatial dimension.
See Noise parameters for the identifiability analysis.
Prepare your data¶
You need:
A SOFA file for the listener’s HRTF.
A
pandas.DataFrameof measured responses with columnsazi_target,ele_target,azi_response,ele_response(all in degrees, spherical-elevation convention).A
pyfar.Coordinatesobject with the target directions.
import pandas as pd
import pyfar as pf
import numpy as np
obs_tbl = pd.read_csv(DATA_CSV)
targets = obs_tbl[["azi_target", "ele_target"]].drop_duplicates()
targets_coords = pf.Coordinates.from_spherical_elevation(
np.deg2rad(targets["azi_target"].values),
np.deg2rad(targets["ele_target"].values),
np.ones(len(targets)),
)
Run the two-stage fit¶
fit_listener runs both stages and returns
a results dictionary.
result = fit_listener(
sofa_path=sofa_path,
obs_tbl=obs_tbl,
num_repetitions=1,
targets_coords=targets_coords,
interpolation_method="SHMAX",
)
print(f"sigma_motor = {result['sigma_motor']:.2f} deg")
print(f"sigma_spectral = {result['sigma_spectral']:.2f} dB")
print(f"sigma_prior = {result['sigma_prior']:.2f} deg")
print(f"NLL = {result['nll']:.2f}")
Note
Fitting a single listener takes roughly 5–15 minutes depending on the
number of trials and Monte Carlo repetitions. Set num_repetitions=50
for a quick exploratory fit; use the default num_repetitions=200
for publishable results.
Inspect the result¶
The returned dictionary contains all fitted and fixed parameter values, timing information, and the final negative log-likelihood:
print(result["sigma_itd"]) # 0.569 (ITD noise, fixed)
print(result["sigma_ild"]) # 1.0 (ILD noise, fixed by default)
print(result["sigma_spectral"])
print(result["sigma_prior"])
print(result["kappa_motor"]) # concentration form of sigma_motor
print(result["n_trials"]) # number of responses used
print(result["time_total"]) # wall-clock seconds
What to do next¶
Use the fitted parameters to simulate responses with
Simulate Localization Responses, or compare fits across interpolation methods with
Compare Interpolation Methods. Full parameter documentation is in
fit_listener.