hrtf_difference

hrtfpykit.hrtf.hrtf_difference(hrtf_reference, hrtfs, metric='rmse', ear='both', plane='all', plane_angle=0.0, positions=None, frequencies=None, frequency_bands=None, reduction_axis=None, reduction_method='mean', epsilon=1e-12)

Compute HRTF difference metrics against a reference HRTF.

hrtf_difference compares one reference HRTF with one or more HRTFs on the same source grid. "rmse", "mae", and "nrmse" compare IR.values. "lsd" compares TF.values. The function selects the requested sources and ears, computes the selected metric for each compared HRTF, source, and ear, and then applies any requested reduction.

metric="rmse", metric="mae", and metric="nrmse" use IR.values with shape (positions, ears, samples). The reference and compared HRTFs must provide matching IR shapes and matching IR sample rates. For each selected source and ear, the sample error is computed as compared - reference. RMSE returns sqrt(mean(error ** 2)) in linear amplitude units. MAE returns mean(abs(error)) in linear amplitude units. NRMSE returns sqrt(sum(error ** 2) / sum(reference ** 2)) as a ratio normalized by the reference, applies any requested reduction to that ratio, and finally converts the result to dB with 20 * log10.

metric="lsd" uses TF.values with shape (positions, ears, frequency_bins). The reference and compared HRTFs must provide matching TF shapes, matching TF.frequency_bins, and matching source positions. Magnitudes are converted to dB, compared as reference minus compared magnitude, and reduced over the selected frequency bins with an RMS operation. The resulting LSD values are in dB before any reduction_axis reduction is applied.

Source selection is resolved against hrtf_reference.Sources in spherical degrees. plane is applied first, then explicit positions queries are intersected with that plane selection. For LSD, frequencies maps requested frequencies to the nearest available TF bins and removes duplicate bin selections. frequency_bands selects inclusive frequency ranges. If neither frequency selector is provided for LSD, the metric uses available bins from 20 Hz to 20000 Hz.

Without reduction_axis, one compared HRTF returns the natural metric array with no leading comparison axis. Several compared HRTFs return a leading difference axis. reduction_axis="differences" reduces the compared HRTF axis, "position" or "positions" reduces selected source positions, "ears" reduces the ear axis, and "global" reduces all available metric axes. "source" and "sources" are accepted as aliases for the position axis. Reductions use either arithmetic mean or root mean square, according to reduction_method.

Parameters:
  • hrtf_reference (HRTF) – Reference HRTF. "rmse", "mae", and "nrmse" require IR.values and IR.sample_rate. metric="lsd" requires TF.values and TF.frequency_bins. All metrics require source positions.

  • hrtfs (HRTF or sequence of HRTF) – HRTF object or objects compared against hrtf_reference. Compared HRTFs must use the same source grid as the reference. "rmse", "mae", and "nrmse" also require matching IR shape and sample rate. metric="lsd" also requires matching TF shape and frequency bins.

  • metric ({"rmse", "mae", "nrmse", "lsd"}, default=``”rmse”``) – Difference metric. "rmse" and "mae" return linear amplitude error. "nrmse" and "lsd" return dB.

  • ear ({"left", "right", "both"}, default=``”both”``) – Ear channel selection. "left" uses ear channel 0, "right" uses ear channel 1, and "both" keeps both ears unless the ear axis is reduced.

  • plane ({"all", "horizontal", "median"}, default=``”all”``) – Spatial subset used before comparison.

  • plane_angle (float, default=0.0) – Plane coordinate in degrees. For plane="horizontal" this is spherical elevation. For plane="median" this is lateral-polar lateral angle.

  • positions (np.ndarray | list | tuple | str | None, default=None) – Optional source-position selector. Queries are resolved on the reference source grid and intersected with the selected plane.

  • frequencies (float, sequence of float, numpy.ndarray, or None, default=None) – Frequency selector in hertz for metric="lsd". Each requested frequency is mapped to the nearest available TF bin. Mutually exclusive with frequency_bands.

  • frequency_bands (pair, sequence of pairs, numpy.ndarray, or None, default=None) – Inclusive frequency band or bands in hertz for metric="lsd". Mutually exclusive with frequencies.

  • reduction_axis ({"differences", "positions", "ears", "global"}, sequence, or None, default=None) – Axis or axes reduced after metric values are computed. None returns the natural metric array. "differences" reduces the compared HRTF axis. "position" or "positions" reduces source positions. "ears" reduces ears and requires ear="both". "global" reduces all axes. "source" and "sources" are accepted as aliases for positions. Metric aliases such as "rmses", "maes", "nrmses", and "lsds" are accepted for the compared HRTF axis.

  • reduction_method ({"mean", "rms"}, default=``”mean”``) – Reduction method applied to selected metric values. "mean" computes the arithmetic mean. "rms" computes the root mean square.

  • epsilon (float, default=1e-12) – Positive lower bound used for NRMSE reference energy normalization, LSD magnitude flooring, and dB conversion.

Returns:

Difference values after the requested reduction. rmse and mae results are linear amplitude errors. nrmse and lsd results are in dB. A full global reduction returns a scalar float.

Return type:

numpy.ndarray or float

Raises:

ValueError – If any input is not an HRTF object, if hrtfs is empty, if selected metric data are missing, if source grids or metric shapes differ, if selected data are not arranged as (positions, ears, samples) or (positions, ears, frequency_bins), if option values are unsupported, if epsilon is not finite and positive, or if source or frequency selectors produce no data.

Examples

Compute one RMSE value per position for the left ear:

>>> from hrtfpykit.hrtf import hrtf_difference, load_hrtf
>>> reference = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> processed = reference.transform.apply_gain(gain=-1.0, scale="db")
>>> values = hrtf_difference(reference, processed, metric="rmse", ear="left")
>>> values.shape
(793,)

Compute one global NRMSE score in dB:

>>> nrmse_score = hrtf_difference(
...     reference,
...     processed,
...     metric="nrmse",
...     reduction_axis="global",
...     reduction_method="rms",
... )
>>> isinstance(nrmse_score, float)
True

Compute LSD over an explicit frequency band:

>>> lsd_values = hrtf_difference(
...     reference,
...     processed,
...     metric="lsd",
...     ear="both",
...     frequency_bands=(700.0, 1800.0),
...     reduction_axis="ears",
... )
>>> lsd_values.shape
(793,)