Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
b68abb5
add sht
tluebeck Oct 29, 2024
8510dee
rearrange sht
tluebeck Oct 30, 2024
a9dbca7
Merge branch 'dev_spherical_harmonic_signal_class' into spherical_har…
tluebeck Apr 18, 2025
c2bc71b
update SHT
tluebeck Apr 28, 2025
ef0c6ca
Merge branch 'develop' into spherical_harmonics_transform
tluebeck May 30, 2025
ef53eef
update according to new SHSignal
tluebeck May 30, 2025
92bb61a
update sht
tluebeck Jun 3, 2025
5b070d7
edit doc strings
tluebeck Jun 3, 2025
a33e022
edit docstring
tluebeck Jun 3, 2025
b25a183
Merge branch 'develop' into spherical_harmonics_transform
tluebeck Jun 16, 2025
0e2a4f7
update tests
tluebeck Jun 17, 2025
dbd0e7a
Merge branch 'develop' into spherical_harmonics_transform
tluebeck Jul 1, 2025
5ab5960
update tests
tluebeck Jul 1, 2025
c26bde0
test back and forth for different normalizations, basis types, and co…
tluebeck Jul 1, 2025
11bdb17
bugfix auto in inverse method in isft
tluebeck Jul 3, 2025
1f4a8ec
bugfix reshaping
tluebeck Jul 3, 2025
6c64125
add SHAudio SHTimeData and SHFrequencyData
tluebeck Jul 6, 2025
700bcb9
add tests
tluebeck Jul 6, 2025
67b163f
add doc and clean up
tluebeck Jul 7, 2025
d4e0556
fix typo
tluebeck Jul 7, 2025
fc6ca7e
restructure sht
tluebeck Jul 8, 2025
90d05ba
fix typo
tluebeck Jul 11, 2025
40a973b
update according to review
tluebeck Jul 11, 2025
77ba14a
bugfix auto axis
tluebeck Jul 15, 2025
111b2f7
fix typo
tluebeck Jul 15, 2025
de6eaaa
update according to review
tluebeck Aug 5, 2025
490dfa8
Merge branch 'develop' into dev/spherical_harmonics_audio
tluebeck Oct 4, 2025
3424e41
adapt to new normalization names
tluebeck Oct 4, 2025
40785fa
Merge branch 'develop' into dev/spherical_harmonics_audio
tluebeck Oct 18, 2025
ef86b74
add SphericalharmonicDefinition to SHAudio Classes
tluebeck Oct 19, 2025
d991c26
ruff checks
tluebeck Oct 19, 2025
a1bf902
update according to review
tluebeck Oct 22, 2025
b3be88d
Merge branch 'develop' into dev/spherical_harmonics_audio
tluebeck Oct 22, 2025
a49f4cd
update according ot review
tluebeck Oct 23, 2025
dfd4d54
rename audio to sh_audio
tluebeck Oct 23, 2025
6666f3a
revert renaming of sh_audio
tluebeck Oct 24, 2025
2fce301
Merge branch 'develop' into dev/spherical_harmonics_audio
tluebeck Nov 19, 2025
e9c4fb0
Incorporate new SHBase class
tluebeck Nov 20, 2025
cc245e2
override time and freq setter in SHaudio
tluebeck Nov 20, 2025
001de12
Change normalization and channel_order behaiviour
tluebeck Nov 21, 2025
25dcd87
udpate according to review
tluebeck Nov 29, 2025
9348b08
add data init methods
tluebeck Dec 3, 2025
c07b683
clean up tests
tluebeck Dec 4, 2025
020af4c
update tests
tluebeck Dec 4, 2025
b05cb04
fix ruff errors
tluebeck Dec 4, 2025
5d1015b
clean up
tluebeck Dec 4, 2025
4ae1205
update according to review
tluebeck Dec 4, 2025
8276700
update according to review
tluebeck Dec 4, 2025
015e14d
update tests
tluebeck Dec 4, 2025
478ae9c
allow Signal, Frequency and TimeData
tluebeck Dec 5, 2025
e5342d0
Merge branch 'dev/spherical_harmonics_audio' into spherical_harmonics…
tluebeck Dec 5, 2025
cceec17
merge sh audio classes
tluebeck Dec 5, 2025
c5048e2
adapt to new SH classes structure
tluebeck Dec 6, 2025
cbdd4a8
add input checks
tluebeck Dec 10, 2025
1adf96c
update tests
tluebeck Dec 10, 2025
67e4915
update tests
tluebeck Dec 10, 2025
010eedb
fix back and forward SHT tests
tluebeck Dec 11, 2025
90f9690
clean up
tluebeck Dec 11, 2025
099c05a
fix typos
tluebeck Dec 11, 2025
5e979bc
implement isht in/out tests
tluebeck Dec 12, 2025
3a8f309
Merge branch 'develop' into spherical_harmonics_transform
tluebeck Dec 12, 2025
925a0a8
refactor sht and isht
tluebeck Dec 12, 2025
5af4434
bugfix wrong axis
tluebeck Dec 13, 2025
883ee55
Merge branch 'develop' into spherical_harmonics_transform
tluebeck Dec 15, 2025
00fd9ca
add dimension test and edit docstring
tluebeck Dec 15, 2025
d1eb512
merge develop
tluebeck Dec 15, 2025
bfaef7e
Merge branch 'develop' into spherical_harmonics_transform
tluebeck Jan 21, 2026
909585d
adapt to new class method
tluebeck Jan 22, 2026
3c46879
replace tensordot with internal matrix_multiplication
tluebeck Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spharpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from . import interpolate
from . import spatial
from . import special
from . import sht


__all__ = [
Expand All @@ -41,4 +42,5 @@
'spatial',
'special',
'SamplingSphere',
'sht'
]
174 changes: 174 additions & 0 deletions spharpy/sht.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import numpy as np
from pyfar import Signal, TimeData, FrequencyData
from pyfar import matrix_multiplication
from . import SphericalHarmonics
from . import SphericalHarmonicDefinition
from . import SphericalHarmonicSignal
from . import SphericalHarmonicTimeData
from . import SphericalHarmonicFrequencyData


def sht(signal, spherical_harmonics, axis='auto'):
"""Compute the spherical harmonic transform

Parameters
----------
signal : Signal, TimeData, or FrequencyData
the signal for which the spherical harmonic transform is computed
spherical_harmonics : :class:`spharpy.SphericalHarmonics`
Spherical harmonics object
axis : integer or 'auto'
Axis along which the spherical harmonic transform is computed. If 'auto' the
transformation is computed along the axis which matches the number
of spherical samples of the spherical_harmonics basis

Returns
----------
sh_signal : SphericalHarmonicSignal, SphericalHarmonicsTimeData,
or SphericalHarmonicsFrequencyData
signal with spherical harmonic coefficients. According to
SphericalHarmonicsAudio definitions, the spherical harmonic
coefficients are always in the second to last dimension. The
order of all other channels remains unchanged.
References
----------

[#] Rafaely, B. (2015). Fundamentals of Spherical Array Processing,
(J. Benesty and W. Kellermann, Eds.) Springer Berlin Heidelberg,
2nd ed., 196 pages. doi:10.1007/978-3-319-99561-8
[#] Ramani Duraiswami, Dimitry N. Zotkin, and Nail A. Gumerov: "Inter-
polation and range extrapolation of HRTFs." IEEE Int. Conf.
Acoustics, Speech, and Signal Processing (ICASSP), Montreal,
Canada, May 2004, p. 45-48, doi: 10.1109/ICASSP.2004.1326759.
"""
if isinstance(signal, (Signal, TimeData)):
data = signal.time
elif isinstance(signal, FrequencyData):
data = signal.freq
else:
raise ValueError("Input signal must be a Signal, TimeData, or "
f"FrequencyData but is {type(signal)}")

if not isinstance(spherical_harmonics, SphericalHarmonics):
raise ValueError("spherical_harmonics must be SphericalHarmonics "
f"but is {type(spherical_harmonics)}")

Y_inv = spherical_harmonics.basis_inv
if axis == 'auto':
axis = np.where(np.array(signal.cshape) == Y_inv.shape[1])[0]
if len(axis) == 0:
raise ValueError("No axes matches the number of spherical "
"harmonics basis functions")
if len(axis) > 1:
raise ValueError("Too many axis match the number of spherical "
"harmonics basis functions")
axis = axis[0]

if signal.cshape[axis] != Y_inv.shape[1]:
raise ValueError("Spherical samples of provided axis does not match "
"the number of spherical harmonics basis functions.")

# move spherical samples to -2
data = np.moveaxis(data, axis, -2)

# perform transform
data_nm = matrix_multiplication((Y_inv, data))

# ensure result has 3 dimensions
if len(data_nm.shape) < 3:
data_nm = data_nm[np.newaxis, ...]

# set up SH definition
shd = SphericalHarmonicDefinition(
n_max=int(np.sqrt(data_nm.shape[-2])-1),
basis_type=spherical_harmonics.basis_type,
normalization=spherical_harmonics.normalization,
channel_convention=spherical_harmonics.channel_convention,
condon_shortley=spherical_harmonics.condon_shortley)

if isinstance(signal, Signal):
sh_signal = SphericalHarmonicSignal.from_definition(
sh_definition=shd,
data=data_nm,
sampling_rate=signal.sampling_rate,
fft_norm=signal.fft_norm,
is_complex=signal.complex,
comment=signal.comment)
elif isinstance(signal, TimeData):
sh_signal = SphericalHarmonicTimeData.from_definition(
sh_definition=shd,
data=data_nm,
times=signal.times,
comment=signal.comment,
is_complex=False)
elif isinstance(signal, FrequencyData):
sh_signal = SphericalHarmonicFrequencyData.from_definition(
sh_definition=shd,
data=data_nm,
frequencies=signal.frequencies,
comment=signal.comment)

return sh_signal


def isht(sh_signal, coordinates):
"""Compute the inverse spherical harmonic transform

Parameters
----------
sh_signal: SphericalHarmonicsSignal, SphericalHarmonicsTimeData, or
SphericalHarmonicsFrequencyData
The spherical harmonic signal for which the inverse spherical
harmonic transform is computed.
coordinates: :class:`spharpy.samplings.Coordinates`, :doc:`pf.Coordinates
<pyfar:classes/pyfar.coordinates>`
Coordinates for which the inverse SH transform is computed

Returns
----------
signal : Signal, TimeData, or FrequencyData
inverse transformed signal in space domain. The spherical
samples are always in the second to last dimension. All other
channels remain unchaged.

"""
if isinstance(sh_signal, (SphericalHarmonicSignal,
SphericalHarmonicTimeData)):
data = sh_signal.time
elif isinstance(sh_signal, SphericalHarmonicFrequencyData):
data = sh_signal.freq
else:
raise ValueError("Input signal has to be SphericalHarmonicSignal, "
"SphericalHarmonicTimeData, or "
"SphericalHarmonicFrequencyData "
f"but is {type(sh_signal)}")

# get spherical harmonics basis functions according to sh_signals
# properties
spherical_harmonics = SphericalHarmonics(
sh_signal.n_max,
coordinates=coordinates,
basis_type=sh_signal.basis_type,
channel_convention=sh_signal.channel_convention,
normalization=sh_signal.normalization,
inverse_method="pseudo_inverse",
condon_shortley=sh_signal.condon_shortley)

data = matrix_multiplication((spherical_harmonics.basis, data))

if isinstance(sh_signal, SphericalHarmonicSignal):
signal = Signal(data,
sh_signal.sampling_rate,
fft_norm=sh_signal.fft_norm,
comment=sh_signal.comment,
is_complex=sh_signal.complex)
elif isinstance(sh_signal, SphericalHarmonicTimeData):
signal = TimeData(data=data,
times=sh_signal.times,
comment=sh_signal.comment,
is_complex=sh_signal.complex)
else:
signal = FrequencyData(data=data,
frequencies=sh_signal.frequencies,
comment=sh_signal.comment)
return signal
Loading