Skip to content

Commit

Permalink
Fixes a few potential bugs with the MicrophoneArray class and adds so…
Browse files Browse the repository at this point in the history
…me tests. (issue #355)
  • Loading branch information
fakufaku committed Dec 7, 2024
1 parent f6d11c9 commit 5dc0594
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 22 deletions.
73 changes: 51 additions & 22 deletions pyroomacoustics/beamforming.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

from __future__ import division

from typing import Sequence

import numpy as np
import scipy.linalg as la

Expand Down Expand Up @@ -342,56 +344,89 @@ class MicrophoneArray(object):
"""Microphone array class."""

def __init__(self, R, fs, directivity=None):
R = np.array(R)
self.dim = R.shape[0] # are we in 2D or in 3D
self.nmic = R.shape[1] # number of microphones
# The array geometry is stored in a (dim, n_mics) array.
self.R = np.array(R) # array geometry

# Check the shape of the passed array
if self.dim != 2 and self.dim != 3:
if self.dim not in (2, 3):
dim_mismatch = True
else:
dim_mismatch = False

if R.ndim != 2 or dim_mismatch:
if self.R.ndim != 2 or dim_mismatch:
raise ValueError(
"The location of microphones should be described by an array_like "
"object with 2 dimensions of shape `(2 or 3, n_mics)` "
"where `n_mics` is the number of microphones. Each column contains "
"the location of a microphone."
)

self.R = R # array geometry

self.fs = fs # sampling frequency of microphones
self.set_directivity(directivity)

self.signals = None

self.center = np.mean(R, axis=1, keepdims=True)

@property
def dim(self):
return self.R.shape[0] # are we in 2D or in 3D

def __len__(self):
return self.R.shape[1]

@property
def nmic(self):
"""The number of microphones of the array."""
return self.__len__()

@property
def M(self):
"""The number of microphones of the array."""
return self.__len__()

@property
def is_directive(self):
return any([d is not None for d in self.directivity])

def set_directivity(self, directivities):
"""
This functions sets self.directivity as a list of directivities with `n_mics` entries,
where `n_mics` is the number of microphones
This functions sets self.directivity as a list of directivities with
`n_mics` entries, where `n_mics` is the number of microphones.
Parameters
-----------
directivities:
single directivity for all microphones or a list of directivities for each microphone
A single directivity for all microphones or a list of directivities
for each microphone
"""

if isinstance(directivities, list):
def _is_correct_type(directivity):
return directivity is None or isinstance(directivity, Directivity)

if isinstance(directivities, Sequence):
# list of directivities specified
assert all(isinstance(x, Directivity) for x in directivities)
assert len(directivities) == self.nmic
self.directivity = directivities
for d in directivities:
if not _is_correct_type(d):
raise TypeError(
"Directivities should be of Directivity type, or None (got "
f"{type(d)})."
)
if not len(directivities) == self.nmic:
raise ValueError(
"Please provide a single Directivity for all microphones, or one "
f"per microphone. Got {len(directivities)} directivities for "
f"{self.nmic} mics."
)
self.directivity = list(directivities)
else:
if not _is_correct_type(directivities):
raise TypeError(
"Directivities should be of Directivity type, or None (got "
f"{type(directivities)})."
)
# only 1 directivity specified
assert directivities is None or isinstance(directivities, Directivity)
self.directivity = [directivities] * self.nmic

def record(self, signals, fs):
Expand Down Expand Up @@ -505,6 +540,7 @@ def append(self, locs):
self.directivity += locs.directivity
else:
self.R = np.concatenate((self.R, locs), axis=1)
self.directivity += [None] * locs.shape[1]

# in case there was already some signal recorded, just pad with zeros
if self.signals is not None:
Expand All @@ -518,13 +554,6 @@ def append(self, locs):
axis=0,
)

def __len__(self):
return self.R.shape[1]

@property
def M(self):
return self.__len__()


class Beamformer(MicrophoneArray):
"""
Expand Down
100 changes: 100 additions & 0 deletions pyroomacoustics/tests/test_microphone_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import pyroomacoustics as pra
import numpy as np
import pytest

_FS = 16000

mic_dir0 = pra.FigureEight(
orientation=pra.DirectionVector(azimuth=90, colatitude=15, degrees=True)
)
mic_dir1 = pra.FigureEight(
orientation=pra.DirectionVector(azimuth=180, colatitude=15, degrees=True)
)


@pytest.mark.parametrize("shape", ((1, 2, 3), (10, 2), (1, 10), (10,)))
def test_microphone_array_invalid_shape(shape):

locs = np.ones(shape)
with pytest.raises(ValueError):
pra.MicrophoneArray(locs, fs=_FS)


@pytest.mark.parametrize(
"directivity, exception_type",
(
("omni", TypeError),
(["omni"] * 3, TypeError),
([mic_dir0, "omni", mic_dir1] * 3, TypeError),
([mic_dir0, mic_dir1], ValueError),
),
)
def test_microphone_array_invalid_directivity(directivity, exception_type):

locs = np.ones((3, 3))
with pytest.raises(exception_type):
pra.MicrophoneArray(locs, fs=_FS, directivity=directivity)


@pytest.mark.parametrize(
"shape, with_dir, same_dir",
(
((2, 1), False, False),
((2, 2), False, False),
((2, 3), False, False),
((3, 1), False, False),
((3, 3), False, False),
((3, 4), False, False),
((2, 3), True, False),
((3, 4), True, False),
((2, 3), True, True),
((3, 4), True, True),
),
)
def test_microphone_array_shape_correct(shape, with_dir, same_dir):

locs = np.ones(shape)
if with_dir:
if same_dir:
mdir = [mic_dir0] * shape[1]
else:
mdir = [mic_dir0, mic_dir1] + [None] * (shape[1] - 2)
else:
mdir = None
mic_array = pra.MicrophoneArray(locs, fs=_FS, directivity=mdir)

assert mic_array.dim == shape[0]
assert mic_array.M == shape[1]
assert mic_array.nmic == mic_array.M
assert len(mic_array.directivity) == shape[1]


@pytest.mark.parametrize(
"shape1, shape2, with_dir, from_raw_locs",
(
((3, 2), (3, 2), False, False),
((3, 2), (3, 2), False, True),
((3, 2), (3, 2), False, False),
((3, 2), (3, 2), False, True),
((3, 2), (3, 2), True, False),
((3, 2), (3, 2), True, True),
((3, 2), (3, 1), False, False),
((3, 2), (3, 1), False, True),
),
)
def test_microphone_array_append(shape1, shape2, with_dir, from_raw_locs):
if with_dir:
mdir = [mic_dir0, mic_dir1] + [None] * (shape1[1] - 2)
else:
mdir = None

mic_array = pra.MicrophoneArray(np.ones(shape1), fs=_FS, directivity=mdir)

if from_raw_locs:
mic_array.append(np.ones(shape2))

else:
mic_array.append(pra.MicrophoneArray(np.ones(shape2), fs=_FS))

assert mic_array.nmic == shape1[1] + shape2[1]
assert len(mic_array.directivity) == shape1[1] + shape2[1]

0 comments on commit 5dc0594

Please sign in to comment.