Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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: 1 addition & 1 deletion docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Modules
.. toctree::
:maxdepth: 1

modules/imkar
modules/imkar.scattering.diffuse


.. _examples gallery: https://pyfar-gallery.readthedocs.io/en/latest/examples_gallery.html
7 changes: 0 additions & 7 deletions docs/modules/imkar.rst

This file was deleted.

7 changes: 7 additions & 0 deletions docs/modules/imkar.scattering.diffuse.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
imkar.scattering.diffuse
========================

.. automodule:: imkar.scattering.diffuse
:members:
:undoc-members:
:show-inheritance:
6 changes: 6 additions & 0 deletions imkar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
__author__ = """The pyfar developers"""
__email__ = ''
__version__ = '0.1.0'

from . import scattering

__all__ = [
"scattering",
]
8 changes: 8 additions & 0 deletions imkar/scattering/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

"""Imkar scattering module."""

from . import diffuse

__all__ = [
"diffuse",
]
92 changes: 92 additions & 0 deletions imkar/scattering/diffuse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
This module contains functions for diffuse scattering calculations based on
ISO 17497-1:2004.
"""
import numpy as np
import pyfar as pf


def maximum_sample_absorption_coefficient(frequencies) -> pf.FrequencyData:
"""Maximum absorption coefficient of the test sample.

Based on section 6.3.4 in ISO 17497-1:2004 [#]_ the absorption coefficient
of the test sample should not exceed a value of :math:`alpha_s=0.5`.
However, if sound absorption is part of the sound-scattering structure,
this absorption shall also be present in the test sample.

Parameters
----------
frequencies : numpy.ndarray
The frequencies at which the absorption coefficient is calculated.

Returns
-------
alpha_s_max : pyfar.FrequencyData
The maximum sample absorption coefficient.

References
----------
.. [#] ISO 17497-1:2004, Sound-scattering properties of surfaces. Part 1:
Measurement of the random-incidence scattering coefficient in a
reverberation room. Geneva, Switzerland: International Organization
for Standards, 2004.

"""
# input checks
try:
frequencies = np.asarray(frequencies, dtype=float)
except ValueError as exc:
raise TypeError(
"frequencies must be convertible to a float array.") from exc
if frequencies.ndim != 1:
raise ValueError("frequencies must be a 1D array.")

# Calculate the maximum absorption coefficient
return pf.FrequencyData(
data=np.ones_like(frequencies) * 0.5,
frequencies=frequencies,
comment="Maximum absorption coefficient of the test sample",
)


def maximum_baseplate_scattering_coefficient(N: int = 1) -> pf.FrequencyData:
"""Maximum scattering coefficient for the base plate alone.

This is based on Table 1 in ISO 17497-1:2004 [#]_.

Parameters
----------
N : int
ratio of any linear dimension in a physical scale model to the
same linear dimension in full scale (1:N). The default is N=1.

Returns
-------
s_base_max : pyfar.FrequencyData
The maximum baseplate scattering coefficient.

References
----------
.. [#] ISO 17497-1:2004, Sound-scattering properties of surfaces. Part 1:
Measurement of the random-incidence scattering coefficient in a
reverberation room. Geneva, Switzerland: International Organization
for Standards, 2004.

"""
if not isinstance(N, int):
raise TypeError("N must be a positive integer.")
if N <= 0:
raise TypeError("N must be a positive integer.")
frequencies = [
100, 125, 160, 200, 250, 315, 400, 500, 630,
800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000,
]
s_base_max = [
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.10,
0.10, 0.10, 0.15, 0.15, 0.15, 0.20, 0.20, 0.20, 0.25,
]
return pf.FrequencyData(
data=s_base_max,
frequencies=np.array(frequencies)/N,
comment="Maximum scattering coefficient of the baseplate",
)
76 changes: 76 additions & 0 deletions tests/test_scattering_diffuse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import numpy as np
import pytest
import imkar.scattering.diffuse as isd
import pyfar as pf


def test_maximum_sample_absorption_coefficient_basic():
freqs = np.array([100, 200, 400, 800])
result = isd.maximum_sample_absorption_coefficient(freqs)
assert isinstance(result, pf.FrequencyData)
np.testing.assert_allclose(result.frequencies, freqs)
np.testing.assert_allclose(result.freq, 0.5)
assert "Maximum absorption coefficient" in result.comment


def test_maximum_sample_absorption_coefficient_list_input():
freqs = [100, 200, 400]
result = isd.maximum_sample_absorption_coefficient(freqs)
np.testing.assert_allclose(result.frequencies, np.array(freqs))
np.testing.assert_allclose(result.freq, 0.5)


def test_maximum_sample_absorption_coefficient_non_1d_input():
freqs = np.array([[100, 200], [300, 400]])
with pytest.raises(ValueError, match="frequencies must be a 1D array"):
isd.maximum_sample_absorption_coefficient(freqs)


def test_maximum_sample_absorption_coefficient_invalid_type():
freqs = "not_a_number"
with pytest.raises(
TypeError,
match="frequencies must be convertible to a float array"):
isd.maximum_sample_absorption_coefficient(freqs)


def test_maximum_baseplate_scattering_coefficient_default():
result = isd.maximum_baseplate_scattering_coefficient()
assert isinstance(result, pf.FrequencyData)
expected_freqs = np.array([
100, 125, 160, 200, 250, 315, 400, 500, 630,
800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000,
])
expected_data = np.array([[
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.10,
0.10, 0.10, 0.15, 0.15, 0.15, 0.20, 0.20, 0.20, 0.25,
]])
np.testing.assert_allclose(result.frequencies, expected_freqs)
np.testing.assert_allclose(result.freq, expected_data)
assert "baseplate" in result.comment


def test_maximum_baseplate_scattering_coefficient_with_scale():
N = 2
result = isd.maximum_baseplate_scattering_coefficient(N)
expected_freqs = np.array([
100, 125, 160, 200, 250, 315, 400, 500, 630,
800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000,
]) / N
np.testing.assert_allclose(result.frequencies, expected_freqs)

def test_maximum_baseplate_scattering_coefficient_invalid_type():
with pytest.raises(TypeError, match="N must be a positive integer."):
isd.maximum_baseplate_scattering_coefficient(N=1.5)
with pytest.raises(TypeError, match="N must be a positive integer."):
isd.maximum_baseplate_scattering_coefficient(N=-1)

def test_maximum_baseplate_scattering_coefficient_negative_N():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be _positive_N?

# Negative N is technically an integer, but let's check if it works
N = 5
result = isd.maximum_baseplate_scattering_coefficient(N)
expected_freqs = np.array([
100, 125, 160, 200, 250, 315, 400, 500, 630,
800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000,
]) / N
np.testing.assert_allclose(result.frequencies, expected_freqs)