HOA7-Spatial-Decoder / tests /test_basis.py
woodfireind's picture
HOA7 Spatial Field Decoder (hoa64 v0.5.0): 7th-order Ambisonics encode/decode, Wigner-D rotation, DOA analysis, vision fuse, diffusion conditioning
570b87b verified
Raw
History Blame Contribute Delete
2.66 kB
"""Basis / Farina formula sanity checks."""
from __future__ import annotations
import math
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from hoa64.basis import (
MAX_ORDER,
N_CHANNELS,
acn_index,
acn_nm,
channel_names,
sh_sn3d,
unit_vector,
)
def test_layout():
assert N_CHANNELS == 64
assert MAX_ORDER == 7
assert acn_index(0, 0) == 0
assert acn_index(1, -1) == 1
assert acn_index(1, 0) == 2
assert acn_index(1, 1) == 3
assert acn_index(7, 7) == 63
for i in range(64):
n, m = acn_nm(i)
assert acn_index(n, m) == i
names = channel_names()
assert names[0] == "W"
assert names[1] == "Y"
assert names[2] == "Z"
assert names[3] == "X"
assert len(names) == 64
def test_unit_vector_cardinals():
# front, left, up
f = unit_vector(0.0, 0.0)
l = unit_vector(90.0, 0.0)
u = unit_vector(0.0, 90.0)
np.testing.assert_allclose(f, [1, 0, 0], atol=1e-12)
np.testing.assert_allclose(l, [0, 1, 0], atol=1e-12)
np.testing.assert_allclose(u, [0, 0, 1], atol=1e-12)
def test_order1_cartesian_identity():
"""Order-1 SN3D: Y=y, Z=z, X=x at unit directions."""
for az, el, xyz in [
(0, 0, (1, 0, 0)),
(90, 0, (0, 1, 0)),
(0, 90, (0, 0, 1)),
(-90, 0, (0, -1, 0)),
]:
y = sh_sn3d(az, el)
assert y.shape == (64,)
assert abs(y[0] - 1.0) < 1e-12 # W
x, yy, z = xyz
assert abs(y[1] - yy) < 1e-12
assert abs(y[2] - z) < 1e-12
assert abs(y[3] - x) < 1e-12
def test_front_source_signs():
y = sh_sn3d(0.0, 0.0)
# front: X dominant positive among order-1 dipoles
assert y[3] > 0.9
assert abs(y[1]) < 1e-12
assert abs(y[2]) < 1e-12
def test_finite_all_channels():
y = sh_sn3d(33.0, -12.0)
assert y.shape == (64,)
assert np.all(np.isfinite(y))
# batch
yb = sh_sn3d([0, 90, 180], [0, 0, 0])
assert yb.shape == (3, 64)
def test_zenith_only_z_orders():
y = sh_sn3d(0.0, 90.0)
# at north pole, only m=0 channels should be nonzero (real SH)
for n in range(0, 8):
for m in range(-n, n + 1):
idx = acn_index(n, m)
if m != 0:
assert abs(y[idx]) < 1e-8, f"n={n} m={m} val={y[idx]}"
if __name__ == "__main__":
for fn in [
test_layout,
test_unit_vector_cardinals,
test_order1_cartesian_identity,
test_front_source_signs,
test_finite_all_channels,
test_zenith_only_z_orders,
]:
fn()
print("OK", fn.__name__)