Skip to content
Open
Show file tree
Hide file tree
Changes from all 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_N():
# Test with a positive integer N to verify expected behavior
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)