Bayesian Listener

The core class and auditory representation.

BayesianListener

The single entry point for all inference and simulation workflows.

class bayesian_listener.BayesianListener(sofa, sigma_itd=0.569, sigma_ild=1.0, sigma_spectral=10.4, sigma_prior=69.0, kappa_motor=23.31)[source]

Bases: object

Bayesian model of human static sound localisation from HRTFs.

Implements the generative pipeline of Barumerli et al.[1], validated and extended in Barumerli et al.[2]: noisy spatial features (ITD, ILD, monaural spectra) are extracted from a binaural stimulus, compared against direction-labelled HRTF templates, combined with an elevation prior, and finally perturbed by motor noise to yield a directional response.

See Model and Statistical Framework for the full equations and parameter definitions.

References

Examples

Load an HRTF, prepare features, and infer a single direction:

>>> from bayesian_listener import BayesianListener
>>> bl = BayesianListener('subject01.sofa')
>>> bl.compute_template(interpolation='SHMAX')
>>> posterior = bl.infer(repetitions=50, seed=0)
>>> response  = bl.estimate(posterior, seed=0)
compute_target(convention='Barumerli2023', spectral_range=None, use_cache=True, force_recompute=False, cache_dir=None)[source]

Extract auditory features from self.hrir and store them as target.

The target is the stimulus-side representation: ITD, ILD, and monaural spectral features computed at the measured HRTF directions. Each row of target.coords corresponds to one measured direction, preserving the original HRTF measurement grid. No interpolation is applied.

Call this method before compute_template when you need control over feature extraction parameters such as spectral_range or convention. If target is already set when compute_template is called, it will not be recomputed. Caching is available only when the listener was initialised with a SOFA file path; it is automatically disabled otherwise.

Parameters:
  • convention (str, default='Barumerli2023') – Auditory representation to use. Must be a key in bayesian_listener.auditory_representation.CONVENTIONS. Currently 'Barumerli2023' (ITD + ILD + spectral amplitudes) is the only fully implemented convention.

  • spectral_range (list of float or None, default=None) – [low_Hz, high_Hz] frequency limits of the gammatone filterbank used for the monaural cues. None selects [700.0, 18000.0].

  • use_cache (bool, default=True) – Load from cache if available and save after computing.

  • force_recompute (bool, default=False) – If True, ignore any cached target and recompute from scratch.

  • cache_dir (str or pathlib.Path or None, default=None) – Cache directory. None selects Path.cwd() / 'data' / 'preprocessed'.

Raises:

ValueError – If convention is not a registered key, or if sofa_file is None (listener was initialised with a sofar.Sofa object rather than a file path).

Examples

>>> bl = BayesianListener('subject01.sofa')
>>> bl.compute_target(spectral_range=[500, 16000])
>>> bl.target.features.shape
(793, 58)
compute_template(interpolation='SHMAX', interpolation_grid=None, use_cache=True, force_recompute=False, cache_dir=None)[source]

Build the template: the listener’s learned mapping from features to directions.

The template is constructed from the individual HRTF by extracting auditory features and interpolating them onto a quasi-uniform spherical grid (default: 2,112 directions at ~4° spacing). It represents the internal model the listener uses to infer source directions during infer.

If target has not been set, compute_target is called automatically with default parameters. For finer control over feature extraction (e.g. spectral range, convention), call compute_target explicitly before calling this method.

This function does not modify target if already set. Caching is available only when the listener was initialised with a SOFA file path; it is automatically disabled otherwise.

Parameters:
  • interpolation ({'SH', 'SHMAX', 'barycentric', 'barumerli2023'} or None, default='SHMAX') – Interpolation method used to resample features onto the uniform grid. Pass None to skip interpolation and set template equal to target (useful when the HRTF is already on a uniform grid).

  • interpolation_grid (pyfar.Coordinates or None, default=None) – Directions of the template grid. None selects a 64th-degree spherical t-design (2,112 directions, ~4° average spacing).

  • use_cache (bool, default=True) – Load from cache if available and save after computing. Requires a SOFA file path; raises ValueError if sofa_file is None.

  • force_recompute (bool, default=False) – If True, ignore any cached template and recompute from scratch.

  • cache_dir (str or pathlib.Path or None, default=None) – Cache directory. None selects Path.cwd() / 'data' / 'preprocessed'.

Raises:

ValueError – If sofa_file is None (i.e. listener was initialised with a sofar.Sofa object rather than a file path).

Examples

>>> bl = BayesianListener('subject01.sofa')
>>> bl.compute_template(interpolation='SHMAX')
>>> bl.template.features.shape[0]
2112

To control feature extraction before building the template:

>>> bl.compute_target(spectral_range=[500, 16000])
>>> bl.compute_template()
estimate(posterior, kappa_motor=None, seed=None)[source]

Convert posterior(s) into pointing responses with motor noise applied.

Selects the MAP template direction for each (trial, repetition) pair and perturbs it with isotropic motor noise drawn from \(\mathrm{vMF}(\mathbf{0}, \kappa_m)\) (Eq. 7 of Barumerli et al.[1]).

Parameters:
  • posterior (numpy.ndarray) –

    Output of infer. Either:

    • log-posteriors of shape (n_targets, repetitions, n_templates) (when infer was called with store_posterior=True), or

    • argmax indices of shape (n_targets, repetitions).

  • kappa_motor (float, False, 0 or None, default=None) –

    von Mises–Fisher concentration for the motor-noise step.

    • None — use self.parameters['kappa_motor'].

    • False or 0 — disable motor noise (return raw MAP directions).

    • any positive float — explicit concentration. Convert from a circular SD with sigma_to_kappa.

  • seed (int or None, default=None) – Seed for the motor-noise RNG.

Returns:

Pointing responses with cartesian array shape (n_targets, repetitions, 3).

Return type:

pyfar.Coordinates

Raises:

ValueError – If posterior has zero rows.

Examples

>>> idx       = bl.infer(repetitions=50, seed=0)
>>> responses = bl.estimate(idx, seed=0)
>>> responses.cartesian.shape
(793, 50, 3)
infer(repetitions=50, prior='horizontal', store_posterior=False, seed=None)[source]

Run Monte Carlo Bayesian inference of source direction.

For each target direction, draw repetitions noisy feature samples \(\mathbf{t}\) from \(\mathcal{N}(\mathbf{s}(\boldsymbol{\varphi}), \boldsymbol{\Sigma})\), then compute the log-posterior \(p(\boldsymbol{\varphi} \mid \mathbf{t}) \propto p(\mathbf{t} \mid \boldsymbol{\varphi})\, p(\boldsymbol{\varphi})\) (Eq. 3 of Barumerli et al.[1]) over all template directions.

Parameters:
  • repetitions (int, default=50) – Number of Monte Carlo trials per target direction.

  • prior ({‘uniform’, ‘horizontal’} or numpy.ndarray, default=’horizontal’) –

    Direction prior \(p(\boldsymbol{\varphi})\):

    • 'uniform' — flat prior over template directions.

    • 'horizontal' — Gaussian prior over elevation with width sigma_prior (Eq. 5 of Barumerli et al.[1]).

    • array of shape (n_templates,) — custom unnormalised prior; normalised internally.

  • store_posterior (bool, default=False) – If True, return the full normalised log-posterior over templates. If False, return only argmax indices (memory-light).

  • seed (int or None, default=None) – Seed for numpy.random.default_rng. None yields a non-reproducible run.

Returns:

If store_posterior=True: log-posterior of shape (n_targets, repetitions, n_templates) (rows sum to 1 in linear domain). Otherwise: integer template indices of shape (n_targets, repetitions).

Return type:

numpy.ndarray

Raises:

Examples

>>> bl.compute_target(); bl.compute_template()
>>> idx = bl.infer(repetitions=200, seed=0)
>>> idx.shape
(793, 200)
property kappa_motor

\(\kappa_m\), the von Mises–Fisher motor-noise concentration.

localise(target=None, directions=None, repetitions=None, seed=None)[source]

Run the full localisation pipeline and return pointing responses.

Convenience wrapper around infer and estimate. Ensures the template (the listener’s internal directional model) and the target (the stimulus features to be localised) are both available, then draws repetitions noisy percepts via Monte Carlo inference and converts each MAP estimate into a pointing response perturbed by motor noise.

Parameters:
  • target (_AuditoryRepresentation or None, default=None) – Auditory representation of the source directions to localise. Must be the same subclass as template. None reuses target, computing it via compute_target if not already set. When combined with directions, acts as the search pool from which directions are selected.

  • directions (pyfar.Coordinates or None, default=None) – Desired source directions. The nearest neighbours in the resolved target are selected via great-circle distance. A UserWarning is raised for any match farther than 10°. Raises ValueError if the intersection is empty.

  • repetitions (int or None, default=None) – Number of Monte Carlo trials passed to infer. None uses the default of infer (50).

  • seed (int or None, default=None) – Seed for numpy.random.default_rng. None yields a non-reproducible run.

Returns:

Pointing responses of shape (n_targets, repetitions, 3).

Return type:

pyfar.Coordinates

Raises:
  • ValueError – If target and directions are both provided.

  • ValueError – If target has a different convention than template.

  • ValueError – If directions finds no match within the provided or stored target (empty intersection).

Examples

>>> responses = bl.localise(repetitions=1, seed=0)
>>> responses.cartesian.shape
(793, 1, 3)
property parameters

Noise and prior parameters as a dict.

Required keys: sigma_itd, sigma_ild, sigma_spectral, sigma_prior, kappa_motor. The setter raises ValueError if any key is missing.

property sigma_ild

\(\sigma_{\mathrm{ild}}\), the ILD noise in dB.

property sigma_itd

\(\sigma_{\mathrm{itd}}\), the warped-ITD noise (dimensionless).

property sigma_motor

Motor-noise circular standard deviation \(\sigma_m\) in degrees.

Computed from kappa_motor via the Bessel-ratio identity \(R = I_1(\kappa_m)/I_0(\kappa_m) = \exp(-\sigma_m^2/2)\). See kappa_to_sigma.

property sigma_prior

\(\sigma_{\mathrm{prior}}\), the elevation prior width in degrees.

property sigma_spectral

\(\sigma_{\mathrm{mon}}\), the monaural spectral noise in dB.

property target

Auditory representation of the stimulus, or None if not computed.

Set by compute_target. Assigning a value validates that it is a Barumerli2023 instance (or subclass, or None); otherwise raises TypeError.

property template

Listener’s internal template, or None if not computed.

Set by compute_template. Assigning a value validates that it is a Barumerli2023 instance (or subclass, or None); otherwise raises TypeError.

Auditory Representation

The concrete auditory representation used by the default workflow: ITD + ILD + monaural spectral amplitude envelopes (Barumerli et al.[2]).

class bayesian_listener.Barumerli2023(convention='Barumerli2023', coords=None, itd=None, ild=None, spectral_cues=None, freqs=None)[source]

Bases: _AuditoryRepresentation

ITD + ILD + spectral-amplitude representation of Barumerli et al.[2].

Builds the feature vector \(\mathbf{t} = [x_{\mathrm{itd}}, x_{\mathrm{ild}}, \mathbf{x}_{L,\mathrm{mon}}, \mathbf{x}_{R,\mathrm{mon}}]\) (Eq. 1 of Barumerli et al.[1]) with monaural cues as gammatone-bank log-amplitudes. n_features = 2 + 2 * n_freqs where n_freqs is typically 28 (ERB-spaced centre frequencies between 700 Hz and 18 kHz).

Parameters:
convention

Fixed to 'Barumerli2023'.

Type:

str

coords

Source positions, one per row.

Type:

pyfar.Coordinates

itd

Warped interaural time differences (signed log of ITD in seconds, as produced by compute_features), shape (n_dirs, 1).

Type:

numpy.ndarray

ild

Interaural level differences in dB, shape (n_dirs, 1).

Type:

numpy.ndarray

spectral_cues

Monaural log-amplitude spectra for the left and right ears in dB, shape (n_dirs, n_freqs, 2).

Type:

numpy.ndarray

freqs

Filterbank centre frequencies in Hz, shape (n_freqs,).

Type:

numpy.ndarray

features

Pre-computed concatenation [itd, ild, spectral_L, spectral_R] of shape (n_dirs, 2 + 2*n_freqs). Built in __post_init__.

Type:

numpy.ndarray

Examples

Construct from raw HRIRs via the helper in bayesian_listener.utils:

>>> from bayesian_listener.utils import compute_features
>>> itd, ild, spec, freqs = compute_features(hrir, coords, fs)
>>> repr_ = Barumerli2023(coords=coords, itd=itd, ild=ild,
...                       spectral_cues=spec, freqs=freqs)
>>> repr_.features.shape[1] == 2 + 2 * freqs.size
True
sigma_matrix(parameters)[source]

Diagonal sensory covariance (Eq. 2 of Barumerli et al.[1]).

Builds \(\boldsymbol{\Sigma} = \mathrm{diag}( \sigma_{\mathrm{itd}}^2,\, \sigma_{\mathrm{ild}}^2,\, \sigma_{\mathrm{mon}}^2 \mathbf{I}_{2\,n_{\mathrm{freqs}}})\).

Parameters:

parameters (dict) – Must contain keys sigma_itd (dimensionless), sigma_ild (dB), sigma_spectral (dB).

Returns:

Diagonal covariance of shape (n_features, n_features).

Return type:

numpy.ndarray

Examples

>>> Sigma = repr_.sigma_matrix(
...     {'sigma_itd': 0.569, 'sigma_ild': 1.0, 'sigma_spectral': 10.4}
... )
>>> Sigma.shape == (repr_.features.shape[1],) * 2
True