Skip to content

Reader for .ita files and test functions for it#839

Open
h-chmeruk wants to merge 16 commits intodevelopfrom
read_ita_finalize
Open

Reader for .ita files and test functions for it#839
h-chmeruk wants to merge 16 commits intodevelopfrom
read_ita_finalize

Conversation

@h-chmeruk
Copy link
Copy Markdown
Contributor

Which issue(s) will be closed by this pull request?

Replaces #720
Closes #206

Here is a draft implementation of test functions for the .ita Reader. If you have any suggestions or comments regarding the test functions or the read_ita function itself, please do not hesitate to write them.

@h-chmeruk h-chmeruk added duplicate This issue or pull request already exists audio Classes pyfar.classes.audio labels Sep 4, 2025
if ita_coordinates.cart.size == 0:
return pf.Coordinates()
if ita_coordinates.cart.shape[-1] != 3:
raise ValueError(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I also thought about implementing a function to test this ValueError, but I was unable to create an .ita file with an invalid number of dimensions (as there are enough exceptions for this case in MATLAB).
If anyone knows how to test this, I would appreciate some help :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

seems to be difficult, in this case you can skip the test, but we should keep the error handling here

@h-chmeruk h-chmeruk self-assigned this Sep 29, 2025
@ahms5 ahms5 mentioned this pull request Oct 8, 2025
3 tasks
Copy link
Copy Markdown
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.

Thanks finalizing this!

pyfar/io/io.py Outdated


def read_ita(filename):
"""Read a *.ita file from the ITA-toolbox.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It may be nice to link to the toolbox here (adding it as reference below): https://www.ita-toolbox.org/

pyfar/io/io.py Outdated
Returns
-------
data : pyfar.Signal, pyfar.TimeData, pyfar.FrequencyData
The data contained in the *.ita file.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
The data contained in the *.ita file.
The audio data contained in the *.ita file.

pyfar/io/io.py Outdated


def read_ita(filename):
"""Read a *.ita file from the ITA-toolbox.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"""Read a *.ita file from the ITA-toolbox.
"""Read an *.ita file created using the ITA-toolbox.

pyfar/io/io.py Outdated
return data, object_coordinates, channel_coordinates, metadata


def _to_coordinates(ita_coordinates):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
def _to_coordinates(ita_coordinates):
def _ita_to_pyfar_coordinates(ita_coordinates):


# checks if *.ita object is itaAudio or itaResult
if (hasattr(matlab_data, 'signalType')): # itaAudio
fft_norm = 'none' if matlab_data.signalType == 'energy' else 'rms'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe add a comment here, that this results from the ITA-toolbox logic?

pyfar/io/io.py Outdated
weights=weights)

# import as spherical coordinates: r, theta, phi
if hasattr(ita_coordinates, 'sph'):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible that ita_coordinates has both attributes "cart" and "sph"? If not, this would need to be an elif connected to line 1035

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not an expert on the ITA-Toolbox, but I think that ita_coordinates can have both attributes at the same time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mberz can you clarify? The files I saw, that contained both had identical data stored in different coordinate conventions, I think.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can either be 'cart' or 'sph', similar to what we has before in our coordinates class.

pyfar/io/io.py Outdated
ita_coordinates.sph[..., 0],
weights=weights)
else:
return pf.Coordinates()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would suggest returning None here, since this object won't be of any use

tests/test_io.py Outdated
"""Tests correct exception raise for example *.ita files."""
filepath = os.path.join('tests', 'test_io_data', filename)
if (filename == 'freq_itaResult.ita' and data_type == 'signal'):
message = "The itaResult object can't contain a signal."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This message is not defined in the function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this file should not be here

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please move the added content to test_io_read_ita.py

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your review!
I just noticed that this file tests/test_io.py contains an outdated test function test_read_ita(filename, data_type) that we don't need. Therefore, I would simply remove this function from the file.

@h-chmeruk
Copy link
Copy Markdown
Contributor Author

h-chmeruk commented Oct 16, 2025

Thanks finalizing this!

* Please check the reason for the failing tests, I think this may have to do with obsolete test filtes.

* I would suggest removing all stars * before .ita in the docstrings, since these are strangly rendered: https://pyfar--839.org.readthedocs.build/en/839/modules/pyfar.io.html#pyfar.io.read_ita

Thanks!
Regarding the stars * before .ita in the docstring:
I could, of course, simply remove all stars, but I could also enclose the * .ita in backticks so that the text is rendered as * .ita.
Is this a better solution, or should I rather remove all asterisks before .ita?

(I've already tested it, it works, and it looks like cursive.)

@h-chmeruk h-chmeruk marked this pull request as ready for review October 16, 2025 08:59
Copy link
Copy Markdown
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! I found data that is returned twice. Apart from that its mostly small suggestions.

pyfar/io/io.py Outdated
# import as spherical coordinates: r, theta, phi
if hasattr(ita_coordinates, 'sph'):
if ita_coordinates.cart.size == 0:
return pf.Coordinates()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See comment above from sikersten. I would suggest to return None here, too.

pyfar/io/io.py Outdated
data_in, matlab_data.abscissa,
comment=comment)

metadata = _matlab_to_dict(matlab_data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This return redundant information, e.g., matlab_data.sampling_rate and matlab_data.data that are already returned in the Signal. I would suggest to remove all data that is already returned elsewehre.

Copy link
Copy Markdown
Contributor Author

@h-chmeruk h-chmeruk Nov 11, 2025

Choose a reason for hiding this comment

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

Thank you for your review!

I'm not quite sure what exactly I should do.
Are you suggesting including less redundant data in the metadata? For example, removing the matlab_data.domain or matlab_data.sampling_rate from the metadata?

Wouldn't that be too confusing? Because the variable data stores an instance of pf.Signal/pf.TimeData/pf.FrequencyData, and in the variable metadata we only have the raw data for this instance, not the instance itself.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point Fabian, since there is not straight forward solution for this, I would still leave it as it is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to exclude at least the matlab_data.data from metadata? This bascially doubles the RAM requirements and might be annyoing if reading large files.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 for @f-brinkmann's suggestion

pyfar/io/io.py Outdated
Comment on lines +983 to +986
object_coordinates : pyfar.Coordinates
The object coordinates of the `*.ita` file.
channel_coordinates : pyfar.Coordinates
The channel coordinates of the `*.ita` file.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In case we follow sikerstens suggestion below, this should become

Suggested change
object_coordinates : pyfar.Coordinates
The object coordinates of the `*.ita` file.
channel_coordinates : pyfar.Coordinates
The channel coordinates of the `*.ita` file.
object_coordinates : pyfar.Coordinates, None
The object coordinates of the `*.ita` file if they exist, ``None`` otherwise.
channel_coordinates : pyfar.Coordinates, None
The channel coordinates of the `*.ita` file if they exist, ``None`` otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe add a note on the difference of object and channel coordinates? Do we need both? Just asking because I'm not familiar with this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The ITA documentation in MATLAB contains some brief descriptions:

channelCoordinates: coordinates for each channel (must be itaCoordinates)
objectCoordinates: the coordinates of the measured object

Would that be enough for us? I am also not very familiar with the purpose and functionality of channelCoordinates and objectCoordinates, so I would appreciate the opinion of someone who worked with this :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same concept as in sofa with listener and receiver.
One object can have multiple channels with relative positioning to the object.

pyfar/io/io.py Outdated
weights=weights)

# import as spherical coordinates: r, theta, phi
if hasattr(ita_coordinates, 'sph'):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mberz can you clarify? The files I saw, that contained both had identical data stored in different coordinate conventions, I think.

pyfar/io/io.py Outdated
mat_dict = {}
for fieldname in mat_obj._fieldnames:
elem = mat_obj.__dict__[fieldname]
if isinstance(elem, spio.matlab.mio5_params.mat_struct):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this line causes the deprecation errors and should be changed accordingly:

DeprecationWarning: Please import mat_struct from the scipy.io.matlab namespace; the scipy.io.matlab.mio5_params namespace is deprecated and will be removed in SciPy 2.0.0.

Copy link
Copy Markdown
Member

@ahms5 ahms5 left a comment

Choose a reason for hiding this comment

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

nice, thank you!

pyfar/io/io.py Outdated
"""
Read an `*.ita` file created using the ITA-toolbox.

ITA-Tollbox is a MATLAB Toolbox for Acoustics [#]_.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ITA-Tollbox is a MATLAB Toolbox for Acoustics [#]_.
ITA-Toolbox is a MATLAB Toolbox for Acoustics [#]_.



def _ita_to_pyfar_coordinates(ita_coordinates):
if hasattr(ita_coordinates, 'weights'):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe add a short docstring here.

pyfar/io/io.py Outdated
weights=weights)

# import as spherical coordinates: r, theta, phi
if hasattr(ita_coordinates, 'sph'):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can either be 'cart' or 'sph', similar to what we has before in our coordinates class.

pyfar/io/io.py Outdated


def _matlab_to_dict(mat_obj):
"""Recursively transform MATLAB struct objects to nested dictionaries."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe also add documation about input and output of the method

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe move to tests/test_io_data/read_ita/* in this way it would be cleaner and easier to distinguish where it bolongs.

Comment on lines +49 to +51
assert np.array_equal(metadata_channel_coords_reshaped,
channel_coords.cartesian) and \
np.array_equal(channel_coords.cartesian, channelCoordinates)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
assert np.array_equal(metadata_channel_coords_reshaped,
channel_coords.cartesian) and \
np.array_equal(channel_coords.cartesian, channelCoordinates)
assert np.array_equal(metadata_channel_coords_reshaped,
channel_coords.cartesian)
assert np.array_equal(channel_coords.cartesian, channelCoordinates)

import os
import pytest
import numpy as np

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is there any test about the metadata which is not part of other returned data?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review!

Regarding your question: No, not really.
Which data from the metadata should I also test?

In the metadata dictionary there are also keys such as "classname", "classversion", "comment", "history", "fileName", "dateCreated", "dateModified", "dateSaved", "userData", "plotAxesProperties", "plotLineProperties", "userName", "channelNames", "channelUnits", "channelSensors", "channelUserData", "dataTypeOutput", but they are mostly empty in most files we are testing.

@github-project-automation github-project-automation bot moved this from Backlog to Require review in Weekly Planning Feb 6, 2026
@mberz mberz moved this from Require review to Implementation in progress in Weekly Planning Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

audio Classes pyfar.classes.audio duplicate This issue or pull request already exists

Projects

Status: Implementation in progress

Development

Successfully merging this pull request may close these issues.

6 participants