sofar 1.0.0__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sofar-1.0.0 → sofar-1.1.1}/CONTRIBUTING.rst +5 -2
- {sofar-1.0.0 → sofar-1.1.1}/HISTORY.rst +12 -0
- {sofar-1.0.0 → sofar-1.1.1}/PKG-INFO +4 -6
- {sofar-1.0.0 → sofar-1.1.1}/README.rst +1 -1
- {sofar-1.0.0 → sofar-1.1.1}/setup.cfg +1 -1
- {sofar-1.0.0 → sofar-1.1.1}/setup.py +3 -2
- {sofar-1.0.0 → sofar-1.1.1}/sofar/__init__.py +3 -2
- {sofar-1.0.0 → sofar-1.1.1}/sofar/io.py +92 -39
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa.py +93 -69
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/rules/upgrade.json +34 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/update_conventions.py +27 -14
- {sofar-1.0.0 → sofar-1.1.1}/sofar/utils.py +18 -38
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/PKG-INFO +4 -6
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/SOURCES.txt +3 -2
- sofar-1.1.1/tests/test_deprecations.py +19 -0
- {sofar-1.0.0 → sofar-1.1.1}/tests/test_io.py +34 -26
- {sofar-1.0.0 → sofar-1.1.1}/tests/test_sofa.py +4 -9
- {sofar-1.0.0 → sofar-1.1.1}/tests/test_sofa_upgrade_conventions.py +10 -1
- {sofar-1.0.0 → sofar-1.1.1}/tests/test_sofa_verify.py +10 -10
- {sofar-1.0.0 → sofar-1.1.1}/tests/test_utils.py +30 -16
- {sofar-1.0.0 → sofar-1.1.1}/AUTHORS.rst +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/LICENSE +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/MANIFEST.in +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/VERSION +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldDirectivityTF_1.1.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldDirectivityTF_1.1.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralFIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralFIR_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralSOS_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralSOS_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF-E_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF-E_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF_2.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/GeneralTF_2.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.json +0 -0
- {sofar-1.0.0/sofar/sofa_conventions/conventions → sofar-1.1.1/sofar/sofa_conventions/conventions/deprecated}/FreeFieldDirectivityTF_1.0.csv +0 -0
- {sofar-1.0.0/sofar/sofa_conventions/conventions → sofar-1.1.1/sofar/sofa_conventions/conventions/deprecated}/FreeFieldDirectivityTF_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.csv +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/rules/deprecations.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/rules/rules.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar/sofa_conventions/rules/unit_aliases.json +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/dependency_links.txt +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/not-zip-safe +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/requires.txt +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/sofar.egg-info/top_level.txt +0 -0
- {sofar-1.0.0 → sofar-1.1.1}/tests/__init__.py +0 -0
@@ -178,9 +178,12 @@ before building the documentation.
|
|
178
178
|
Submodules
|
179
179
|
~~~~~~~~~~
|
180
180
|
|
181
|
-
To update the
|
181
|
+
To update the submodule containing the conventions and verification rules run
|
182
182
|
|
183
|
-
$ git submodule update --
|
183
|
+
$ git submodule update --init --recursive
|
184
|
+
$ git submodule update --recursive --remote
|
185
|
+
|
186
|
+
and then commit the changes
|
184
187
|
|
185
188
|
|
186
189
|
Deploying
|
@@ -1,6 +1,18 @@
|
|
1
1
|
History
|
2
2
|
=======
|
3
3
|
|
4
|
+
1.1.1 (2023-7-7)
|
5
|
+
----------------
|
6
|
+
* Fix deploying to PyPi.org
|
7
|
+
|
8
|
+
1.1.0 (2023-7-7)
|
9
|
+
----------------
|
10
|
+
* Deprecate FreeFieldDirectivityTV 1.0 in favor of FreeFieldDirectivityTV 1.1 (according to sofaconventoins.org and AES69-2022)
|
11
|
+
* Add `sofar.read_sofa_as_netcdf` for reading SOFA files with erroneous data
|
12
|
+
* Document SOFA conventions on https://sofar.readthedocs.io/en/stable/resources/conventions.html. `Sofa.info()` will this be deprecated in sofar v1.3.0
|
13
|
+
* `sofar.read_sofa` and `sofar.write_sofa` now accept filenames and path objects
|
14
|
+
* Add testing for Python 3.11
|
15
|
+
|
4
16
|
1.0.0 (2022-12-16)
|
5
17
|
------------------
|
6
18
|
* Use SOFA conventions of version 2.1 from https://github.com/pyfar/sofa_conventions
|
@@ -1,17 +1,16 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sofar
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.1.1
|
4
4
|
Summary: Maybe the most complete python package for SOFA files so far
|
5
5
|
Home-page: https://pyfar.org/
|
6
|
+
Download-URL: https://pypi.org/project/sofar/
|
6
7
|
Author: The pyfar developers
|
7
8
|
Author-email: info@pyfar.org
|
8
9
|
License: MIT license
|
9
|
-
Download-URL: https://pypi.org/project/sofar/
|
10
10
|
Project-URL: Bug Tracker, https://github.com/pyfar/sofar/issues
|
11
11
|
Project-URL: Documentation, https://sofar.readthedocs.io/
|
12
12
|
Project-URL: Source Code, https://github.com/pyfar/sofar
|
13
13
|
Keywords: sofar
|
14
|
-
Platform: UNKNOWN
|
15
14
|
Classifier: Development Status :: 4 - Beta
|
16
15
|
Classifier: Intended Audience :: Science/Research
|
17
16
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -20,6 +19,7 @@ Classifier: Programming Language :: Python :: 3
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
23
23
|
Requires-Python: >=3.8
|
24
24
|
License-File: LICENSE
|
25
25
|
License-File: AUTHORS.rst
|
@@ -81,7 +81,5 @@ format*, Audio Engineering Society, Inc., New York, NY, USA.
|
|
81
81
|
|
82
82
|
P. Majdak, F. Zotter, F. Brinkmann, J. De Muynke, M. Mihocic, and M.
|
83
83
|
Noisternig, "Spatially Oriented Format for Acoustics 2.1: Introduction and
|
84
|
-
Recent Advances
|
84
|
+
Recent Advances", *J. Audio Eng. Soc.*, vol. 70, no. 7/8, pp. 565-584,
|
85
85
|
Jul. 2022. DOI: https://doi.org/10.17743/jaes.2022.0026
|
86
|
-
|
87
|
-
|
@@ -55,5 +55,5 @@ format*, Audio Engineering Society, Inc., New York, NY, USA.
|
|
55
55
|
|
56
56
|
P. Majdak, F. Zotter, F. Brinkmann, J. De Muynke, M. Mihocic, and M.
|
57
57
|
Noisternig, "Spatially Oriented Format for Acoustics 2.1: Introduction and
|
58
|
-
Recent Advances
|
58
|
+
Recent Advances", *J. Audio Eng. Soc.*, vol. 70, no. 7/8, pp. 565-584,
|
59
59
|
Jul. 2022. DOI: https://doi.org/10.17743/jaes.2022.0026
|
@@ -44,7 +44,8 @@ setup(
|
|
44
44
|
'Programming Language :: Python :: 3',
|
45
45
|
'Programming Language :: Python :: 3.8',
|
46
46
|
'Programming Language :: Python :: 3.9',
|
47
|
-
'Programming Language :: Python :: 3.10'
|
47
|
+
'Programming Language :: Python :: 3.10',
|
48
|
+
'Programming Language :: Python :: 3.11'
|
48
49
|
],
|
49
50
|
description="Maybe the most complete python package for SOFA files so far",
|
50
51
|
install_requires=requirements,
|
@@ -64,7 +65,7 @@ setup(
|
|
64
65
|
"Documentation": "https://sofar.readthedocs.io/",
|
65
66
|
"Source Code": "https://github.com/pyfar/sofar",
|
66
67
|
},
|
67
|
-
version='1.
|
68
|
+
version='1.1.1',
|
68
69
|
zip_safe=False,
|
69
70
|
python_requires='>=3.8'
|
70
71
|
)
|
@@ -4,11 +4,11 @@
|
|
4
4
|
|
5
5
|
__author__ = """The pyfar developers"""
|
6
6
|
__email__ = 'info@pyfar.org'
|
7
|
-
__version__ = '1.
|
7
|
+
__version__ = '1.1.1'
|
8
8
|
|
9
9
|
from .sofa import Sofa
|
10
10
|
|
11
|
-
from .io import read_sofa, write_sofa
|
11
|
+
from .io import read_sofa, read_sofa_as_netcdf, write_sofa
|
12
12
|
|
13
13
|
from .utils import (list_conventions,
|
14
14
|
equals,
|
@@ -21,6 +21,7 @@ __all__ = ['Sofa',
|
|
21
21
|
'update_conventions',
|
22
22
|
'list_conventions',
|
23
23
|
'read_sofa',
|
24
|
+
'read_sofa_as_netcdf',
|
24
25
|
'write_sofa',
|
25
26
|
'equals',
|
26
27
|
'version']
|
@@ -3,7 +3,8 @@ import os
|
|
3
3
|
import numpy as np
|
4
4
|
from netCDF4 import Dataset, chartostring, stringtochar
|
5
5
|
import warnings
|
6
|
-
import
|
6
|
+
import pathlib
|
7
|
+
from packaging.version import parse
|
7
8
|
import sofar as sf
|
8
9
|
from .utils import _verify_convention_and_version, _atleast_nd
|
9
10
|
|
@@ -18,8 +19,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
18
19
|
Parameters
|
19
20
|
----------
|
20
21
|
filename : str
|
21
|
-
The
|
22
|
-
explicitly given.
|
22
|
+
The full path to the sofa data.
|
23
23
|
verify : bool, optional
|
24
24
|
Verify and update the SOFA object by calling :py:func:`~Sofa.verify`.
|
25
25
|
This helps to find potential errors in the default values and is thus
|
@@ -32,7 +32,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
32
32
|
Returns
|
33
33
|
-------
|
34
34
|
sofa : Sofa
|
35
|
-
|
35
|
+
Object containing the data from `filename`.
|
36
36
|
|
37
37
|
Notes
|
38
38
|
-----
|
@@ -52,9 +52,63 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
52
52
|
will be a scalar inside SOFA objects after reading from disk.
|
53
53
|
"""
|
54
54
|
|
55
|
+
return _read_netcdf(filename, verify, verbose, mode="sofa")
|
56
|
+
|
57
|
+
|
58
|
+
def read_sofa_as_netcdf(filename):
|
59
|
+
"""
|
60
|
+
Read corrupted SOFA data from disk.
|
61
|
+
|
62
|
+
.. note::
|
63
|
+
`read_sofa_as_netcdf` is intended to read and fix corrupted SOFA data
|
64
|
+
that could not be read by :py:func:`~read_sofa`. The recommend workflow
|
65
|
+
is
|
66
|
+
|
67
|
+
- Try to read the data with `read_sofa` and ``verify=True``
|
68
|
+
- If this fails, try the above with ``verify=False``
|
69
|
+
- If this fails, use `read_sofa_as_netcdf`
|
70
|
+
|
71
|
+
The SOFA object returned by `read_sofa_as_netcdf` may not work
|
72
|
+
correctly before the issues with the data were fixed, i.e., before the
|
73
|
+
data are in agreement with the SOFA standard AES-69.
|
74
|
+
|
75
|
+
Numeric data is returned as floats or numpy float arrays unless they have
|
76
|
+
missing data, in which case they are returned as numpy masked arrays.
|
77
|
+
|
78
|
+
Parameters
|
79
|
+
----------
|
80
|
+
filename : str
|
81
|
+
The full path to the NetCDF data.
|
82
|
+
|
83
|
+
Returns
|
84
|
+
-------
|
85
|
+
sofa : Sofa
|
86
|
+
Object containing the data from `filename`.
|
87
|
+
|
88
|
+
Notes
|
89
|
+
-----
|
90
|
+
|
91
|
+
1. Missing dimensions are appended when writing the SOFA object to disk.
|
92
|
+
E.g., if ``sofa.Data_IR`` is of shape (1, 2) it is written as an array
|
93
|
+
of shape (1, 2, 1) because the SOFA standard AES69-2020 defines it as a
|
94
|
+
three dimensional array with the dimensions (`M: measurements`,
|
95
|
+
`R: receivers`, `N: samples`)
|
96
|
+
2. When reading data from a SOFA file, array data is always returned as
|
97
|
+
numpy arrays and singleton trailing dimensions are discarded (numpy
|
98
|
+
default). I.e., ``sofa.Data_IR`` will again be an array of shape (1, 2)
|
99
|
+
after writing and reading to and from disk.
|
100
|
+
3. One dimensional arrays with only one element will be converted to scalar
|
101
|
+
values. E.g. ``sofa.Data_SamplingRate`` is stored as an array of shape
|
102
|
+
(1, ) inside SOFA files (according to the SOFA standard AES69-2020) but
|
103
|
+
will be a scalar inside SOFA objects after reading from disk.
|
104
|
+
"""
|
105
|
+
return _read_netcdf(filename, False, False, mode="netcdf")
|
106
|
+
|
107
|
+
|
108
|
+
def _read_netcdf(filename, verify, verbose, mode):
|
109
|
+
|
55
110
|
# check the filename
|
56
|
-
|
57
|
-
raise ValueError("Filename must end with .sofa")
|
111
|
+
filename = pathlib.Path(filename).with_suffix('.sofa')
|
58
112
|
if not os.path.isfile(filename):
|
59
113
|
raise ValueError(f"{filename} does not exist")
|
60
114
|
|
@@ -68,29 +122,25 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
68
122
|
# open new NETCDF4 file for reading
|
69
123
|
with Dataset(filename, "r", format="NETCDF4") as file:
|
70
124
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
all_attr.append("GLOBAL_SOFAConventionsVersion")
|
125
|
+
if mode == "sofa":
|
126
|
+
# get convention name and version
|
127
|
+
convention = getattr(file, "SOFAConventions")
|
128
|
+
version = getattr(file, "SOFAConventionsVersion")
|
76
129
|
|
77
|
-
|
78
|
-
|
79
|
-
version_in, version_in, convention)
|
130
|
+
# check if convention and version exist
|
131
|
+
_verify_convention_and_version(version, convention)
|
80
132
|
|
81
|
-
|
82
|
-
|
133
|
+
# get SOFA object with default values
|
134
|
+
sofa = sf.Sofa(convention, version=version, verify=verify)
|
135
|
+
else:
|
136
|
+
sofa = sf.Sofa(None)
|
83
137
|
|
84
138
|
# allow writing read only attributes
|
85
|
-
sofa.
|
139
|
+
sofa.protected = False
|
86
140
|
|
87
141
|
# load global attributes
|
88
142
|
for attr in file.ncattrs():
|
89
143
|
|
90
|
-
if attr in ["SOFAConventionsVersion", "SOFAConventions"]:
|
91
|
-
# convention and version were already set above
|
92
|
-
continue
|
93
|
-
|
94
144
|
value = getattr(file, attr)
|
95
145
|
all_attr.append(f"GLOBAL_{attr}")
|
96
146
|
|
@@ -98,7 +148,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
98
148
|
sofa._add_custom_api_entry(
|
99
149
|
f"GLOBAL_{attr}", value, None, None, "attribute")
|
100
150
|
custom.append(f"GLOBAL_{attr}")
|
101
|
-
sofa.
|
151
|
+
sofa.protected = False
|
102
152
|
else:
|
103
153
|
setattr(sofa, f"GLOBAL_{attr}", value)
|
104
154
|
|
@@ -117,7 +167,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
117
167
|
sofa._add_custom_api_entry(var.replace(".", "_"), value, None,
|
118
168
|
dimensions, dtype)
|
119
169
|
custom.append(var.replace(".", "_"))
|
120
|
-
sofa.
|
170
|
+
sofa.protected = False
|
121
171
|
|
122
172
|
# load variable attributes
|
123
173
|
for attr in [a for a in file[var].ncattrs() if a not in skip]:
|
@@ -130,7 +180,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
130
180
|
var.replace(".", "_") + "_" + attr, value, None,
|
131
181
|
None, "attribute")
|
132
182
|
custom.append(var.replace(".", "_") + "_" + attr)
|
133
|
-
sofa.
|
183
|
+
sofa.protected = False
|
134
184
|
else:
|
135
185
|
setattr(sofa, var.replace(".", "_") + "_" + attr, value)
|
136
186
|
|
@@ -142,7 +192,7 @@ def read_sofa(filename, verify=True, verbose=True):
|
|
142
192
|
delattr(sofa, attr)
|
143
193
|
|
144
194
|
# do not allow writing read only attributes any more
|
145
|
-
sofa.
|
195
|
+
sofa.protected = True
|
146
196
|
|
147
197
|
# notice about custom entries
|
148
198
|
if custom and verbose:
|
@@ -206,20 +256,23 @@ def _write_sofa(filename: str, sofa: sf.Sofa, compression=4, verify=True):
|
|
206
256
|
"""
|
207
257
|
|
208
258
|
# check the filename
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
259
|
+
filename = pathlib.Path(filename).with_suffix('.sofa')
|
260
|
+
|
261
|
+
if verify:
|
262
|
+
# check if the latest version is used for writing and warn otherwise
|
263
|
+
# if case required for writing SOFA test data that violates the
|
264
|
+
# conventions
|
265
|
+
if sofa.GLOBAL_SOFAConventions != "invalid-value":
|
266
|
+
latest = sf.Sofa(sofa.GLOBAL_SOFAConventions)
|
267
|
+
latest = latest.GLOBAL_SOFAConventionsVersion
|
268
|
+
current = sofa.GLOBAL_SOFAConventionsVersion
|
269
|
+
|
270
|
+
if parse(current) < parse(latest):
|
271
|
+
warnings.warn((
|
272
|
+
"Writing SOFA object with outdated Convention "
|
273
|
+
f"version {current}. It is recommend to upgrade "
|
274
|
+
" data with Sofa.upgrade_convention() before "
|
275
|
+
"writing to disk if possible."))
|
223
276
|
|
224
277
|
# setting the netCDF compression parameter
|
225
278
|
use_zlib = compression != 0
|
@@ -89,35 +89,43 @@ class Sofa():
|
|
89
89
|
"""See class docstring"""
|
90
90
|
|
91
91
|
# get convention
|
92
|
-
|
92
|
+
if convention is not None:
|
93
|
+
self._convention = self._load_convention(convention, version)
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
# update read only attributes
|
96
|
+
self._read_only_attr = [
|
97
|
+
key for key in self._convention.keys()
|
98
|
+
if self._read_only(self._convention[key]["flags"])]
|
98
99
|
|
99
|
-
|
100
|
-
|
100
|
+
# add attributes with default values
|
101
|
+
self._convention_to_sofa(mandatory)
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
# add and update the API
|
104
|
+
# (mandatory=False can not be verified because some conventions
|
105
|
+
# have default values that have optional variables as dependencies)
|
106
|
+
if verify and not mandatory:
|
107
|
+
self.verify(mode="read")
|
107
108
|
|
108
|
-
|
109
|
+
self.protected = True
|
110
|
+
else:
|
111
|
+
verify = False
|
112
|
+
self._convention = {}
|
109
113
|
|
110
114
|
def __setattr__(self, name: str, value):
|
111
115
|
# don't allow new attributes to be added outside the class
|
112
|
-
if self.
|
116
|
+
if self.protected and not hasattr(self, name):
|
113
117
|
raise TypeError(f"{name} is an invalid attribute")
|
114
118
|
|
115
119
|
# don't allow setting read only attributes
|
116
|
-
if name in self._read_only_attr and self.
|
117
|
-
raise TypeError(
|
120
|
+
if name in self._read_only_attr and self.protected:
|
121
|
+
raise TypeError((
|
122
|
+
f"{name} is a read only attribute. Iy you know what you are "
|
123
|
+
"doing, you can set Sofa.protected = False to write read "
|
124
|
+
"only data (e.g., to repair corrupted SOFA data)."))
|
118
125
|
|
119
126
|
# convert to numpy array or scalar
|
120
|
-
if not isinstance(value, (str, dict, np.ndarray))
|
127
|
+
if not isinstance(value, (str, dict, np.ndarray)) \
|
128
|
+
and name != "protected":
|
121
129
|
value = np.atleast_2d(value)
|
122
130
|
if value.size == 1:
|
123
131
|
value = value.flatten()[0]
|
@@ -129,7 +137,7 @@ class Sofa():
|
|
129
137
|
if not hasattr(self, name):
|
130
138
|
raise TypeError(f"{name} is not an attribute")
|
131
139
|
# delete anything if not frozen, delete non mandatory
|
132
|
-
if not self.
|
140
|
+
if not self.protected or \
|
133
141
|
not self._mandatory(self._convention[name]["flags"]):
|
134
142
|
super().__delattr__(name)
|
135
143
|
|
@@ -287,9 +295,17 @@ class Sofa():
|
|
287
295
|
attribute will be printed.
|
288
296
|
"""
|
289
297
|
|
298
|
+
# warn for upcoming deprecation
|
299
|
+
warnings.warn((
|
300
|
+
'Sofa.info() will be deprecated in sofar 1.3.0 The conventions are'
|
301
|
+
' now documented at '
|
302
|
+
'https://sofar.readthedocs.io/en/stable/resources/conventions.html'), # noqa
|
303
|
+
UserWarning)
|
304
|
+
|
290
305
|
# update the private attribute `_convention` to make sure the required
|
291
306
|
# meta data is in place
|
292
|
-
self
|
307
|
+
if not hasattr(self, "_convention"):
|
308
|
+
self._reset_convention()
|
293
309
|
|
294
310
|
# list of all attributes
|
295
311
|
keys = [k for k in self.__dict__.keys() if not k.startswith("_")]
|
@@ -461,13 +477,13 @@ class Sofa():
|
|
461
477
|
"""
|
462
478
|
|
463
479
|
# initialize
|
464
|
-
self.
|
480
|
+
self._reset_convention()
|
465
481
|
added = "Added the following missing data with their default values:\n"
|
466
482
|
|
467
483
|
# current data
|
468
484
|
keys = [key for key in self.__dict__.keys() if not key.startswith("_")]
|
469
485
|
|
470
|
-
self.
|
486
|
+
self.protected = False
|
471
487
|
|
472
488
|
# loop data in convention
|
473
489
|
for key in self._convention.keys():
|
@@ -480,7 +496,7 @@ class Sofa():
|
|
480
496
|
added += f"- {key} "
|
481
497
|
added += f"({'mandatory' if is_mandatory else 'optional'})\n"
|
482
498
|
|
483
|
-
self.
|
499
|
+
self.protected = True
|
484
500
|
|
485
501
|
if verbose:
|
486
502
|
if "-" in added:
|
@@ -510,7 +526,7 @@ class Sofa():
|
|
510
526
|
|
511
527
|
dimensions : str
|
512
528
|
The shape of the new entry as a string. See
|
513
|
-
|
529
|
+
:py:func:`~Sofa.list_dimensions`.
|
514
530
|
|
515
531
|
Examples
|
516
532
|
--------
|
@@ -565,9 +581,10 @@ class Sofa():
|
|
565
581
|
"""
|
566
582
|
Delete variable or attribute from SOFA object.
|
567
583
|
|
568
|
-
Note that mandatory data can not be deleted.
|
569
|
-
|
570
|
-
|
584
|
+
Note that mandatory data can not be deleted. Check the
|
585
|
+
`sofar documentation
|
586
|
+
<https://sofar.readthedocs.io/en/stable/resources/conventions.html>`_
|
587
|
+
for a complete list of optional variables and attributes.
|
571
588
|
|
572
589
|
Parameters
|
573
590
|
----------
|
@@ -576,6 +593,26 @@ class Sofa():
|
|
576
593
|
"""
|
577
594
|
delattr(self, name)
|
578
595
|
|
596
|
+
@property
|
597
|
+
def protected(self):
|
598
|
+
"""
|
599
|
+
If Sofa.protected is ``True``, read only data can not be changed. Only
|
600
|
+
change this to ``False`` if you know what you are doing, e.g., if you
|
601
|
+
need to repair corrupted SOFA data.
|
602
|
+
"""
|
603
|
+
return self._protected
|
604
|
+
|
605
|
+
@protected.setter
|
606
|
+
def protected(self, value: bool):
|
607
|
+
"""
|
608
|
+
If Sofa.protected is ``True``, read only data can not be changed. Only
|
609
|
+
change this to ``False`` if you know what you are doing, e.g., if you
|
610
|
+
need to repair corrupted SOFA data.
|
611
|
+
"""
|
612
|
+
if not isinstance(value, bool):
|
613
|
+
raise ValueError("Sofa.protected can only be True or False")
|
614
|
+
self._protected = value
|
615
|
+
|
579
616
|
def _add_entry(self, name, value, dtype, dimensions):
|
580
617
|
"""
|
581
618
|
Add custom data to a SOFA object. See add_variable and add_attribute
|
@@ -633,7 +670,7 @@ class Sofa():
|
|
633
670
|
double, string, or attribute
|
634
671
|
"""
|
635
672
|
# create custom API if it not exists
|
636
|
-
self.
|
673
|
+
self.protected = False
|
637
674
|
|
638
675
|
# lower case letters to indicate custom dimensions
|
639
676
|
if dimensions is not None:
|
@@ -652,11 +689,11 @@ class Sofa():
|
|
652
689
|
"type": dtype,
|
653
690
|
"default": None,
|
654
691
|
"comment": ""}
|
655
|
-
|
692
|
+
self._convention[key] = self._custom[key]
|
656
693
|
|
657
694
|
# add attribute to object
|
658
695
|
setattr(self, key, value)
|
659
|
-
self.
|
696
|
+
self.protected = True
|
660
697
|
|
661
698
|
def upgrade_convention(self, target=None, verify=True):
|
662
699
|
"""
|
@@ -687,7 +724,7 @@ class Sofa():
|
|
687
724
|
"""
|
688
725
|
|
689
726
|
# check input ---------------------------------------------------------
|
690
|
-
self.
|
727
|
+
self._reset_convention()
|
691
728
|
|
692
729
|
# get deprecations and information about Sofa object
|
693
730
|
_, _, deprecations, upgrade = self._verification_rules()
|
@@ -763,7 +800,7 @@ class Sofa():
|
|
763
800
|
"GLOBAL_Version",
|
764
801
|
"GLOBAL_DataType"]
|
765
802
|
|
766
|
-
self.
|
803
|
+
self.protected = False
|
767
804
|
for key in keys:
|
768
805
|
setattr(self, key, self._convention[key]["default"])
|
769
806
|
|
@@ -813,7 +850,7 @@ class Sofa():
|
|
813
850
|
|
814
851
|
# check for missing mandatory data
|
815
852
|
self.add_missing(True, False)
|
816
|
-
self.
|
853
|
+
self.protected = True
|
817
854
|
|
818
855
|
# display general message
|
819
856
|
if upgrade["message"] is not None:
|
@@ -908,7 +945,7 @@ class Sofa():
|
|
908
945
|
|
909
946
|
# ---------------------------------------------------------------------
|
910
947
|
# 0. update the convention
|
911
|
-
self.
|
948
|
+
self._reset_convention()
|
912
949
|
|
913
950
|
# ---------------------------------------------------------------------
|
914
951
|
# 1. check if the mandatory attributes are contained
|
@@ -921,9 +958,9 @@ class Sofa():
|
|
921
958
|
|
922
959
|
if issue_handling != "raise":
|
923
960
|
# add missing data with default value
|
924
|
-
self.
|
961
|
+
self.protected = False
|
925
962
|
setattr(self, key, self._convention[key]["default"])
|
926
|
-
self.
|
963
|
+
self.protected = True
|
927
964
|
|
928
965
|
# prepare to raise warning
|
929
966
|
missing += "- " + key + "\n"
|
@@ -1104,10 +1141,10 @@ class Sofa():
|
|
1104
1141
|
# 4. Get dimensions (E, R, M, N, S, c, I, and custom)
|
1105
1142
|
|
1106
1143
|
# initialize required API fields
|
1107
|
-
self.
|
1144
|
+
self.protected = False
|
1108
1145
|
self._dimensions = {}
|
1109
1146
|
self._api = {}
|
1110
|
-
self.
|
1147
|
+
self.protected = True
|
1111
1148
|
|
1112
1149
|
# get keys for checking the dimensions (all SOFA variables)
|
1113
1150
|
keys = [key for key in self.__dict__.keys()
|
@@ -1548,49 +1585,36 @@ class Sofa():
|
|
1548
1585
|
"""Return a copy of the SOFA object."""
|
1549
1586
|
return deepcopy(self)
|
1550
1587
|
|
1551
|
-
def
|
1588
|
+
def _reset_convention(self):
|
1552
1589
|
"""
|
1553
|
-
Add SOFA convention to SOFA object in private attribute
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
``'match'``
|
1562
|
-
Match the version of the sofa file.
|
1563
|
-
str
|
1564
|
-
Version string, e.g., ``'1.0'``.
|
1590
|
+
- Add SOFA convention to SOFA object in private attribute
|
1591
|
+
`_convention`. If The object already contains a convention, it will
|
1592
|
+
be overwritten.
|
1593
|
+
- If the SOFA object contains custom entries, check if any of the
|
1594
|
+
custom entries part of the convention. If yes, delete the entry from
|
1595
|
+
self._custom
|
1596
|
+
- If the SOFA objects contains custom entries, add entries from
|
1597
|
+
self._custom to self._convention
|
1565
1598
|
"""
|
1566
1599
|
|
1567
1600
|
# verify convention and version
|
1568
1601
|
c_current = self.GLOBAL_SOFAConventions
|
1569
1602
|
v_current = str(self.GLOBAL_SOFAConventionsVersion)
|
1570
1603
|
|
1571
|
-
|
1572
|
-
version, v_current, c_current)
|
1604
|
+
_verify_convention_and_version(v_current, c_current)
|
1573
1605
|
|
1574
1606
|
# load and add convention and version
|
1575
1607
|
convention = self._load_convention(
|
1576
|
-
c_current,
|
1608
|
+
c_current, v_current)
|
1577
1609
|
self._convention = convention
|
1578
1610
|
|
1579
|
-
if v_current != v_new:
|
1580
|
-
self._protected = False
|
1581
|
-
self.GLOBAL_SOFAConventionsVersion = v_new
|
1582
|
-
self._protected = True
|
1583
|
-
|
1584
|
-
# feedback in case of up/downgrade
|
1585
|
-
if float(v_current) < float(v_new):
|
1586
|
-
warnings.warn(("Upgraded SOFA object from "
|
1587
|
-
f"version {v_current} to {v_new}"))
|
1588
|
-
elif float(v_current) > float(v_new):
|
1589
|
-
warnings.warn(("Downgraded SOFA object from "
|
1590
|
-
f"version {v_current} to {v_new}"))
|
1591
|
-
|
1592
|
-
# check if custom fields can be added
|
1593
1611
|
if hasattr(self, "_custom"):
|
1612
|
+
# check of custom fields can be removed
|
1613
|
+
for key in self._convention:
|
1614
|
+
if key in self._custom:
|
1615
|
+
del self._custom[key]
|
1616
|
+
|
1617
|
+
# check if custom fields can be added
|
1594
1618
|
for key in self._custom:
|
1595
1619
|
self._convention[key] = self._custom[key]
|
1596
1620
|
|
@@ -1684,7 +1708,7 @@ class Sofa():
|
|
1684
1708
|
|
1685
1709
|
# write API and date specific fields (some read only)
|
1686
1710
|
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
1687
|
-
self.
|
1711
|
+
self.protected = False
|
1688
1712
|
self.GLOBAL_DateCreated = now
|
1689
1713
|
self.GLOBAL_DateModified = now
|
1690
1714
|
self.GLOBAL_APIName = "sofar SOFA API for Python (pyfar.org)"
|
@@ -1694,7 +1718,7 @@ class Sofa():
|
|
1694
1718
|
f"{platform.python_version()} "
|
1695
1719
|
f"[{platform.python_implementation()} - "
|
1696
1720
|
f"{platform.python_compiler()}]")
|
1697
|
-
self.
|
1721
|
+
self.protected = True
|
1698
1722
|
|
1699
1723
|
@staticmethod
|
1700
1724
|
def _get_size_and_shape_of_string_var(value, key):
|