-
Notifications
You must be signed in to change notification settings - Fork 14
RAP center time #169
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
base: develop
Are you sure you want to change the base?
RAP center time #169
Changes from all commits
5dbdb0b
f952ddb
c12898b
76e5f45
e8cda5b
80a3fc7
4769506
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| from pyrato.parameters import modulation_transfer_function | ||
| from pyrato.parameters import _sti_calc | ||
| from pyrato.parameters import _ambient_noise_correction | ||
| from pyrato.parameters import center_time | ||
| # parameter clarity tests | ||
| @pytest.mark.parametrize( | ||
| ("energy", "expected_shape"), | ||
|
|
@@ -1391,3 +1392,115 @@ def test_sti_ir_level_snr(): | |
| sti_test = speech_transmission_index_indirect( | ||
| ir, rir_type="acoustical", level=level, snr=snr) | ||
| np.testing.assert_allclose(sti_test, sti_expected, atol=0.07) | ||
|
|
||
| @pytest.mark.parametrize( | ||
| ("energy", "expected_shape"), | ||
| [ | ||
| # 1D single channel | ||
| (np.linspace(1, 0, 1000), (1,)), | ||
| # 2D two channels | ||
| (np.linspace((1, 0.5), (0, 0), 1000).T, (2,)), | ||
| # 3D multichannel (2x3 channels) | ||
| (np.arange(2 * 3 * 1000).reshape(2, 3, 1000), (2, 3)), | ||
| ], | ||
| ) | ||
| def test_center_time_accepts_timedata_and_returns_correct_shape( | ||
| energy, expected_shape, make_edc, | ||
| ): | ||
| """Test return shape and type of pyfar.TimeData input.""" | ||
| edc = make_edc(energy=energy, sampling_rate=1000) | ||
| result = center_time(edc) | ||
| assert isinstance(result, np.ndarray) | ||
| assert result.shape == expected_shape | ||
| assert result.shape == edc.cshape | ||
|
|
||
| def test_center_time_rejects_non_timedata(): | ||
| """Reject wrong input type.""" | ||
| with pytest.raises(TypeError, | ||
| match="energy_decay_curve must be a pyfar.TimeData"): | ||
| center_time(np.ones(100)) | ||
|
|
||
| def test_center_time_rejects_edc_not_starting_at_zero(): | ||
| """Reject EDC whose time axis does not start at zero.""" | ||
| edc = pf.TimeData(np.ones((1, 100)), np.arange(1, 101) / 1000) | ||
| with pytest.raises(ValueError, match="must start at time zero"): | ||
| center_time(edc) | ||
|
|
||
| def test_center_time_rejects_zero_initial_energy(): | ||
| """Reject EDC with zero initial energy (would cause division by zero).""" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make sence to check for a plausible ETC as well, e.g. if the energy is positive and decaying, sth like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This issue actually involves several methods. Should we open a new pull request for this?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first check would typically fail for experimental data where the decay curve is not strictly exponential (decaying sinusoids)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have opend an issue #172, its not part of this pr
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just decaying not exponential decaying. |
||
| edc = pf.TimeData(np.zeros((1, 100)), np.arange(100) / 1000) | ||
| with pytest.raises(ValueError, match="Initial energy"): | ||
| center_time(edc) | ||
|
|
||
| def test_center_time_accepts_non_uniform_time_spacing(): | ||
| """Accept EDC with non-uniform time spacing (trapezoid integration).""" | ||
| # monotonically increasing but not uniform | ||
| times = np.concatenate([[0, 0.001, 0.003], np.arange(4, 101) / 1000]) | ||
| edc = pf.TimeData(np.ones((1, len(times))), times) | ||
| result = center_time(edc) | ||
| assert np.all(np.isfinite(result)) | ||
|
|
||
| def test_center_time_non_uniform_spacing_analytical(): | ||
| r"""center_time() is correct for non-uniform spacing with known solution. | ||
|
|
||
| For a linear EDC e(t) = 1 - t/T over [0, T]: | ||
|
|
||
| T_s = integral(e(t), 0, T) / e(0) = (T/2) / 1 = T/2 | ||
| """ | ||
| T = 0.1 # total duration in seconds | ||
| # non-uniform time grid: dense at start, coarse at end | ||
| times = np.concatenate([ | ||
| np.linspace(0, 0.02, 20, endpoint=False), | ||
| np.linspace(0.02, T, 10), | ||
| ]) | ||
| edc_values = 1 - times / T | ||
| edc = pf.TimeData(edc_values[np.newaxis, :], times) | ||
| result = center_time(edc) | ||
| npt.assert_allclose(result, T / 2, rtol=1e-6) | ||
|
|
||
| def test_center_time_exponential_decay_analytical(make_edc): | ||
| r"""Center time for exponential EDC matches analytical solution. | ||
|
|
||
| For e(t) = exp(-alpha * t) with alpha = 13.8155 / RT60: | ||
|
|
||
| T_s = integral(e(t), 0, inf) / e(0) = 1 / alpha | ||
| """ | ||
| rt60 = 2.0 | ||
| sampling_rate = 1000 | ||
| total_samples = 5000 | ||
| edc = make_edc(rt=rt60, sampling_rate=sampling_rate, | ||
| total_samples=total_samples) | ||
| result = center_time(edc) | ||
|
|
||
| # Analytical expected value | ||
| a = 13.8155 / rt60 | ||
| expected = 1 / a | ||
| npt.assert_allclose(result, expected, atol=1e-3) | ||
|
|
||
| def test_center_time_multichannel(make_edc): | ||
| """Each channel is computed independently and results differ.""" | ||
| energy = np.stack([ | ||
| np.exp(-13.8155 / 1.0 * np.arange(2000) / 1000), | ||
| np.exp(-13.8155 / 2.0 * np.arange(2000) / 1000), | ||
| ]) | ||
| edc = make_edc(energy=energy, sampling_rate=1000) | ||
| result = center_time(edc) | ||
| assert result[1] > result[0] | ||
|
artur-pa marked this conversation as resolved.
|
||
|
|
||
| def test_center_time_nan_tail_returns_finite_result(make_edc): | ||
| """center_time() returns a finite result when the EDC tail is NaN. | ||
|
|
||
| Lundeby/Chu methods set the noise tail to NaN. The finite head should | ||
| still produce a valid Ts. | ||
| """ | ||
| rt60 = 1.0 | ||
| sampling_rate = 1000 | ||
| total_samples = 2000 | ||
| edc = make_edc(rt=rt60, sampling_rate=sampling_rate, | ||
| total_samples=total_samples) | ||
| # Simulate Lundeby/Chu truncation: set the last 500 samples to NaN | ||
| edc.time[..., 1500:] = np.nan | ||
|
|
||
| result = center_time(edc) | ||
|
|
||
| assert np.all(np.isfinite(result)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that this import requires are least scipy version 1.11.
Can you please update this in the pyproject.toml?