Skip to content

Conversation

@ahms5
Copy link
Member

@ahms5 ahms5 commented Apr 25, 2025

Which issue(s) are closed by this pull request?

Closes #12

Changes proposed in this pull request:

  • introduce correlation method after mommertz, in other words, calcualte scattering coefficient from freefield measurements depending on the incident direciton

requires #32

@ahms5 ahms5 added this to the v0.1.0 milestone Apr 25, 2025
@ahms5 ahms5 added documentation Improvements or additions to documentation enhancement New feature or request labels Apr 25, 2025
@ahms5 ahms5 self-assigned this Apr 25, 2025
@ahms5 ahms5 moved this from Backlog to Require review in Weekly Planning May 9, 2025
@ahms5 ahms5 changed the title draft correlation method after mommertz Feature: correlation method after mommertz May 26, 2025
@ahms5 ahms5 requested a review from Copilot May 26, 2025 23:49
Copilot

This comment was marked as outdated.

Copy link
Member

@f-brinkmann f-brinkmann left a comment

Choose a reason for hiding this comment

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

Thanks for the feature. It generally looks good to me. I had mostly minor comments.

- :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.
Copy link
Member

Choose a reason for hiding this comment

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

I would rather link here than to the coordinates object for explaining the angles: https://pyfar.readthedocs.io/en/stable/classes/pyfar.coordinates.html#coordinate-systems. And I think it would help to mention that they denote the incidence direction

Returns
-------
scattering_coefficients : :py:class:`~pyfar.classes.audio.FrequencyData`
The scattering coefficient for each incident direction as a function
Copy link
Member

Choose a reason for hiding this comment

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

Maybe mention here and also above, that the channels up to the last channel dimension of sample_pressure, reference_pressure and scattering_coefficients represent the incident direction. It might help to get an example as 'cshape = (45, 25) denotes a measurement with 45 incidenet directions and recorded at 25 microphone positions.'

Copy link
Member Author

Choose a reason for hiding this comment

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

actually it is just relevant what the last cdim is, because this is the receiver grid, from which we are averaging. what ever is before that (typically incident direction) will come out. it can also be just one incident direction. I tired to make it more clear in th docstring

and ``azimuth`` angles from the
:py:class:`~pyfar.classes.coordinates.Coordinates` object.

The test sample is assumed to lie in the x-y-plane.
Copy link
Member

Choose a reason for hiding this comment

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

Is this relevant for the computation? Otherwise this information could be omitted.

Comment on lines 66 to 70
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")
Copy link
Member

Choose a reason for hiding this comment

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

This will pass also for signals. You have to use type(sample_pressure) == pf.FrequencyData

Copy link
Member Author

Choose a reason for hiding this comment

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

yes it can also be an impulse response, where we just use freq data. I made it more clear in docs and checks

Comment on lines 84 to 87
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")
Copy link
Member

Choose a reason for hiding this comment

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

I think this error can not be reached and should be deleted. One of the two if-cases above would already if sample_pressure.cshape[:-1] != reference_pressure.cshape[:-1]

from imkar.scattering import freefield as sff


def plane_wave(amplitude, direction, sampling):
Copy link
Member

Choose a reason for hiding this comment

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

Please add a minimal docstring to ease maintainance



def test_correlation_method_0():
sampling = pf.samplings.sph_equal_area(5000)
Copy link
Member

Choose a reason for hiding this comment

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

Better already use samplings from spharpy?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll shift it to the todo, after the spharpy release


@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):
Copy link
Member

Choose a reason for hiding this comment

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

This would take a little while to understand. Can you add some comments for guidance?

)


def test_correlation_method_0():
Copy link
Member

Choose a reason for hiding this comment

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

Could this be integrated into test_correlation_method_with_plane_waves?

npt.assert_almost_equal(sd_scatter_0.freq, 0, 1)


def test_correlation_method():
Copy link
Member

Choose a reason for hiding this comment

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

In how far is this different from test_correlation_method_0 above?

@ahms5 ahms5 requested review from Copilot and f-brinkmann July 29, 2025 08:32
Copilot

This comment was marked as outdated.

@ahms5 ahms5 requested a review from Copilot July 29, 2025 10:09
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new correlation method for calculating scattering coefficients from free-field measurements based on the Mommertz correlation method. The implementation allows users to compute scattering coefficients by analyzing reflection patterns from samples compared to reference measurements.

Key changes:

  • Implements the Mommertz correlation method for scattering coefficient calculation
  • Adds comprehensive test coverage for various scattering scenarios
  • Updates module structure and documentation to include the new freefield scattering functionality

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
imkar/scattering/freefield.py Core implementation of the correlation_method function with mathematical formulation and input validation
tests/test_scattering_freefield.py Comprehensive test suite covering edge cases, parametric testing, and input validation
imkar/scattering/init.py New module initialization file exposing freefield submodule
imkar/init.py Updated package initialization to include scattering module
docs/modules/imkar.scattering.freefield.rst Documentation file for the new freefield module
docs/modules/imkar.rst Removed original imkar module documentation
docs/api_reference.rst Updated API reference to point to new freefield module
Comments suppressed due to low confidence (2)

tests/test_scattering_freefield.py:130

  • The error message in the test doesn't match the actual error message from the function. The function accepts both FrequencyData and Signal types, but the test expects an error message mentioning only FrequencyData.
            TypeError, match="sample_pressure must be of type "

tests/test_scattering_freefield.py:140

  • The error message in the test doesn't match the actual error message from the function. The function accepts both FrequencyData and Signal types, but the test expects an error message mentioning only FrequencyData.
            TypeError, match="reference_pressure must be of type "

Copy link
Member

@sikersten sikersten left a comment

Choose a reason for hiding this comment

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

Looks good, only minor things. I'm not sure if I understood the testing in detail though...

cshape of ``reference_pressure``. The frequency vectors of both
``sample_pressure`` and ``reference_pressure`` must match.
reference_pressure : pyfar.FrequencyData, pyfar.Signal
Reflected sound pressure or directivity of the reference sample. Its
Copy link
Member

Choose a reason for hiding this comment

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

What is meant with the "or"? Do reflected sound pressure and directivity mean the same thing or can both be used as input for the function?

Copy link
Member Author

Choose a reason for hiding this comment

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

both can be used, because it is proportional

"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
Copy link
Member

Choose a reason for hiding this comment

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

Is checking the frequencies the only purpose? If yes, +1 for an explicit solution

Comment on lines +96 to +102
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)
Copy link
Member

Choose a reason for hiding this comment

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

It would be cool to have pyfar functions sum, abs, conj in the future, wouldn't it? pyfar/pyfar#830

npt.assert_almost_equal(sd_scatter_0.freq, 0, 1)


def test_correlation_one_scattering():
Copy link
Member

Choose a reason for hiding this comment

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

Short docstrings would be helpful :)

@ahms5 ahms5 requested a review from sikersten August 22, 2025 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

Status: Require review

Development

Successfully merging this pull request may close these issues.

3 participants