Conversation
implement tests
Adapt docstrings
change property name adapt tests
Allow caxis_spherical_harmonics to be a tuple
mberz
left a comment
There was a problem hiding this comment.
Looks very much feasible!
I think we'll need an update to the n_max property and additional checks to ensure that the order of each dimensions corresponding to spherical harmonic data matches.
Or would you suggest to allow different orders for each dimension?
| # check if all sh_channels match | ||
| sh_channels = [data.shape[a] for a in axis] | ||
|
|
||
| if len(set(sh_channels)) != 1: | ||
| raise ValueError( | ||
| "All SH axes must have the same number of channels, " | ||
| f"but got {sh_channels}" | ||
| ) |
There was a problem hiding this comment.
I think this check should not be necessary, since the order governs the total number of channels. So all axes containing spherical harmonic data should have the same length.
So this check should probably be moved to the respective SphericalHarmonic* classes.
| # check if all sh_channels match | ||
| sh_channels = [data.shape[a] for a in axis] | ||
|
|
||
| if len(set(sh_channels)) != 1: |
There was a problem hiding this comment.
same comment as above. Implementing the check also makes individual checks in each function obsolete.
move check if all spherical harmonics axes have equal length to data init
test change_channel_convention and renormalize with mutlichannel data
implement tests
Adapt docstrings
change property name adapt tests
d345de4 to
a6f6306
Compare
…property_multichannel
f-brinkmann
left a comment
There was a problem hiding this comment.
Thanks for pushing ths forward. Docs look mainly good but I think I found one bug and have three things for general discussion. I did not yet review the tests.
- I think renormalize does not work as intended yet (see detailed comment). If true, tests should be adapted to detect the unwanted behavior and test the desired.
- I think it would be more consistent and easier to handle if
caxis_spherical_harmonicswould always be a tuple. - This is the biggest: We are inheriting from
pyfar._Audiowhich has thereshape,transposeandTmethods that move around axes. I think we need to overload them in_SphericalHarmonicAudioto changecaxis_spherical_harmonicsaccordingly. Should be rather straigt forward: call the parent function first, then updatecaxis_spherical_harmonics. For saftey I would suggest to check dimensions again and would suggest to add a private setter forcaxis_spherical_harmonicsto_SphericalHarmonicAudiothat does the checks. This would also reduce code redundancy, because these checks are implemented three times at the moment (once in each SH Audio class).
| samples and the second to last the spherical harmonic coefficients. If | ||
| the raw data have more then 3 dimensions `caxis_spherical_harmonics` | ||
| defines the axis holding the spherical harmonic coefficients. The | ||
| default is -1 (second to last channel axis). Accordingly, the default |
There was a problem hiding this comment.
should be last caxis or second to last axis
| default is -1 (second to last channel axis). Accordingly, the default | |
| default is -1 (last channel axis). Accordingly, the default |
(should be changed everywhere)
| ``float`` or ``complex``. Data of type ``int`` is converted to | ||
| ``float``. | ||
| coefficients with 1024 samples each, and | ||
| `caxis_spherical_harmonics` = -1. The data can be ``int``, ``float`` |
There was a problem hiding this comment.
| `caxis_spherical_harmonics` = -1. The data can be ``int``, ``float`` | |
| a `caxis_spherical_harmonics` of -1. The data can be ``int``, ``float`` |
or
| `caxis_spherical_harmonics` = -1. The data can be ``int``, ``float`` | |
| ``caxis_spherical_harmonics = -1``. The data can be ``int``, ``float`` |
(should be changed everywhere)
| if abs(caxis_spherical_harmonics) > data.ndim: | ||
| raise ValueError( | ||
| f"caxis_spherical_harmonics ({caxis_spherical_harmonics}) " | ||
| f"exceeds the number of dimensions of data ({data.ndim})") |
There was a problem hiding this comment.
I think this fails if caxis_spherical_harmonics is a tuple. It can probably be omitted because it is checked below as well.
| if isinstance(caxis_spherical_harmonics, int): | ||
| caxis_spherical_harmonics = (caxis_spherical_harmonics, ) | ||
|
|
||
| for caxis in caxis_spherical_harmonics: | ||
| if abs(caxis) > data.ndim: | ||
| raise ValueError( | ||
| f"caxis_spherical_harmonics " | ||
| f"({caxis}) exceeds the number " | ||
| f"of dimensions of data ({data.ndim})") |
There was a problem hiding this comment.
Isn't this code identical for all three SHAudio classes? I would suggest to put it into a private function to make it reusable
| caxis_spherical_harmonics = (caxis_spherical_harmonics, ) | ||
|
|
||
| for caxis in caxis_spherical_harmonics: | ||
| if abs(caxis) > data.ndim: |
There was a problem hiding this comment.
Wouldn't this fail, if data is an list but not an ndarray? I think this should be moved after data = _atleast_3d_first_dimension(data)
| sh_caxis = target.caxis_spherical_harmonics - 1 | ||
|
|
||
| # move SH axis to front | ||
| data = np.moveaxis(data, sh_caxis, 0) |
There was a problem hiding this comment.
The numpy docs say that source and destination axes must be unique, so I assume this must be
| data = np.moveaxis(data, sh_caxis, 0) | |
| data = np.moveaxis(data, sh_caxis, np.arange(len(sh_caxis))) |
for things to work if we a have a tuple of SH caxes
| rotated_data = np.tensordot(M, data, axes=(1, 0)) | ||
|
|
||
| # move SH axis back to original position | ||
| rotated_data = np.moveaxis(rotated_data, 0, sh_caxis) |
| if isinstance(axis, int): | ||
| axis = (axis,) |
There was a problem hiding this comment.
Would not be required anymore if axis was always a tuple.
There was a problem hiding this comment.
I changed it for all the private methods, but I think for convenience, it would be nice to allow both, int and tuple here for the public methods.
| shape = [1] * data.ndim | ||
| shape[axis] = data.shape[axis] | ||
|
|
||
| shape[axis[0]] = sh_channels |
There was a problem hiding this comment.
I think this does not work as intended yet. I get the same result for renormalizing an array with a single and two SH axes:
import spharpy
import numpy as np
data = np.ones((1, 4, 4, 8))
channel_convention = 'ACN'
current_norm = 'N3D'
target_norm = 'SN3D'
data1 = spharpy.spherical.renormalize(
data, channel_convention, current_norm, target_norm, (1, ))
data2 = spharpy.spherical.renormalize(
data, channel_convention, current_norm, target_norm, (1, 2))
np.all(data1 == data2) # is True but should not be true (?)| if isinstance(axis, int): | ||
| axis = (axis,) |
There was a problem hiding this comment.
Would not be required anymore of axis was always a tuple.
- bugfix normalization - edit caxis_spherical_harmonic getter - refactor _assert_valid_number_of_sh_channels and _convert_to_standard_definition - update tests
- override transpose
- implement override reshape - bugfix rotation
|
Thanks for the review. Good catches. I think everything is adapted and fixed now. Maybe we should discuss on the new behavior of reshaping SHsignals. Do we want to reshape caxis_spherical_harmonics as well? I think that wouldn't make sense. |
|
In addition to the above, I noticed that
All usages are fine since
|
f-brinkmann
left a comment
There was a problem hiding this comment.
Thanks for the quick implementation. Reshaping seems to get really annoying. Maybe something we can discuss in general.
|
|
||
| def _assert_valid_number_of_sh_channels(shape, sh_axis): | ||
| def _assert_valid_caxis_spherical_harmonics(data, caxis_spherical_harmonics): | ||
| """Check if the spherical harmonic channel axes are ``ìnt`` and do |
| ------ | ||
| ValueError | ||
| Raised if the number of spherical harmonic channels does not match | ||
| (n_max + 1)^2 for an integer n_max. |
There was a problem hiding this comment.
This is checked below. Can you update this please?
|
|
||
| Parameters | ||
| ---------- | ||
| data : numpy.ndarray |
There was a problem hiding this comment.
I would suggest to directly pass data.ndim
| Return reshaped copy of the SphericalHarmonicAudio object. Axes | ||
| containing the spherical harmonics coefficients can not be reshaped. |
There was a problem hiding this comment.
Did not think of this before to be honest. A different solution would be to allow freely reshaping and return an Audio either always or if SH channels are affected by reshaping.
| new_caxis_sh = [] | ||
| for caxis in self._caxis_spherical_harmonics: | ||
| n_sh_channels = old_cshape[caxis] | ||
| if n_sh_channels not in new_cshape: | ||
| raise ValueError( | ||
| "The requested new shape does not leave a " | ||
| "channel axis to hold the spherical " | ||
| "harmonic channels.") |
There was a problem hiding this comment.
Not sure if this always works. Could fail in case the size of a non-SH channel matches that of an SH channel.
| shape = [1] * data.ndim | ||
|
|
||
| shape[axis[0]] = sh_channels | ||
| # calculate one renormalization factor per sh channel |
There was a problem hiding this comment.
Is this required? All SH channels have the same order and hence the same normalization will be applied everywhere.
| shape = [1] * data.ndim | ||
| shape[a] = sh_channels | ||
|
|
||
| data_renorm *= factor.reshape(shape) |
There was a problem hiding this comment.
see comment above - the same factor is applied all the time, which is correct.
Which issue(s) are closed by this pull request?
Closes #326
Changes proposed in this pull request:
caxis_spherical_harmonicsin_SphericalHarmonicsAudio,SphericalHarmonicTimeData,SphericalHarmonicFrequencyData, andSphericalHarmonicSignalwhich defines the axis along which SHSignals store SH coefficientscaxis_spherical_harmonicsproperty for multichannel SH data