HRTF

class hrtfpykit.hrtf.HRTF(Sofa=None)

Bases: object

Represent an HRTF or HRIR object loaded from SOFA data.

HRTF is the main in-memory object used to inspect, subset, transform, synchronize, and save HRTF/HRIR data loaded from SOFA files. It supports the SimpleFreeFieldHRIR and SimpleFreeFieldHRTF conventions and keeps both acoustic representations available:

  • IR: time-domain impulse responses

  • TF: frequency-domain transfer functions

The object stores acoustic arrays separately from the backing SOFA object. Transformations and selections operate on the in-memory arrays first; SOFA variables are updated only when update_sofa() or save() is called. This makes processing workflows explicit and prevents intermediate edits from being written to the file representation automatically.

Spatial metadata and source-grid operations are exposed through Sources. That object resolves SOFA SourcePosition data, named directions, coordinate-system conversion, and plane-based queries used by processing, metrics, dataset extraction, and hrtfpykit.plots functions.

The object stores the optional SOFA backing object and starts with empty metadata fields. Domain interface objects such as IR, TF, Sources, and Transform are created lazily when their properties are first accessed. Raw SOFA variables are not parsed and missing domains are not derived here; load_hrtf() performs that loading and synchronization work.

Notes

A typical workflow is to load an object with load_hrtf(), inspect or subset data with Sources and select(), apply transforms through transform, visualize by passing the object to hrtfpykit.plots functions, then synchronize and export with update_sofa() and save().

The instance keeps a reference to the backing SOFA object in Sofa. Transformation state is tracked internally; selected source subsets are tracked separately by Sources. Mesh2HRTF compatible loading state is also stored so reset and TF-domain workflows can reconstruct HRIR data with the same convention used at load time.

Parameters:

Sofa (SOFA | None, default=None) – SOFA object that backs the HRTF instance. When None, the object is created empty and should be populated later.

Sofa

Backing SOFA object used for source metadata, persistence, and SOFA synchronization.

Type:

SOFA or None

SOFAConventions

Active SOFA convention associated with the loaded or constructed HRTF object.

Type:

str or None

fft_length

FFT length used when synchronizing between IR and TF representations.

Type:

int or None

mesh2hrtf_compatible

Whether SimpleFreeFieldHRTF data should use Mesh2HRTF compatible TF-to-IR reconstruction.

Type:

bool

mesh2hrtf_n_shift

Circular shift in samples used when mesh2hrtf_compatible is True.

Type:

int or None

_transformed

Internal flag indicating whether the in-memory acoustic data were produced by a transform workflow.

Type:

bool

property IR: IR

Access the time-domain HRIR representation object.

This object stores IR.values and IR.sample_rate for the parent HRTF object and exposes time-domain inspection helpers such as sample length and duration.

Examples

Load a SOFA file and access the HRIR samples, sample-rate metadata, signal length, and duration through hrtf.IR, then compute ITD with the public metric:

>>> from hrtfpykit.hrtf import load_hrtf, itd
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> ir_values = hrtf.IR.values
>>> sample_rate = hrtf.IR.sample_rate
>>> first_position_left_ir = hrtf.IR.values[0, 0, :]
>>> itd_samples = itd(hrtf, output="samples")
>>> ir_values.shape
(793, 2, 256)
>>> sample_rate
44100.0
>>> first_position_left_ir.shape
(256,)
>>> hrtf.IR.ir_length
256
>>> round(hrtf.IR.ir_duration, 3)
5.805
>>> itd_samples.shape
(793,)
property TF: TF

Access the frequency-domain HRTF representation object.

This object stores complex TF.values and TF.frequency_bins for the parent object and exposes derived magnitude, phase, real, and imaginary views used by transforms, metrics, and plots.

Examples

Load a SOFA file and access the frequency-domain HRTF values, frequency axis, bin metadata, and derived spectral arrays through hrtf.TF:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.values.shape
(793, 2, 129)
>>> hrtf.TF.values.dtype
dtype('complex128')
>>> hrtf.TF.frequency_bins[:5]
array([  0.      , 172.265625, 344.53125 , 516.796875, 689.0625  ])
>>> hrtf.TF.tf_length
129
>>> hrtf.TF.frequency_bins_step
172.265625
>>> hrtf.TF.min_frequency_bin
0.0
>>> hrtf.TF.max_frequency_bin
22050.0
>>> hrtf.TF.magnitude.shape
(793, 2, 129)
>>> hrtf.TF.get_magnitude_db().shape
(793, 2, 129)
>>> hrtf.TF.phase.shape
(793, 2, 129)
>>> hrtf.TF.real.shape
(793, 2, 129)
>>> hrtf.TF.imag.shape
(793, 2, 129)
property Sources: Sources

Access the spatial source-grid object.

Sources reads SOFA SourcePosition data, converts between the supported coordinate systems, resolves named positions, and tracks selected source indices after spatial subsetting.

Examples

Load a SOFA file and access source-grid positions, coordinate-system metadata, available angles, nearest-position matches, and selected source views through hrtf.Sources:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.source_coordinate_system
'spherical'
>>> hrtf.Sources.get_positions().shape
(793, 3)
>>> hrtf.Sources.get_positions()[:3]
array([[  0. , -45. ,   1.5],
       [  0. , -30. ,   1.5],
       [  0. , -20. ,   1.5]])
>>> hrtf.Sources.get_positions(angle_unit="radians")[0]
array([ 0.        , -0.78539816,  1.5       ])
>>> hrtf.Sources.get_azimuth_angles()[:5]
array([ 0.,  5., 10., 15., 20.])
>>> hrtf.Sources.get_elevation_angles()
array([-45., -30., -20., -10.,   0.,  10.,  20.,  30.,  45.,  60.,  75.,
        90.])
>>> elevations_at_front, real_azimuth = (
...     hrtf.Sources.get_elevation_angles_for_azimuth(0.0)
... )
>>> elevations_at_front[:5]
array([-45., -30., -20., -10.,   0.])
>>> real_azimuth
0.0
>>> azimuths_on_horizontal, real_elevation = (
...     hrtf.Sources.get_azimuth_angles_for_elevation(0.0)
... )
>>> azimuths_on_horizontal[:5]
array([ 0.,  5., 10., 15., 20.])
>>> real_elevation
0.0
>>> hrtf.Sources.get_position_index("front")
(4, array([0. , 0. , 1.5]))
>>> selected = hrtf.select(positions=["front", "left", "right"])
>>> selected.Sources.get_positions().shape
(3, 3)
property transform: Transform

Access the immutable transformation interface for this HRTF.

Methods on this object clone the current HRTF, apply one processing operation, synchronize the affected IR or TF representation, and return the derived HRTF without mutating the original instance.

clone()

Create a deep clone of the current HRTF object.

The clone receives copied IR and TF arrays, sample-rate and frequency-bin metadata, FFT length, Mesh2HRTF compatible reconstruction settings, transformation state, and source selection state. If the source object is backed by an open SOFA dataset, the clone receives an independent in-memory SOFA clone. If the source SOFA dataset is closed, the clone keeps a closed SOFA object and uses the copied in-memory IR, TF, and Sources data for runtime operations.

Returns:

New object with copied acoustic arrays, domain metadata, source selection state, SOFA convention metadata, Mesh2HRTF compatible loading state, transformation flag, and matching SOFA open/closed state.

Return type:

HRTF

Examples

Clone a loaded HRTF before changing array values so the original object remains unchanged:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf_copy = hrtf.clone()
>>> hrtf_copy.IR.values[0, 0, 0] += 1.0
>>> hrtf_copy.IR.values[0, 0, 0] == hrtf.IR.values[0, 0, 0]
False
reset()

Reset in-memory HRTF data to the backed SOFA content.

This method discards current in-memory acoustic edits and reloads the active domain data from Sofa. The backed SOFA dataset must be open. HRIR files are restored from Data.IR and Data.SamplingRate and then converted to TF. HRTF files are restored from Data.Real, Data.Imag, and N and then converted to IR. If the object was loaded with Mesh2HRTF compatible reconstruction, reset uses the same compatibility flag and sample shift. Source selections are cleared when the Sources object has already been initialized.

Returns:

Current instance after restoring IR/TF, source-state, and metadata from the backed SOFA object while keeping the stored Mesh2HRTF compatible reconstruction settings.

Return type:

HRTF

Raises:

ValueError – If no SOFA file is attached, the SOFA file is not loaded, the SOFA dataset is closed, the convention is unsupported, or required acoustic variables are missing or empty.

Examples

Restore a selected HRTF object from its backed SOFA content:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> selected = hrtf.select(positions=["front", "left", "right"])
>>> selected.IR.values.shape
(3, 2, 256)
>>> restored = selected.reset()
>>> restored.IR.values.shape
(793, 2, 256)
>>> restored.is_transformed()
False
is_transformed()

Return the current HRTF transformation flag.

The flag is set by transform workflows that modify acoustic data. It is a workflow state indicator, not a byte-by-byte comparison between in-memory arrays and the backing SOFA object. Source selection is tracked separately on Sources and is handled independently by update_sofa().

Returns:

True if a transform workflow has marked the object as transformed; False otherwise.

Return type:

bool

Examples

Check whether a transform returned a derived HRTF while the original object stayed unchanged:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> windowed = hrtf.transform.apply_window("hann")
>>> hrtf.is_transformed()
False
>>> windowed.is_transformed()
True
update_sofa(change_sofa_dimensions=False, sofa_convention='same')

Synchronize in-memory IR/TF data into the backed SOFA object.

The method converts the current acoustic representation into the requested SOFA convention and writes the corresponding SOFA variables on a cloned or updated SOFA object. It updates HRIR output through Data.IR and Data.SamplingRate; HRTF output through Data.Real, Data.Imag, and N. Obsolete variables from the opposite convention are removed when the output convention changes. If a SimpleFreeFieldHRTF file omitted the DC bin at load time, synchronization writes the normalized TF with the inserted DC bin.

Dimension handling is conservative. If transformed data no longer fit existing SOFA dimensions, synchronization raises unless change_sofa_dimensions=True. Source selections are written as real M dimension changes, including single-source selections with M=1. Ear-selected HRTFs stay valid in memory, but SOFA synchronization requires both left and right receiver channels. When resizing is allowed, supported dependent variables on the measurement axis are subset with the current source selection; unsupported dependent variables still raise explicit errors to avoid silently corrupting SOFA structure.

The method updates the in-memory Sofa object only and requires its backing SOFA dataset to be open. Use save() to persist the synchronized SOFA object to disk. When TF values must be converted back to IR during synchronization, the stored Mesh2HRTF compatible reconstruction settings are reused.

Parameters:
  • change_sofa_dimensions (bool, default=False) – If True, allows resizing fixed SOFA dimensions when transformed data shape differs from backed variables.

  • sofa_convention ({same, SimpleFreeFieldHRIR, SimpleFreeFieldHRTF}, default=``same``) – Output SOFA convention to enforce during synchronization. same keeps the original backed SOFA convention.

Returns:

This method updates Sofa in-place and does not return data.

Return type:

None

Raises:

ValueError – If no SOFA file is loaded, the SOFA dataset is closed, the convention is unsupported, required domain values are missing, transformed shapes cannot be represented by the SOFA dimensions, or requested dimension changes would affect variables that the method cannot update safely.

Examples

Synchronize a selected source subset into the backed SOFA object before saving or inspecting SOFA variables:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> selected = hrtf.select(positions=["front", "left", "right"])
>>> selected.update_sofa(change_sofa_dimensions=True)
>>> source_positions = selected.Sofa.Variables.get("SourcePosition").value
>>> source_positions.shape
(3, 3)
save(path=None, overwrite=False, change_sofa_dimensions=False, sofa_convention='same')

Update SOFA variables and save to disk.

This is the persistence endpoint for the HRTF workflow. It first calls update_sofa() so the backed SOFA object reflects the current in-memory IR/TF state and requested convention, then delegates the disk write to save(). The backed SOFA dataset must be open.

Parameters:
  • path (str | Path | None, default=None) – Output file path. If None, the current backed SOFA path is used by the underlying SOFA object.

  • overwrite (bool, default=False) – If True, allows overwriting an existing file.

  • change_sofa_dimensions (bool, default=False) – Forwarded to update_sofa() to control SOFA dimension resizing.

  • sofa_convention ({same, SimpleFreeFieldHRIR, SimpleFreeFieldHRTF}, default=``same``) – Forwarded to update_sofa() to select output convention.

Returns:

Path to the saved SOFA file.

Return type:

Path

Raises:
  • ValueError – If no SOFA file is attached, the SOFA dataset is closed, or synchronization fails.

  • FileExistsError – If the target path already exists and overwrite=False.

Examples

Save a processed HRTF to a new SOFA file using a relative output path:

>>> from pathlib import Path
>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> windowed = hrtf.transform.apply_window("hann")
>>> output_dir = Path("processed")
>>> output_dir.mkdir(exist_ok=True)
>>> saved_path = windowed.save(
...     output_dir / "P0001_windowed.sofa",
...     overwrite=True,
... )
>>> saved_path.name
'P0001_windowed.sofa'
select(positions=None, position_coordinate_system='spherical', plane=None, plane_angle=0.0, azimuth_angles=None, elevation_angles=None, ear='both', angle_unit='degrees', start_sample=None, end_sample=None)

Select a spatial subset, ear subset, and/or IR crop from the HRTF.

Selection returns a cloned HRTF object and leaves the original object unchanged. Spatial selection can be expressed with explicit positions, named positions, a geometric plane, or azimuth/elevation angle filters. These source-selection modes are mutually exclusive: positions cannot be combined with plane or angle filters, and plane cannot be combined with angle filters. azimuth_angles and elevation_angles may be used together; when both are provided, selected sources must satisfy both filters. Source selections are applied along the leading source axis of both IR.values and TF.values when those domains are available.

Ear selection keeps both ears by default. Selecting left or right removes the ear axis entry from available IR and TF arrays. IR cropping is applied along the final sample axis and automatically recomputes the TF representation from the cropped IR using the cropped IR length as the FFT length.

Parameters:
  • positions (np.ndarray | list[list[float]] | list[float] | None, default=None) – Explicit positions or named aliases to select. Named positions use the source-grid aliases such as front, back, left, and right. Numeric positions are interpreted in position_coordinate_system.

  • position_coordinate_system ({spherical, cartesian, lateral-polar}, default=``spherical``) – Coordinate system used by numeric positions queries.

  • plane (str | None, default=None) – Plane name to filter positions. Supported values are horizontal, median, and frontal.

  • plane_angle (float, default=0.0) – Plane coordinate used to resolve the nearest available plane. For plane="horizontal" this is spherical elevation. For plane="median" this is lateral-polar lateral angle. For plane="frontal" this is spherical azimuth.

  • azimuth_angles (float, sequence of float, numpy.ndarray, or None, default=None) – Azimuth angle or angles used to keep matching source positions. Requested values resolve to the nearest available source-grid azimuth in spherical coordinates and may be combined only with elevation_angles.

  • elevation_angles (float, sequence of float, numpy.ndarray, or None, default=None) – Elevation angle or angles used to keep matching source positions. Requested values resolve to the nearest available source-grid elevation in spherical coordinates and may be combined only with azimuth_angles.

  • ear ({both, left, right}, default=``both``) – Ear selection.

  • angle_unit ({degrees, radians}, default=``degrees``) – Angle unit used for spatial queries and plane angles.

  • start_sample (int | None, default=None) – IR crop start sample index.

  • end_sample (int | None, default=None) – IR crop end sample index.

Returns:

New HRTF object containing the selected subset.

Return type:

HRTF

Raises:

ValueError – If the requested selection is invalid, no positions remain after filtering, more than one source-selection mode is requested, the requested ear is unavailable, crop boundaries are invalid, or IR data are unavailable for cropping.

Examples

Select three named directions, keep only the left ear, and crop the HRIR samples used in the returned HRTF:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.values.shape
(793, 2, 256)
>>> selected = hrtf.select(
...     positions=["front", "left", "right"],
...     ear="left",
...     start_sample=0,
...     end_sample=128,
... )
>>> selected.IR.values.shape
(3, 128)
>>> selected.TF.values.shape
(3, 65)
>>> selected.fft_length
128

Composed interfaces

HRTF keeps the loaded HRTF/HRIR state in one object and exposes related data and operations through composed interface properties. In normal use, users do not create those interface objects directly. They load or receive an HRTF instance and access each working surface from that instance: IR for the IR object with time-domain HRIR values, sample-rate metadata, ir_duration; use itd() for interaural time difference metrics, ild() for interaural level difference metrics, and rms() for RMS levels. TF for the TF object with frequency-domain HRTF values, frequency bins, magnitude, phase, real, and imag; Sources for the Sources object with get_positions(), coordinate conversion, named position queries, and selection state; and transform for the Transform object with immutable processing operations.

Plotting is intentionally separate from the core HRTF class. Use hrtfpykit.plots functions such as plot_magnitude() or plot_source_grid() and pass the loaded HRTF object as the first argument. This keeps visualization in the plotting layer while still using the current IR, TF, and source-grid state of the object.

class hrtfpykit.hrtf.domain.IR(hrtf)

Represent the time-domain HRIR view owned by an HRTF object.

IR stores the HRIR sample array and sample-rate metadata used by the parent HRTF abstraction. It is created lazily by IR and acts as the in-memory time-domain view of SOFA Data.IR data or data reconstructed from SimpleFreeFieldHRTF frequency-domain files.

The object does not own independent source metadata. Its leading axes are expected to stay aligned with Sources and with the sibling TF representation. Time-domain transforms update IR.values and IR.sample_rate first, then synchronize TF.values and TF.frequency_bins through FFT helpers.

Parameters:

hrtf (HRTF) – Parent HRTF object that owns this time-domain representation.

values

Time-domain impulse-response values. Standard HRTF data use layout (positions, ears, samples), but methods generally treat any leading axes before the final sample axis as preserved metadata axes.

Type:

numpy.ndarray or None

sample_rate

Sampling rate in hertz for the impulse-response data.

Type:

float or None

Examples

Load an HRIR-backed SOFA file and inspect the time-domain array exposed by the HRTF object:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.values.shape
(793, 2, 256)
>>> hrtf.IR.sample_rate
44100.0
property ir_length: int

Return the number of HRIR samples along the final axis.

The value is derived from the final axis of IR.values and therefore reflects the current in-memory time-domain representation, including any padding, resampling, or replacement performed through transform.

Returns:

Number of samples on the final axis of IR.values.

Return type:

int

Raises:

AttributeError – If IR.values is None.

Examples

Load an HRTF and read the number of samples in each HRIR:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.ir_length
256
property ir_duration: float

Return the current HRIR duration in milliseconds.

Duration is computed from the sample count and IR.sample_rate using the shared DSP duration helper. Leading source and ear axes do not affect the result; only the final sample axis is used.

Returns:

Duration in milliseconds, computed from IR.values and IR.sample_rate.

Return type:

float

Raises:

ValueError – If IR.values is missing, not a NumPy array, has no sample axis, or IR.sample_rate is missing or not finite and positive.

Examples

Load an HRTF and compute the duration represented by each HRIR:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> round(hrtf.IR.ir_duration, 3)
5.805
class hrtfpykit.hrtf.domain.TF(hrtf)

Represent the frequency-domain HRTF view owned by an HRTF object.

TF stores the complex HRTF frequency-response array and its frequency bins for the parent HRTF abstraction. It is created lazily by TF and acts as the in-memory frequency-domain view of SOFA Data.Real and Data.Imag data or data computed from HRIR files.

The object is expected to stay aligned with the sibling IR and Sources representations. Frequency-domain transforms update TF.values and TF.frequency_bins first, then synchronize IR.values and IR.sample_rate through inverse FFT helpers.

Parameters:

hrtf (HRTF) – Parent HRTF object that owns this frequency-domain representation.

values

Complex frequency-domain transfer-function values. Standard HRTF data use layout (positions, ears, frequency_bins), but derived properties preserve any leading axes before the final frequency axis.

Type:

numpy.ndarray or None

frequency_bins

One-dimensional frequency-bin values in hertz corresponding to the final axis of TF.values.

Type:

numpy.ndarray or None

Examples

Load an HRIR SOFA file and inspect the derived frequency-domain view:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.values.shape
(793, 2, 129)
>>> hrtf.TF.frequency_bins.shape
(129,)
property tf_length: int

Return the number of HRTF frequency bins along the final axis.

The value is derived from the final axis of TF.values and corresponds to the current one-sided frequency-domain representation used by the HRTF object.

Returns:

Number of frequency bins on the final axis of TF.values.

Return type:

int

Raises:

AttributeError – If TF.values is None.

Examples

Read the number of one-sided frequency bins in the current HRTF:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.tf_length
129
property frequency_bins_step: float | None

Return the frequency-bin spacing in hertz when it is uniform.

The method compares consecutive entries in TF.frequency_bins. It returns the common spacing for uniformly sampled spectra and None when the bins are not uniformly spaced.

Returns:

Uniform spacing in hertz, or None when consecutive frequency differences are not equal within the local tolerance.

Return type:

float | None

Raises:
  • ValueError – If TF.frequency_bins is None.

  • IndexError – If fewer than two frequency bins are available.

Examples

Inspect the spacing of a uniformly sampled one-sided frequency grid:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> round(hrtf.TF.frequency_bins_step, 3)
172.266
property min_frequency_bin: float

Return the minimum available frequency bin in hertz.

This is normally 0 Hz for one-sided spectra loaded from HRIR data, but it reflects the current TF.frequency_bins array exactly.

Returns:

Minimum value in TF.frequency_bins.

Return type:

float

Raises:

Examples

Read the lowest available frequency bin:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.min_frequency_bin
0.0
property max_frequency_bin: float

Return the maximum available frequency bin in hertz.

For uniformly sampled one-sided spectra, this usually corresponds to the Nyquist frequency implied by the IR sample rate and FFT length.

Returns:

Maximum value in TF.frequency_bins.

Return type:

float

Raises:

Examples

Read the highest available frequency bin:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.max_frequency_bin
22050.0
property magnitude: ndarray

Return the linear magnitude of the complex HRTF values.

The result has the same source, ear, and frequency-bin layout as TF.values and is computed through the shared DSP magnitude helper.

Returns:

Linear magnitude values with the same shape as TF.values.

Return type:

numpy.ndarray

Raises:

ValueError – If TF.values is missing or is not a NumPy array.

Examples

Compute linear magnitude without modifying the complex transfer function:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> magnitude_values = hrtf.TF.magnitude
>>> magnitude_values.shape
(793, 2, 129)
get_magnitude_db(reference=1.0)

Return TF magnitude in decibels.

This method computes the linear magnitude of TF.values and converts it with the shared dB conversion helper. It is the domain-object convenience API used by plotting and spectral inspection workflows.

Parameters:

reference (float | {max}, default=1.0) – Positive reference magnitude used for 20 * log10(magnitude / reference). The special value max normalizes to the maximum magnitude present in TF.values.

Returns:

Magnitude values in decibels with the same shape as TF.values.

Return type:

numpy.ndarray

Raises:

ValueError – If TF.values is missing or invalid, if a magnitude is invalid, or if reference is not accepted by the dB conversion helper.

Examples

Convert the current transfer-function magnitude to decibels:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> magnitude_db = hrtf.TF.get_magnitude_db()
>>> magnitude_db.shape
(793, 2, 129)
property phase: ndarray

Return the phase of the complex HRTF values in degrees.

The result has the same shape as TF.values and uses the library phase helper so phase handling is consistent with transform methods.

Returns:

Phase values in degrees with the same shape as TF.values.

Return type:

numpy.ndarray

Raises:

ValueError – If TF.values is missing or is not a NumPy array.

Examples

Inspect phase values for every source, ear, and frequency bin:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> phase_values = hrtf.TF.phase
>>> phase_values.shape
(793, 2, 129)
property real: ndarray

Return the real component of the complex HRTF values.

This property exposes Data.Real-style values for the current frequency-domain representation without modifying the parent HRTF.

Returns:

Real component of TF.values with the same shape.

Return type:

numpy.ndarray

Raises:

ValueError – If TF.values is missing or is not a NumPy array.

Examples

Access the SOFA-style real component of the complex HRTF:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> real_values = hrtf.TF.real
>>> real_values.shape
(793, 2, 129)
property imag: ndarray

Return the imaginary component of the complex HRTF values.

This property exposes Data.Imag-style values for the current frequency-domain representation without modifying the parent HRTF.

Returns:

Imaginary component of TF.values with the same shape.

Return type:

numpy.ndarray

Raises:

ValueError – If TF.values is missing or is not a NumPy array.

Examples

Access the SOFA-style imaginary component of the complex HRTF:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> imag_values = hrtf.TF.imag
>>> imag_values.shape
(793, 2, 129)
class hrtfpykit.hrtf.sources.Sources(hrtf=None)

Manage source positions, coordinate systems, and spatial selections.

Sources is the source-position manager used by HRTF. It stores an in-memory copy of SOFA SourcePosition values and their SourcePosition:Type and SourcePosition:Units attributes, converts positions on demand, and resolves source-grid queries used by selection, metrics, spherical harmonics, and plotting utilities.

The manager stores the target coordinate system in source_coordinate_system. Changing that value changes how get_positions() and query methods expose positions; it does not rewrite the stored SOFA SourcePosition array by itself. Spatial subsets created through select() are also respected, so returned arrays and matched indices refer to the current HRTF view rather than necessarily to every source in the original SOFA file.

Notes

Conventions implemented by this class:

  • Spherical (SOFA-style): (azimuth, elevation, radius) with azimuth in [0, 360) degrees (anticlockwise in horizontal plane), elevation in [-90, 90] degrees (positive up), and non-negative radius.

  • Lateral-polar: (lateral, polar, radius) with lateral in [-90, 90] degrees (positive left), polar normalized to [-90, 270) degrees, and non-negative radius.

  • Cartesian: (x, y, z) with +y as left and +z as up.

At lateral poles (abs(lateral) == 90) and at zero radius, polar is singular. This implementation uses a deterministic placeholder polar = 0.

Parameters:

hrtf (HRTF | None, default=None) – Owning HRTF instance. Most user code obtains this object from Sources. When provided, the HRTF must have a loaded SOFA object containing SourcePosition metadata.

source_coordinate_system

Target coordinate system used by source-grid query methods.

Type:

str

_selected_indices

Source-position indices retained by the current HRTF view after spatial selection.

Type:

numpy.ndarray or None

Examples

Load an HRTF and inspect the source manager exposed by the HRTF object:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.source_coordinate_system
'spherical'
>>> hrtf.Sources.get_positions().shape
(793, 3)
get_positions(angle_unit='degrees', coordinate_system='spherical', plane=None, plane_angle=None)

Return the current source grid in the requested coordinate system.

Positions are read from the in-memory SourcePosition snapshot stored on this object. The source coordinate system is taken from SourcePosition:Type and converted on read. By default, positions are returned in spherical coordinates. If the owning HRTF has been spatially selected, only the selected source rows are returned. If plane is provided, hrtfpykit first resolves the nearest measured plane, keeps only those source rows, and then returns them in coordinate_system.

Parameters:
  • angle_unit ({degrees, radians}, default=``degrees``) – Angular unit used for returned spherical or lateral-polar angles. Cartesian coordinates are returned in their stored distance unit.

  • coordinate_system ({spherical, cartesian, lateral-polar}, default=``spherical``) – Coordinate system used for returned positions.

  • plane ({horizontal, median, frontal} or None, default=None) –

    Optional source plane used to filter returned positions. Plane matching is independent of the requested output coordinate_system.

    • "horizontal" selects a constant spherical elevation.

    • "median" selects a constant lateral-polar lateral angle.

    • "frontal" selects the nearest requested spherical azimuth and the nearest opposite azimuth.

  • plane_angle (float or None, default=None) – Requested plane coordinate in angle_unit. For plane="horizontal" this is spherical elevation. For plane="median" this is lateral-polar lateral angle. For plane="frontal" this is spherical azimuth; the opposite azimuth is added automatically. None uses 0 for horizontal and median planes, and 90 degrees or pi / 2 radians for the frontal plane.

Returns:

Source-position array with shape (N, 3). The columns are (azimuth, elevation, radius) for spherical, (lateral, polar, radius) for lateral-polar, or (x, y, z) for cartesian coordinates.

Return type:

np.ndarray

Raises:

ValueError – If angle_unit is unsupported, plane is unsupported, the source or target coordinate system is unsupported, SOFA angular units cannot be interpreted, or the requested conversion is not implemented.

Notes

SOFA angular units are detected from the SourcePosition:Units attribute. Angular source data stored in radians are converted through cartesian coordinates when degree/radian conversion is required.

Examples

Load an HRTF, inspect the full spherical source grid, then request plane-filtered positions in spherical and Cartesian coordinates:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.get_positions().shape
(793, 3)
>>> horizontal = hrtf.Sources.get_positions(plane="horizontal")
>>> horizontal.shape
(72, 3)
>>> frontal_xyz = hrtf.Sources.get_positions(
...     coordinate_system="cartesian",
...     plane="frontal",
... )
>>> frontal_xyz.shape
(22, 3)
get_azimuth_angles(angle_unit='degrees')

Return unique azimuth values available in the current source grid.

The source grid is first normalized to spherical coordinates. This makes the result independent of the active source_coordinate_system while still respecting any spatial subset selected on the owning HRTF.

Parameters:

angle_unit ({degrees, radians}, default=``degrees``) – Angular unit used for returned azimuth values.

Returns:

One-dimensional array of sorted unique azimuth angles rounded to two decimals.

Return type:

np.ndarray

Raises:

ValueError – If positions cannot be read or converted to spherical coordinates.

Examples

Load an HRTF and inspect the available azimuth grid in degrees:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.get_azimuth_angles()[:5]
array([ 0.,  5., 10., 15., 20.])
get_elevation_angles(angle_unit='degrees')

Return unique elevation values available in the current source grid.

The source grid is first normalized to spherical coordinates. This makes the result independent of the active source_coordinate_system while still respecting any spatial subset selected on the owning HRTF.

Parameters:

angle_unit ({degrees, radians}, default=``degrees``) – Angular unit used for returned elevation values.

Returns:

One-dimensional array of sorted unique elevation angles rounded to two decimals.

Return type:

np.ndarray

Raises:

ValueError – If positions cannot be read or converted to spherical coordinates.

Examples

Load an HRTF and inspect the available elevation grid in degrees:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.get_elevation_angles()
array([-45., -30., -20., -10.,   0.,  10.,  20.,  30.,  45.,  60.,  75.,
        90.])
get_elevation_angles_for_azimuth(azimuth, angle_unit='degrees')

Return elevations available at the nearest source-grid azimuth.

This method is useful for plane-based plotting and selection UIs where a requested azimuth may not exist exactly in the measured source grid. The azimuth match is circular, so values near 0 and 360 degrees, or 0 and 2*pi radians, are treated as neighbors.

Parameters:
  • azimuth (float) – Requested azimuth angle used to query the source grid.

  • angle_unit ({degrees, radians}, default=``degrees``) – Angular unit for azimuth, returned elevations, and real_azimuth.

Returns:

(elevation_angles, real_azimuth) where elevation_angles is a one-dimensional array of unique elevations available at the matched azimuth, and real_azimuth is the actual azimuth selected from the grid. Both outputs are rounded to two decimals.

Return type:

tuple[np.ndarray, float]

Raises:

ValueError – If azimuth is boolean or non-finite, angle_unit is unsupported, or positions cannot be read or converted to spherical coordinates.

Examples

Ask for the elevations available at the source-grid azimuth nearest to 0 degrees:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> elevations, real_azimuth = hrtf.Sources.get_elevation_angles_for_azimuth(0.0)
>>> elevations[:5]
array([-45., -30., -20., -10.,   0.])
>>> real_azimuth
0.0
get_azimuth_angles_for_elevation(elevation, angle_unit='degrees')

Return azimuths available at the nearest source-grid elevation.

This method is useful for horizontal-plane workflows where the requested elevation may not exist exactly in the measured source grid. Elevation matching uses the nearest available numerical elevation in spherical coordinates.

Parameters:
  • elevation (float) – Requested elevation angle used to query the source grid.

  • angle_unit ({degrees, radians}, default=``degrees``) – Angular unit for elevation, returned azimuths, and real_elevation.

Returns:

(azimuth_angles, real_elevation) where azimuth_angles is a one-dimensional array of unique azimuths available at the matched elevation, and real_elevation is the actual elevation selected from the grid. Both outputs are rounded to two decimals.

Return type:

tuple[np.ndarray, float]

Raises:

ValueError – If elevation is boolean or non-finite, angle_unit is unsupported, or positions cannot be read or converted to spherical coordinates.

Examples

Ask for the azimuths available at the source-grid elevation nearest to the horizontal plane:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> azimuths, real_elevation = hrtf.Sources.get_azimuth_angles_for_elevation(0.0)
>>> azimuths[:5]
array([ 0.,  5., 10., 15., 20.])
>>> real_elevation
0.0
get_position_index(position, coordinate_system='spherical', angle_unit='degrees')

Return the nearest source index and its resolved grid position.

The query is matched against the current source grid, including any source subset already selected on the owning HRTF object. Numeric positions are interpreted in coordinate_system. Named positions use the canonical horizontal spherical aliases front, back, left, and right and are then returned in the requested coordinate system.

Parameters:
  • position (np.ndarray | list[float] | tuple[float, float, float] | str) – Query position. Numeric spherical and lateral-polar queries may be angle-only (2,) or full (3,) coordinates. Cartesian queries must be (3,). String queries must be one of the supported named positions.

  • coordinate_system ({spherical, cartesian, lateral-polar}, default=``spherical``) – Coordinate system of numeric position queries and returned real_position.

  • angle_unit ({degrees, radians}, default=``degrees``) – Angular unit for spherical/lateral-polar inputs and outputs.

Returns:

(idx, real_position) where idx is the nearest source index in the current source view and real_position is the matched grid coordinate rounded to two decimals in coordinate_system.

Return type:

tuple[int, np.ndarray]

Raises:

ValueError – If the coordinate system or angle unit is unsupported, source positions do not form an (N, 3) grid, a named position is unknown, a query has an invalid shape, or the needed coordinate conversion is unsupported.

Examples

Resolve a named source direction to the nearest measured source index and its real spherical grid position:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.Sources.get_position_index("front")
(4, array([0. , 0. , 1.5]))
class hrtfpykit.hrtf.transforms.Transform(hrtf)

Provide immutable HRTF-processing operations for one parent object.

Transform is accessed through transform and provides non-mutating HRTF-processing operations. Each method clones the parent HRTF object, applies one operation to the clone, resynchronizes the affected domain representation, marks the returned object as transformed, and leaves the original HRTF unchanged.

Time-domain operations modify the impulse-response array stored in IR.values, then refresh the frequency-response array stored in TF.values. Frequency-domain operations do the reverse: they modify the transfer-function array and refresh the impulse-response array. This keeps both acoustic representations available for later plotting, metric calculation, SOFA synchronization, and export.

Parameters:

hrtf (HRTF) – Parent HRTF object used as the source for cloned transform results.

Examples

Access the transform namespace from a loaded HRTF and apply a preprocessing step without changing the original object:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> windowed = hrtf.transform.apply_window("hann")
>>> hrtf.is_transformed()
False
>>> windowed.is_transformed()
True
apply_window(window_name, start_sample=None, end_sample=None, ear='both')

Apply a time-domain window to IR values and rebuild TF.

The window is applied along the final IR sample axis. By default, it spans the complete HRIR and applies to both ears. start_sample and end_sample restrict the window to one interval while samples outside that interval remain unchanged. ear selects whether the window is applied to both ears, only the left ear, or only the right ear.

Parameters:
  • window_name (str) – Window identifier passed to the DSP layer, for example hann, hamming, blackman, or rectangular.

  • start_sample (int or None, default=None) – First sample included in the windowed interval. None starts at sample 0.

  • end_sample (int or None, default=None) – First sample after the windowed interval. None uses the full IR length.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the window. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with windowed IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data are unavailable, ear is invalid, the requested window is unsupported, or the requested sample interval is invalid.

Examples

Apply a Hann window to the left-ear HRIR samples and keep the source layout unchanged:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> windowed = hrtf.transform.apply_window(
...     "hann",
...     start_sample=0,
...     end_sample=128,
...     ear="left",
... )
>>> windowed.IR.values.shape
(793, 2, 256)
>>> windowed.TF.values.shape
(793, 2, 129)
apply_crop(start_sample=None, end_sample=None, window=None, ear='both')

Remove an IR sample interval, append zero padding, and rebuild TF.

The crop operates on the final IR sample axis. start_sample is included and end_sample is excluded, matching Python slicing and apply_window(). Samples after the removed interval are shifted left, then zeros are appended at the end so the IR sample length stays unchanged. When window is not None, the final retained samples adjacent to the zero-padded tail are tapered with the named window before TF is recomputed. ear selects whether the crop is applied to both ears, only the left ear, or only the right ear.

Parameters:
  • start_sample (int or None, default=None) – First sample removed from each impulse response. None starts at sample 0.

  • end_sample (int or None, default=None) – First sample after the removed interval. This value is required. For example, start_sample=4 and end_sample=8 removes samples 4, 5, 6, and 7. start_sample=None and end_sample=4 removes samples 0, 1, 2, and 3.

  • window (str or None, default=None) – Optional window name used to taper the final retained samples before the trailing zero padding. Supported names match apply_window(). None skips tapering.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the crop. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with cropped and zero-padded IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data are unavailable, ear is invalid, the crop interval is invalid, or window is not None and the requested window name is unsupported.

Examples

Remove samples 64 through 127 from the left-ear HRIRs and keep the original IR length by appending zeros:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> cropped = hrtf.transform.apply_crop(64, 128, window="hann", ear="left")
>>> cropped.IR.values.shape
(793, 2, 256)
>>> cropped.TF.values.shape
(793, 2, 129)
>>> cropped.is_transformed()
True
apply_padding(padding_length, location='end', value=0, preserve_length=False, ear='both')

Pad IR values along the sample axis and rebuild TF.

Padding is applied to the final IR axis while preserving source and ear layout. By default, padding applies to both ears and the sample axis grows by padding_length. ear can target one ear. For start padding without length preservation, the selected ear is shifted right and unselected ears are extended with trailing zeros so their timing stays unchanged. preserve_length=True applies start padding and removes the same number of samples from the end.

Parameters:
  • padding_length (int) – Number of samples added to the IR.

  • location ({start, end}, default=``end``) – Side where the padding is applied.

  • value (float, default=0) – Constant value used in the selected padded region.

  • preserve_length (bool, default=False) – If True, only start padding is accepted and the returned IR keeps the original sample length.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the requested padding. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with padded IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data are unavailable, padding length is invalid, location or ear is not supported, or preserve_length=True is used with end padding.

Examples

Delay the left-ear HRIR by prepending zeros while keeping the original IR sample count:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.values.shape
(793, 2, 256)
>>> padded = hrtf.transform.apply_padding(
...     4,
...     location="start",
...     preserve_length=True,
...     ear="left",
... )
>>> padded.IR.values.shape
(793, 2, 256)
>>> padded.TF.values.shape
(793, 2, 129)
upsampling(new_sample_rate)

Upsample impulse-response values and resynchronize TF data.

The transform changes IR.values and IR.sample_rate in the returned object, then recomputes TF.values and frequency bins from the resampled IR.

Parameters:

new_sample_rate (float) – Target sample rate in Hz. It must be strictly greater than the current IR sample rate.

Returns:

A new HRTF instance with upsampled IR values, updated IR sample rate, and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data or sample-rate metadata are unavailable, or if the target sample rate is not finite and greater than the current rate.

Examples

Resample a SOFA-loaded HRTF to a higher sampling rate before later time-domain processing:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.sample_rate
44100.0
>>> hrtf.IR.values.shape
(793, 2, 256)
>>> upsampled = hrtf.transform.upsampling(88200.0)
>>> upsampled.IR.sample_rate
88200.0
>>> upsampled.IR.values.shape
(793, 2, 512)
downsampling(new_sample_rate)

Downsample impulse-response values and resynchronize TF data.

The transform changes IR.values and IR.sample_rate in the returned object, then recomputes TF.values and frequency bins from the resampled IR.

Parameters:

new_sample_rate (float) – Target sample rate in Hz. It must be strictly lower than the current IR sample rate.

Returns:

A new HRTF instance with downsampled IR values, updated IR sample rate, and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data or sample-rate metadata are unavailable, or if the target sample rate is not finite and lower than the current rate.

Examples

Resample a SOFA-loaded HRTF to a lower sampling rate and keep TF data synchronized with the new HRIR samples:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.IR.sample_rate
44100.0
>>> hrtf.IR.values.shape
(793, 2, 256)
>>> downsampled = hrtf.transform.downsampling(22050.0)
>>> downsampled.IR.sample_rate
22050.0
>>> downsampled.IR.values.shape
(793, 2, 128)
apply_fir_filter(filter, cutoff=None, num_taps=101, window=None, ear='both')

Apply FIR filtering to IR values and rebuild TF.

Filtering is performed in the time domain along the final IR sample axis. By default, both ears are filtered. ear can restrict the filtering to the left or right channel while the other channel remains unchanged.

Parameters:
  • filter (str) – Filter type. Low-pass, high-pass, and band-pass aliases are accepted by the DSP layer.

  • cutoff (float | tuple[float, float] | None, default=None) – Cutoff frequency or frequency pair in Hz.

  • num_taps (int, default=101) – FIR filter length.

  • window (str | None, default=None) – Optional FIR design window.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the filter. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with filtered IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data or sample-rate metadata are unavailable, filter arguments are invalid, ear is invalid, or cutoff values are incompatible with the sample rate.

Examples

Low-pass filter the left-ear HRIRs with an FIR design:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> filtered = hrtf.transform.apply_fir_filter(
...     filter="lowpass",
...     cutoff=3000.0,
...     num_taps=31,
...     ear="left",
... )
>>> filtered.IR.values.shape
(793, 2, 256)
>>> filtered.is_transformed()
True
apply_iir_filter(filter, cutoff=None, order=10, ear='both')

Apply IIR filtering to IR values and rebuild TF.

Filtering is performed in the time domain along the final IR sample axis. By default, both ears are filtered. ear can restrict the filtering to the left or right channel while the other channel remains unchanged.

Parameters:
  • filter (str) – Filter type. Low-pass, high-pass, and band-pass aliases are accepted by the DSP layer.

  • cutoff (float | tuple[float, float] | None, default=None) – Cutoff frequency or frequency pair in Hz.

  • order (int, default=10) – Butterworth filter order.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the filter. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with filtered IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data or sample-rate metadata are unavailable, filter arguments are invalid, ear is invalid, or cutoff values are incompatible with the sample rate.

Examples

Apply a Butterworth low-pass filter to the right-ear HRIRs:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> filtered = hrtf.transform.apply_iir_filter(
...     filter="lowpass",
...     cutoff=3000.0,
...     order=4,
...     ear="right",
... )
>>> filtered.IR.values.shape
(793, 2, 256)
>>> filtered.TF.values.shape
(793, 2, 129)
minimum_phase(method='homomorphic', fft_length=None, epsilon=1e-12)

Convert IR values to minimum phase and rebuild TF.

The transform replaces each current HRIR with a minimum-phase version derived from its magnitude response. The returned object then refreshes the TF representation from the minimum-phase IR.

Parameters:
  • method (str, default=``homomorphic``) – Minimum-phase method key passed to the DSP layer.

  • fft_length (int | None, default=None) – Optional FFT length used during cepstral reconstruction.

  • epsilon (float, default=1e-12) – Small positive floor used for numerical stability.

Returns:

A new HRTF instance with minimum-phase IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data are unavailable or minimum-phase parameters are invalid.

Examples

Convert the HRIRs to a minimum-phase representation while preserving the current HRTF layout:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> minimum = hrtf.transform.minimum_phase()
>>> minimum.IR.values.shape
(793, 2, 256)
>>> minimum.is_transformed()
True
to_ctf(weights=False, magnitude_average='log', attenuation=None)

Convert the current HRTF into its common transfer function (CTF).

The CTF collapses the source axis into a single common response per ear. The returned HRTF keeps a singleton source axis for compatibility with source-based plotting and SOFA update workflows.

Parameters:
  • weights (bool, optional) – If False, all source positions contribute equally. If True, diffuse-field weights are derived internally from the HRTF source positions using spherical Voronoi areas.

  • magnitude_average ({log, linear}, optional) – Rule used to average source magnitudes before the minimum-phase CTF reconstruction. log computes a log-magnitude average (geometric mean in linear magnitude). linear computes a direct linear-magnitude average (arithmetic mean).

  • attenuation (float | None, optional) – Optional attenuation in dB applied to the CTF magnitude before the minimum-phase reconstruction. If None, no attenuation is applied.

Returns:

A new HRTF instance containing the CTF. The output keeps a singleton compatibility source axis.

Return type:

HRTF

Raises:

ValueError – If TF data, IR reference length, source geometry, weighting inputs, or averaging parameters are invalid.

Examples

Estimate the common transfer function of a SOFA-loaded HRTF and inspect the singleton source axis kept in the result:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.values.shape
(793, 2, 129)
>>> ctf = hrtf.transform.to_ctf(weights=False)
>>> ctf.TF.values.shape
(1, 2, 129)
to_dtf(weights=False, magnitude_average='log', attenuation=None)

Convert the current HRTF into its directional transfer function (DTF).

The DTF removes a common transfer component estimated from the current HRTF. The returned HRTF preserves the source layout of the input object and rebuilds IR data from the DTF-domain TF values.

Parameters:
  • weights (bool, optional) – If False, all source positions contribute equally to the internal CTF estimate. If True, diffuse-field weights are derived internally from the HRTF source positions using spherical Voronoi areas.

  • magnitude_average ({log, linear}, optional) – Rule used to estimate the internal CTF magnitude before the DTF division. log computes a log-magnitude average (geometric mean in linear magnitude). linear computes a direct linear-magnitude average (arithmetic mean).

  • attenuation (float | None, optional) – Optional attenuation in dB applied to the DTF after the CTF division. If None, no attenuation is applied.

Returns:

A new HRTF instance containing the DTF while preserving the source layout of the current HRTF.

Return type:

HRTF

Raises:

ValueError – If TF data, IR reference length, source geometry, weighting inputs, or averaging parameters are invalid.

Examples

Remove the common transfer component while keeping the original source grid layout:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.TF.values.shape
(793, 2, 129)
>>> dtf = hrtf.transform.to_dtf(weights=False)
>>> dtf.TF.values.shape
(793, 2, 129)
>>> dtf.IR.values.shape
(793, 2, 256)
modify_ir(new_ir)

Replace time-domain IR values and rebuild TF data.

new_ir replaces the full current IR array. The leading dimensions before the final sample axis must match the current spatial and ear layout. When new_ir is an IR or HRTF object and provides a sample rate, that sample rate is copied into the returned HRTF before TF recomputation.

Parameters:

new_ir (np.ndarray | IR | HRTF) – Time-domain data used to replace the current IR values. NumPy arrays must keep the same spatial and ear layout as the current HRTF. IR and HRTF inputs contribute their IR values, and when available their sample rate.

Returns:

A new HRTF instance with modified IR values and rebuilt TF data.

Return type:

HRTF

Raises:

ValueError – If replacement data are missing, empty, not array-like in the expected way, or do not match the current leading IR/TF layout.

Examples

Replace HRIR values with an edited copy and let the transform rebuild the transfer functions:

>>> import numpy as np
>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> ir = np.array(hrtf.IR.values, copy=True)
>>> ir[..., :8] = 0.0
>>> modified = hrtf.transform.modify_ir(ir)
>>> modified.IR.values[0, 0, :8]
array([0., 0., 0., 0., 0., 0., 0., 0.])
>>> modified.IR.values.shape
(793, 2, 256)
>>> modified.TF.values.shape
(793, 2, 129)
modify_phase(new_phase, unit='degrees')

Replace TF phase values and rebuild IR.

The transform preserves the current TF magnitude and replaces only the phase component. The replacement phase must be compatible with the current TF layout and is interpreted according to unit.

Parameters:
  • new_phase (np.ndarray) – Phase array with the same TF layout as the current HRTF.

  • unit ({degrees, radians}, default=``degrees``) – Unit used by new_phase.

Returns:

A new HRTF instance with modified TF phase and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If TF data or frequency bins are unavailable, unit is invalid, or the replacement phase cannot be broadcast to the TF layout.

Examples

Replace phase values with an edited phase array while preserving the current TF magnitude:

>>> import numpy as np
>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> phase = np.array(hrtf.TF.phase, copy=True)
>>> phase[..., 1:] *= 0.95
>>> modified = hrtf.transform.modify_phase(phase, unit="radians")
>>> modified.TF.values.shape
(793, 2, 129)
>>> modified.IR.values.shape
(793, 2, 256)
modify_tf(new_tf)

Replace frequency-domain TF values and rebuild IR data.

new_tf replaces the full complex TF array. The leading dimensions before the frequency axis must match the current TF layout, or the current IR leading layout when no TF data are present. Frequency bins are copied from TF or HRTF inputs when available; otherwise they are reused or inferred from the current sample rate when the TF length changes.

Parameters:

new_tf (np.ndarray | TF | HRTF) – Frequency-domain data used to replace the current TF values. NumPy arrays must keep the same spatial and ear layout as the current HRTF. TF and HRTF inputs contribute their TF values and, when available, their frequency bins.

Returns:

A new HRTF instance with modified TF values and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If replacement data are missing, empty, have incompatible leading shape, contain too few frequency bins, or require frequency-bin inference without valid sample-rate metadata.

Examples

Replace TF values with a scaled complex copy and rebuild the time-domain representation:

>>> import numpy as np
>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> round(float(hrtf.TF.magnitude[0, 0, 1]), 6)
0.209696
>>> tf = np.array(hrtf.TF.values, copy=True) * 0.98
>>> modified = hrtf.transform.modify_tf(tf)
>>> round(float(modified.TF.magnitude[0, 0, 1]), 6)
0.205502
>>> modified.TF.values.shape
(793, 2, 129)
>>> modified.IR.values.shape
(793, 2, 256)
modify_magnitude(new_magnitude, scale='linear')

Replace TF magnitude values and rebuild IR.

The transform preserves the current TF phase and replaces only the magnitude component. new_magnitude must be compatible with the current TF layout and is interpreted according to scale.

Parameters:
  • new_magnitude (np.ndarray) – Magnitude array with the same TF layout as the current HRTF.

  • scale ({linear, db}, default=``linear``) – Magnitude scale used by new_magnitude.

Returns:

A new HRTF instance with modified TF magnitude and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If TF data or frequency bins are unavailable, scale is invalid, or the replacement magnitude cannot be broadcast to the TF layout.

Examples

Replace the TF magnitude with a slightly attenuated copy and keep the original phase:

>>> import numpy as np
>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> round(float(hrtf.TF.magnitude[0, 0, 1]), 6)
0.209696
>>> magnitude = np.array(hrtf.TF.magnitude, copy=True) * 0.95
>>> modified = hrtf.transform.modify_magnitude(magnitude, scale="linear")
>>> round(float(modified.TF.magnitude[0, 0, 1]), 6)
0.199212
>>> modified.TF.values.shape
(793, 2, 129)
>>> modified.IR.values.shape
(793, 2, 256)
apply_gain(gain, scale='db', ear='both')

Apply a TF-domain gain and rebuild IR.

Gain modifies TF magnitude while preserving phase. Scalar gains apply globally by default. ear can restrict the gain to the left or right channel while the other channel remains unchanged. Array gains must be broadcast-compatible with the selected TF layout.

Parameters:
  • gain (float | np.ndarray) – Gain applied to the current TF magnitude while preserving phase. In scale=``db``, negative values attenuate and positive values amplify.

  • scale ({linear, db}, default=``db``) – Scale used by gain.

  • ear ({both, left, right}, default=``both``) – Ear channel that receives the gain. left uses channel 0 and right uses channel 1.

Returns:

A new HRTF instance with gain-adjusted TF values and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If TF data are unavailable, scale or ear is invalid, or gain cannot be broadcast to the selected TF layout.

Examples

Apply a broadband gain in dB to the left ear:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> round(float(hrtf.TF.magnitude[0, 0, 1]), 6)
0.209696
>>> louder_left = hrtf.transform.apply_gain(3.0, scale="db", ear="left")
>>> round(float(louder_left.TF.magnitude[0, 0, 1]), 6)
0.296294
>>> louder_left.TF.values.shape
(793, 2, 129)
>>> louder_left.is_transformed()
True
modify_fft_length(new_fft_length)

Set the HRTF FFT length and recompute TF from the current IR.

The returned object keeps the current IR unchanged and rebuilds TF using new_fft_length. This changes frequency-bin spacing and TF length but does not add time-domain information to the HRIR.

Parameters:

new_fft_length (int) – FFT size used for IR-to-TF conversion.

Returns:

A new HRTF instance with updated FFT length and recomputed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data are unavailable or the FFT length is invalid for real-FFT conversion.

Examples

Increase the FFT length used for IR-to-TF conversion and inspect the resulting frequency-bin count:

>>> from hrtfpykit.hrtf import load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> hrtf.fft_length
256
>>> hrtf.TF.values.shape
(793, 2, 129)
>>> modified = hrtf.transform.modify_fft_length(512)
>>> modified.fft_length
512
>>> modified.TF.values.shape
(793, 2, 257)
add_itd(itd, unit='samples')

Add an interaural time delay to IR values and rebuild TF.

Positive ITD values delay the left ear; negative values delay the right ear. A scalar delay is applied to every source position. Array delays must match the leading IR shape before the ear and sample axes, which allows position-dependent ITD perturbations.

Parameters:
  • itd (float) – ITD value to apply. Positive values delay the left ear and negative values delay the right ear.

  • unit ({time, samples}, default=``samples``) – Unit used by itd. time is interpreted in microseconds.

Returns:

A new HRTF instance with ITD-modified IR values and refreshed TF data.

Return type:

HRTF

Raises:

ValueError – If IR data do not contain two ear channels, delay values are non-finite, delay-array shape is incompatible with the IR layout, time values are requested without sample-rate metadata, or the absolute delay is not smaller than the IR length.

Examples

Add a two-sample delay to the left ear for every source position:

>>> from hrtfpykit.hrtf import load_hrtf, itd
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> itd(hrtf, output="samples")[:5]
array([-3, -4, -3, -4, -4])
>>> delayed = hrtf.transform.add_itd(2, unit="samples")
>>> itd(delayed, output="samples")[:5]
array([-1, -2, -1, -2, -2])
>>> delayed.IR.values.shape
(793, 2, 256)
>>> delayed.is_transformed()
True
delete_itd(method='threshold', thresh_level=-10.0, upper_cut_freq=3000.0, filter_order=10)

Estimate and remove ITD from the current IR values, then resync TF.

The ITD sign convention follows itd: positive ITD means left-ear delay relative to right-ear and negative ITD means right-ear delay relative to left-ear. Compensation is applied per source by advancing the delayed channel and zero-filling the tail introduced by the shift.

Parameters:
  • method ({threshold, maxiacce}, default=``threshold``) – ITD estimator used to compute the delay per position.

  • thresh_level (float, default=-10.0) – Threshold offset in dB used when method=``threshold``.

  • upper_cut_freq (float, default=3000.0) – Low-pass cutoff in Hz applied before ITD estimation.

  • filter_order (int, default=10) – Butterworth low-pass filter order used before ITD estimation.

Returns:

A new HRTF instance with ITD-compensated IR values and refreshed TF data. Compensation is applied per source position.

Return type:

HRTF

Raises:

ValueError – If IR data do not contain two ear channels, ITD estimation parameters are invalid, or an estimated delay is not smaller than the IR length.

Examples

Estimate and remove ITD from each source position before comparing magnitude-focused features:

>>> from hrtfpykit.hrtf import load_hrtf, itd
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> itd(hrtf, output="samples")[:5]
array([-3, -4, -3, -4, -4])
>>> no_itd = hrtf.transform.delete_itd()
>>> itd(no_itd, output="samples")[:5]
array([0, 0, 0, 0, 0])
>>> no_itd.IR.values.shape
(793, 2, 256)
>>> no_itd.TF.values.shape
(793, 2, 129)
add_ild(ild)

Add an interaural level difference in dB and rebuild IR.

Positive values increase the left ear level relative to the right ear. Negative values increase the right ear level relative to the left ear. The transform applies a symmetric TF-domain correction: half of the ILD is added to the left channel and half is subtracted from the right channel. Existing phase values are preserved and the time-domain IR is rebuilt from the modified TF.

Parameters:

ild (float | numpy.ndarray) – ILD values in dB. A scalar applies the same ILD to every source and frequency bin. An array matching the TF leading shape before the ear and frequency axes applies one ILD per source entry and broadcasts across frequency. An array matching the TF leading shape plus the frequency axis applies frequency-dependent ILD values.

Returns:

A new HRTF instance with ILD-modified TF values and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If TF data are unavailable or do not contain at least two ear channels, if ILD values are empty or non-finite, or if an ILD array shape does not match the accepted source or source and frequency layouts.

Examples

Add 6 dB of ILD to every source position and frequency bin:

>>> from hrtfpykit.hrtf import ild, load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> modified = hrtf.transform.add_ild(6.0)
>>> ild(modified, mode="frequency-dependent").shape
(793, 129)
>>> modified.IR.values.shape
(793, 2, 256)
>>> modified.is_transformed()
True
delete_ild(epsilon=1e-12)

Remove frequency-dependent interaural level difference and rebuild IR.

The transform computes signed frequency-dependent ILD from the current TF values, then applies the inverse ILD with add_ild(). This equalizes the first two ear channels at each source position and frequency bin while preserving TF phase.

Parameters:

epsilon (float, default=1e-12) – Positive floor passed to hrtfpykit.hrtf.ild() while measuring the current frequency-dependent ILD.

Returns:

A new HRTF instance with frequency-dependent ILD removed from TF values and rebuilt IR data.

Return type:

HRTF

Raises:

ValueError – If TF data are unavailable, if TF data do not contain at least two ear channels, or if epsilon is not finite and positive.

Examples

Remove frequency-dependent ILD before inspecting phase-focused data:

>>> from hrtfpykit.hrtf import ild, load_hrtf
>>> hrtf = load_hrtf("P0001_FreeFieldComp_44kHz.sofa")
>>> no_ild = hrtf.transform.delete_ild()
>>> ild(no_ild, mode="frequency-dependent").shape
(793, 129)
>>> no_ild.TF.values.shape
(793, 2, 129)
>>> no_ild.is_transformed()
True