-
Notifications
You must be signed in to change notification settings - Fork 2
Feature: correlation method after mommertz #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ahms5
wants to merge
5
commits into
develop
Choose a base branch
from
scattering/correlation_method
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
imkar.scattering.freefield | ||
========================== | ||
|
||
.. automodule:: imkar.scattering.freefield | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
"""Imkar scattering module.""" | ||
|
||
from . import freefield | ||
|
||
__all__ = [ | ||
"freefield", | ||
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
"""Scattering calculation functions based on free-field data.""" | ||
import numpy as np | ||
import pyfar as pf | ||
|
||
|
||
def correlation_method( | ||
sample_pressure, reference_pressure, microphone_weights): | ||
r""" | ||
Calculate the incident-dependent free-field scattering coefficient. | ||
|
||
This function uses the Mommertz correlation method [#]_ to compute the | ||
scattering coefficient of the input data: | ||
|
||
.. math:: | ||
s = 1 - | ||
\frac{|\sum_w \underline{p}_{\text{sample}}(\vartheta,\varphi) | ||
\cdot \underline{p}_{\text{reference}}^*(\vartheta,\varphi) | ||
\cdot w(\vartheta,\varphi)|^2} | ||
{\sum_w |\underline{p}_{\text{sample}}(\vartheta,\varphi)|^2 | ||
\cdot w(\vartheta,\varphi) \cdot \sum_w | ||
|\underline{p}_{\text{reference}}(\vartheta,\varphi)|^2 | ||
\cdot w(\vartheta,\varphi) } | ||
|
||
where: | ||
- :math:`\underline{p}_{\text{sample}}` is the reflected sound | ||
pressure of the sample under investigation. | ||
- :math:`\underline{p}_{\text{reference}}` is the reflected sound | ||
pressure from the reference sample. | ||
- :math:`w` represents the area weights of the sampling, and | ||
:math:`\vartheta` and :math:`\varphi` are the ``colatitude`` | ||
and ``azimuth`` angles from the | ||
:py:class:`~pyfar.classes.coordinates.Coordinates` object. | ||
|
||
The test sample is assumed to lie in the x-y-plane. | ||
|
||
Parameters | ||
---------- | ||
sample_pressure : :py:class:`~pyfar.classes.audio.FrequencyData` | ||
Reflected sound pressure or directivity of the test sample. Its cshape | ||
must be (..., microphone_weights.size) and broadcastable to the | ||
cshape of ``reference_pressure``. | ||
reference_pressure : :py:class:`~pyfar.classes.audio.FrequencyData` | ||
Reflected sound pressure or directivity of the reference sample. Its | ||
cshape must be (..., microphone_weights.size) and broadcastable to the | ||
cshape of ``sample_pressure``. | ||
microphone_weights : array_like | ||
1D array containing the area weights for the microphone positions. | ||
No normalization is required. Its shape must match the last dimension | ||
in the cshape of ``sample_pressure`` and ``reference_pressure``. | ||
|
||
Returns | ||
------- | ||
scattering_coefficients : :py:class:`~pyfar.classes.audio.FrequencyData` | ||
The scattering coefficient for each incident direction as a function | ||
of frequency. | ||
|
||
References | ||
---------- | ||
.. [#] E. Mommertz, "Determination of scattering coefficients from the | ||
reflection directivity of architectural surfaces," Applied | ||
Acoustics, vol. 60, no. 2, pp. 201-203, June 2000, | ||
doi: 10.1016/S0003-682X(99)00057-2. | ||
|
||
""" | ||
# check input types | ||
if not isinstance(sample_pressure, pf.FrequencyData): | ||
raise TypeError("sample_pressure must be of type pyfar.FrequencyData") | ||
if not isinstance(reference_pressure, pf.FrequencyData): | ||
raise TypeError( | ||
"reference_pressure must be of type pyfar.FrequencyData") | ||
microphone_weights = np.atleast_1d( | ||
np.asarray(microphone_weights, dtype=float)) | ||
|
||
# check input dimensions | ||
if sample_pressure.cshape[-1] != microphone_weights.size: | ||
raise ValueError( | ||
"The last dimension of sample_pressure must match the size of " | ||
"microphone_weights") | ||
if reference_pressure.cshape[-1] != microphone_weights.size: | ||
raise ValueError( | ||
"The last dimension of reference_pressure must match the size of " | ||
"microphone_weights") | ||
|
||
if sample_pressure.cshape[:-1] != reference_pressure.cshape[:-1]: | ||
raise ValueError( | ||
"The cshape of sample_pressure and reference_pressure must be " | ||
"broadcastable except for the last dimension") | ||
# Test whether the objects are able to perform arithmetic operations. | ||
# e.g. does the frequency vectors match | ||
_ = sample_pressure + reference_pressure | ||
|
||
ahms5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# prepare data | ||
microphone_weights = microphone_weights[:, np.newaxis] | ||
p_sample = sample_pressure.freq | ||
p_reference = reference_pressure.freq | ||
|
||
# calculate according to mommertz correlation method Equation (5) | ||
p_sample_sum = np.sum(microphone_weights * np.abs(p_sample)**2, axis=-2) | ||
p_ref_sum = np.sum(microphone_weights * np.abs(p_reference)**2, axis=-2) | ||
p_cross_sum = np.sum( | ||
p_sample * np.conj(p_reference) * microphone_weights, axis=-2) | ||
|
||
data_scattering_coefficient \ | ||
= 1 - ((np.abs(p_cross_sum)**2)/(p_sample_sum*p_ref_sum)) | ||
|
||
# create pyfar.FrequencyData object | ||
scattering_coefficients = pf.FrequencyData( | ||
data_scattering_coefficient, | ||
sample_pressure.frequencies) | ||
|
||
return scattering_coefficients |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import pyfar as pf | ||
import numpy as np | ||
import numpy.testing as npt | ||
import pytest | ||
from imkar.scattering import freefield as sff | ||
|
||
|
||
def plane_wave(amplitude, direction, sampling): | ||
f = 5000 | ||
c = 343 | ||
x = sampling | ||
direction.cartesian = direction.cartesian/direction.radius | ||
dot_product = direction.x*x.x+direction.y*x.y+direction.z*x.z | ||
dot_product = dot_product[..., np.newaxis] | ||
f = np.atleast_1d(f) | ||
return pf.FrequencyData( | ||
amplitude*np.exp(-1j*2*np.pi*f/c*dot_product), | ||
frequencies=f, | ||
) | ||
|
||
|
||
def test_correlation_method_0(): | ||
sampling = pf.samplings.sph_equal_area(5000) | ||
sampling.weights = pf.samplings.calculate_sph_voronoi_weights(sampling) | ||
sampling = sampling[sampling.z>0] | ||
sample_pressure = plane_wave(1, pf.Coordinates(0, 0, 1), sampling) | ||
reference_pressure = plane_wave(1, pf.Coordinates(0, 0, 1), sampling) | ||
s = sff.correlation_method( | ||
sample_pressure, reference_pressure, sampling.weights, | ||
) | ||
npt.assert_almost_equal(s.freq, 0) | ||
|
||
|
||
@pytest.mark.parametrize("s_scatter", [0.1, 0.5, 0.9]) | ||
@pytest.mark.parametrize("Phi_scatter_deg", [30, 60, 90, 120, 150, 42]) | ||
def test_correlation_method_with_plane_waves(s_scatter, Phi_scatter_deg): | ||
s_spec = 1-s_scatter | ||
Phi_spec = 45/180*np.pi | ||
Phi_scatter = Phi_scatter_deg/180*np.pi | ||
R_spec = np.sqrt(s_spec) | ||
R_scatter = np.sqrt(np.abs(s_scatter*np.sin(Phi_spec)/np.sin(Phi_scatter))) | ||
sampling = pf.samplings.sph_equal_area(10000) | ||
sampling.weights = pf.samplings.calculate_sph_voronoi_weights(sampling) | ||
sampling = sampling[sampling.z>0] | ||
sample_pressure = plane_wave( | ||
R_spec, | ||
pf.Coordinates.from_spherical_front(np.pi/2, Phi_spec, 1), sampling) | ||
sample_pressure += plane_wave( | ||
R_scatter, | ||
pf.Coordinates.from_spherical_front(np.pi/2, Phi_scatter, 1), sampling) | ||
reference_pressure = plane_wave( | ||
1, pf.Coordinates.from_spherical_front(np.pi/2, Phi_spec, 1), sampling) | ||
sd_spec = 1-sff.correlation_method( | ||
sample_pressure, reference_pressure, sampling.weights, | ||
) | ||
reference_pressure = plane_wave( | ||
1, pf.Coordinates.from_spherical_front( | ||
np.pi/2, Phi_scatter, 1), sampling) | ||
sd_scatter = 1-sff.correlation_method( | ||
sample_pressure, reference_pressure, sampling.weights, | ||
) | ||
npt.assert_almost_equal(sd_spec.freq, s_spec, 1) | ||
npt.assert_almost_equal(sd_scatter.freq, s_scatter, 1) | ||
|
||
reference_pressure = plane_wave( | ||
1, pf.Coordinates.from_spherical_front( | ||
np.pi/2, Phi_spec+5/180*np.pi, 1), sampling) | ||
sd_scatter_0 = 1-sff.correlation_method( | ||
sample_pressure, reference_pressure, sampling.weights, | ||
) | ||
npt.assert_almost_equal(sd_scatter_0.freq, 0, 1) | ||
|
||
|
||
def test_correlation_method(): | ||
sampling = pf.samplings.sph_equal_area(5000) | ||
sampling.weights = pf.samplings.calculate_sph_voronoi_weights(sampling) | ||
sampling = sampling[sampling.z>0] | ||
sample_pressure = plane_wave(1, pf.Coordinates(0, 0, 1), sampling) | ||
reference_pressure = plane_wave(1, pf.Coordinates(0, 0, 1), sampling) | ||
s = sff.correlation_method( | ||
sample_pressure, reference_pressure, sampling.weights, | ||
) | ||
npt.assert_almost_equal(s.freq, 0) | ||
|
||
|
||
def test_correlation_method_invalid_sample_pressure_type(): | ||
reference_pressure = pf.FrequencyData(np.array([1, 2, 3]), [100, 200, 300]) | ||
microphone_weights = np.array([0.5, 0.5, 0.5]) | ||
with pytest.raises( | ||
TypeError, match="sample_pressure must be of type " | ||
"pyfar.FrequencyData"): | ||
sff.correlation_method( | ||
"invalid_type", reference_pressure, microphone_weights) | ||
|
||
|
||
def test_correlation_method_invalid_reference_pressure_type(): | ||
sample_pressure = pf.FrequencyData(np.array([1, 2, 3]), [100, 200, 300]) | ||
microphone_weights = np.array([0.5, 0.5, 0.5]) | ||
with pytest.raises( | ||
TypeError, match="reference_pressure must be of type " | ||
"pyfar.FrequencyData"): | ||
sff.correlation_method( | ||
sample_pressure, "invalid_type", microphone_weights) | ||
|
||
|
||
def test_correlation_method_mismatched_sample_pressure_weights(): | ||
sample_pressure = pf.FrequencyData(np.array([[1, 2, 3]]), [100, 200, 300]) | ||
reference_pressure = pf.FrequencyData( | ||
np.array([[1, 2, 3]]), [100, 200, 300]) | ||
microphone_weights = np.array([0.5, 0.5]) | ||
with pytest.raises( | ||
ValueError, match="The last dimension of sample_pressure must " | ||
"match the size of microphone_weights"): | ||
sff.correlation_method( | ||
sample_pressure, reference_pressure, microphone_weights) | ||
|
||
|
||
def test_correlation_method_mismatched_reference_pressure_weights(): | ||
sample_pressure = pf.FrequencyData(np.array([[1, 2, 3]]), [100, 200, 300]) | ||
reference_pressure = pf.FrequencyData( | ||
np.array([[1, 2, 3]]), [100, 200, 300]) | ||
microphone_weights = np.array([0.5, 0.5]) | ||
with pytest.raises( | ||
ValueError, match="The last dimension of sample_pressure must " | ||
"match the size of microphone_weights"): | ||
sff.correlation_method( | ||
sample_pressure, reference_pressure, microphone_weights) | ||
|
||
|
||
def test_correlation_method_mismatched_sample_reference_shapes(): | ||
sample_pressure = pf.FrequencyData(np.array([[1, 2, 3]]), [100, 200, 300]) | ||
reference_pressure = pf.FrequencyData(np.array([[1, 2]]), [100, 200]) | ||
microphone_weights = np.array([0.5, 0.5, 0.5]) | ||
with pytest.raises( | ||
ValueError, match="The last dimension of sample_pressure must " | ||
"match the size of microphone_weights"): | ||
sff.correlation_method( | ||
sample_pressure, reference_pressure, microphone_weights) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.