sofar 1.1.4__py2.py3-none-any.whl → 1.2.0__py2.py3-none-any.whl
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/__init__.py +4 -4
- sofar/io.py +18 -10
- sofar/sofa.py +42 -25
- sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.csv +46 -0
- sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.json +353 -0
- sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.csv +46 -0
- sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.json +353 -0
- sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.csv +46 -0
- sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.json +351 -0
- sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.csv +46 -0
- sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.json +351 -0
- sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.csv +47 -0
- sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.json +366 -0
- sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.csv +51 -0
- sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.json +397 -0
- sofar/sofa_conventions/rules/deprecations.json +2 -1
- sofar/sofa_conventions/rules/rules.json +21 -2
- sofar/sofa_conventions/rules/upgrade.json +36 -0
- sofar/sofastream.py +296 -0
- sofar/update_conventions.py +108 -85
- sofar/utils.py +1 -1
- {sofar-1.1.4.dist-info → sofar-1.2.0.dist-info}/LICENSE +4 -1
- sofar-1.2.0.dist-info/METADATA +93 -0
- {sofar-1.1.4.dist-info → sofar-1.2.0.dist-info}/RECORD +34 -19
- {sofar-1.1.4.dist-info → sofar-1.2.0.dist-info}/WHEEL +1 -1
- tests/conftest.py +27 -0
- tests/test_io.py +9 -5
- tests/test_sofa.py +1 -1
- tests/test_sofa_upgrade_conventions.py +10 -1
- tests/test_sofa_verify.py +1 -1
- tests/test_sofastream.py +126 -0
- tests/test_utils.py +18 -8
- sofar-1.1.4.dist-info/METADATA +0 -91
- {sofar-1.1.4.dist-info → sofar-1.2.0.dist-info}/AUTHORS.rst +0 -0
- {sofar-1.1.4.dist-info → sofar-1.2.0.dist-info}/top_level.txt +0 -0
sofar/sofastream.py
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
from netCDF4 import Dataset
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
|
5
|
+
class SofaStream():
|
6
|
+
"""
|
7
|
+
Read desired data from SOFA-file directly from disk without loading entire
|
8
|
+
file into memory.
|
9
|
+
|
10
|
+
:class:`SofaStream` opens a SOFA-file and retrieves only the requested
|
11
|
+
data.
|
12
|
+
|
13
|
+
If you want to use all the data from a SOFA-file use :class:`Sofa`
|
14
|
+
class and :func:`read_sofa` function instead.
|
15
|
+
|
16
|
+
Parameters
|
17
|
+
----------
|
18
|
+
filename : str
|
19
|
+
Full path to a SOFA-file
|
20
|
+
|
21
|
+
Returns
|
22
|
+
--------
|
23
|
+
sofa_stream : SofaStream
|
24
|
+
A SofaStream object which reads directly from the file.
|
25
|
+
|
26
|
+
Examples
|
27
|
+
--------
|
28
|
+
Get an attribute from a SOFA-file:
|
29
|
+
|
30
|
+
>>> import sofar as sf
|
31
|
+
>>> filename = "path/to/file.sofa"
|
32
|
+
>>> with sf.SofaStream(filename) as file:
|
33
|
+
>>> data = file.GLOBAL_RoomType
|
34
|
+
>>> print(data)
|
35
|
+
free field
|
36
|
+
|
37
|
+
Get a variable from a SOFA-file:
|
38
|
+
|
39
|
+
>>> with SofaStream(filename) as file:
|
40
|
+
>>> data = file.Data_IR
|
41
|
+
>>> print(data)
|
42
|
+
<class 'netCDF4._netCDF4.Variable'>
|
43
|
+
float64 Data.IR(M, R, N)
|
44
|
+
unlimited dimensions:
|
45
|
+
current shape = (11950, 2, 256)
|
46
|
+
filling on, default _FillValue of 9.969209968386869e+36 used
|
47
|
+
|
48
|
+
What is returned is a `netCDF-variable`. To access the values (in this
|
49
|
+
example the IRs) the variable needs to be sliced:
|
50
|
+
|
51
|
+
>>> with SofaStream(filename) as file:
|
52
|
+
>>> data = file.Data_IR
|
53
|
+
>>> # get all values
|
54
|
+
>>> all_irs = data[:]
|
55
|
+
>>> print(all_irs.shape)
|
56
|
+
(11950, 2, 256)
|
57
|
+
>>> # get data from first channel
|
58
|
+
>>> specific_irs = data[:,0,:]
|
59
|
+
>>> print(specific_irs.shape)
|
60
|
+
(11950, 256)
|
61
|
+
"""
|
62
|
+
|
63
|
+
def __init__(self, filename):
|
64
|
+
"""Initialize a new SofaStream object (see documentation above)"""
|
65
|
+
self._filename = filename
|
66
|
+
|
67
|
+
def __enter__(self):
|
68
|
+
"""
|
69
|
+
Executed when entering a ``with`` statement
|
70
|
+
(see documentation above).
|
71
|
+
"""
|
72
|
+
self._file = Dataset(self._filename, mode="r")
|
73
|
+
return self
|
74
|
+
|
75
|
+
def __exit__(self, *args):
|
76
|
+
"""
|
77
|
+
Executed when exiting a ``with`` statement
|
78
|
+
(see documentation above).
|
79
|
+
"""
|
80
|
+
self._file.close()
|
81
|
+
|
82
|
+
def __getattr__(self, name):
|
83
|
+
"""
|
84
|
+
Executed when accessing data within a with statement
|
85
|
+
(see documentation above)."""
|
86
|
+
# get netCDF4-attributes and -variable-keys from SOFA-file
|
87
|
+
dset_variables = np.array([key for key in self._file.variables.keys()])
|
88
|
+
dset_attributes = np.asarray(self._file.ncattrs())
|
89
|
+
|
90
|
+
# remove delimiter from passed sofar-attribute
|
91
|
+
name_netcdf = name.replace(
|
92
|
+
'GLOBAL_', '').replace('Data_', 'Data.')
|
93
|
+
|
94
|
+
# Handle variable-attributes (e.g. '_Units' and '_Type')
|
95
|
+
var_attr = None
|
96
|
+
if "_" in name_netcdf:
|
97
|
+
name_netcdf, var_attr = name_netcdf.split('_')
|
98
|
+
|
99
|
+
# get value if passed attribute points to a netCDF4-variable
|
100
|
+
if name_netcdf in dset_variables:
|
101
|
+
# get variable from SOFA-file
|
102
|
+
self._data = self._file.variables[name_netcdf]
|
103
|
+
if var_attr is not None:
|
104
|
+
self._data = getattr(self._data, var_attr)
|
105
|
+
|
106
|
+
# get value if passed attribute points to a netCDF4-attribute
|
107
|
+
elif name_netcdf in dset_attributes:
|
108
|
+
# get attribute value from SOFA-file
|
109
|
+
self._data = self._file.getncattr(name_netcdf)
|
110
|
+
|
111
|
+
else:
|
112
|
+
raise AttributeError(f"{name} is not contained in SOFA-file")
|
113
|
+
|
114
|
+
return self._data
|
115
|
+
|
116
|
+
@property
|
117
|
+
def list_dimensions(self):
|
118
|
+
"""
|
119
|
+
Print the dimensions of the SOFA-file
|
120
|
+
|
121
|
+
See :py:func:`~SofaStream.inspect` to see the shapes of the data inside
|
122
|
+
the SOFA-file and :py:func:`~SofaStream.get_dimension` to get the
|
123
|
+
size/value of a specific dimensions as integer number.
|
124
|
+
|
125
|
+
The SOFA standard defines the following dimensions that are used
|
126
|
+
to define the shape of the data entries:
|
127
|
+
|
128
|
+
M
|
129
|
+
number of measurements
|
130
|
+
N
|
131
|
+
number of samples, frequencies, SOS coefficients
|
132
|
+
(depending on GLOBAL_DataType)
|
133
|
+
R
|
134
|
+
Number of receivers or SH coefficients
|
135
|
+
(depending on ReceiverPosition_Type)
|
136
|
+
E
|
137
|
+
Number of emitters or SH coefficients
|
138
|
+
(depending on EmitterPosition_Type)
|
139
|
+
S
|
140
|
+
Maximum length of a string in a string array
|
141
|
+
C
|
142
|
+
Size of the coordinate dimension. This is always three.
|
143
|
+
I
|
144
|
+
Single dimension. This is always one.
|
145
|
+
|
146
|
+
"""
|
147
|
+
dim = self._file.dimensions
|
148
|
+
|
149
|
+
# get verbose description for dimesion N
|
150
|
+
if self._file.getncattr('DataType').startswith("FIR"):
|
151
|
+
N_verbose = "samples"
|
152
|
+
elif self._file.getncattr('DataType').startswith("TF"):
|
153
|
+
N_verbose = "frequencies"
|
154
|
+
elif self._file.getncattr('DataType').startswith("SOS"):
|
155
|
+
N_verbose = "SOS coefficients"
|
156
|
+
|
157
|
+
# get verbose description for dimensions R and E
|
158
|
+
R_verbose = (
|
159
|
+
"receiver spherical harmonics coefficients"
|
160
|
+
if 'harmonic'
|
161
|
+
in self._file.variables['ReceiverPosition'].getncattr('Type')
|
162
|
+
else "receiver"
|
163
|
+
)
|
164
|
+
E_verbose = (
|
165
|
+
"emitter spherical harmonics coefficients"
|
166
|
+
if 'harmonic'
|
167
|
+
in self._file.variables['EmitterPosition'].getncattr('Type')
|
168
|
+
else "emitter"
|
169
|
+
)
|
170
|
+
|
171
|
+
dimensions = {
|
172
|
+
"M": "measurements",
|
173
|
+
"N": N_verbose,
|
174
|
+
"R": R_verbose,
|
175
|
+
"E": E_verbose,
|
176
|
+
"S": "maximum string length",
|
177
|
+
"C": "coordinate dimensions, fixed",
|
178
|
+
"I": "single dimension, fixed"}
|
179
|
+
|
180
|
+
info_str = ""
|
181
|
+
for key, value in dim.items():
|
182
|
+
value = value.size
|
183
|
+
dim_info = dimensions[key] if key in dimensions \
|
184
|
+
else "custom dimension"
|
185
|
+
|
186
|
+
info_str += f"{key} = {value} {dim_info}" + '\n'
|
187
|
+
|
188
|
+
print(info_str)
|
189
|
+
|
190
|
+
def get_dimension(self, dimension):
|
191
|
+
"""
|
192
|
+
Get size of a SOFA dimension
|
193
|
+
|
194
|
+
SOFA dimensions specify the shape of the data contained in a SOFA-file.
|
195
|
+
For a list of all dimensions see :py:func:`~list_dimensions`, for more
|
196
|
+
information about the data contained in a SOFA-file use
|
197
|
+
:py:func:`~inspect`.
|
198
|
+
|
199
|
+
Parameters
|
200
|
+
----------
|
201
|
+
dimension : str
|
202
|
+
The dimension as a string, e.g., ``'N'``.
|
203
|
+
|
204
|
+
Returns
|
205
|
+
-------
|
206
|
+
size : int
|
207
|
+
the size of the queried dimension.
|
208
|
+
"""
|
209
|
+
|
210
|
+
# get dimensons from SOFA-file
|
211
|
+
dims = self._file.dimensions
|
212
|
+
|
213
|
+
if dimension not in dims.keys():
|
214
|
+
raise ValueError((
|
215
|
+
f"{dimension} is not a valid dimension. "
|
216
|
+
"See Sofa.list_dimensions for a list of valid dimensions."))
|
217
|
+
|
218
|
+
return dims[dimension].size
|
219
|
+
|
220
|
+
def inspect(self, file=None):
|
221
|
+
"""
|
222
|
+
Get information about the data inside a SOFA-file
|
223
|
+
|
224
|
+
Prints the values of all attributes and variables with six or less
|
225
|
+
entries and the shapes and type of all numeric and string variables.
|
226
|
+
When printing the values of arrays, single dimensions are discarded for
|
227
|
+
easy of display, i.e., an array of shape (1, 3, 2) will be displayed as
|
228
|
+
an array of shape (3, 2).
|
229
|
+
|
230
|
+
Parameters
|
231
|
+
----------
|
232
|
+
file : str
|
233
|
+
Full path of a file under which the information is to be stored in
|
234
|
+
plain text. The default ``None`` only print the information to the
|
235
|
+
console.
|
236
|
+
"""
|
237
|
+
|
238
|
+
# Header of inspect-print
|
239
|
+
info_str = (
|
240
|
+
f"{self._file.getncattr('SOFAConventions')} "
|
241
|
+
f"{self._file.getncattr('SOFAConventionsVersion')} "
|
242
|
+
f"(SOFA version {self._file.getncattr('Version')})\n")
|
243
|
+
info_str += "-" * len(info_str) + "\n"
|
244
|
+
|
245
|
+
# information for attributes
|
246
|
+
for attr in self._file.ncattrs():
|
247
|
+
|
248
|
+
value = self._file.getncattr(attr)
|
249
|
+
sofar_attr = f"GLOBAL_{attr}"
|
250
|
+
info_str += sofar_attr + ' : ' + str(value) + '\n'
|
251
|
+
|
252
|
+
# information for variables
|
253
|
+
for key in self._file.variables.keys():
|
254
|
+
# get values, shape and dimensions
|
255
|
+
data = self._file[key]
|
256
|
+
shape = data.shape
|
257
|
+
dimensions = data.dimensions
|
258
|
+
|
259
|
+
# add variable name to info-string
|
260
|
+
info_str += key.replace('.', '_') + ' : '
|
261
|
+
|
262
|
+
# pad shape if required (trailing single dimensions are
|
263
|
+
# discarded following the numpy default)
|
264
|
+
while len(shape) < len(dimensions):
|
265
|
+
shape += (1, )
|
266
|
+
|
267
|
+
# add value for scalars
|
268
|
+
if data.size == 1:
|
269
|
+
info_str += str(data[:][0]) + '\n'
|
270
|
+
|
271
|
+
# Handle multidimensional data
|
272
|
+
else:
|
273
|
+
# make verbose shape, e.g., '(M=100, R=2, N=128, '
|
274
|
+
shape_verbose = "("
|
275
|
+
for s, d in zip(shape, dimensions):
|
276
|
+
shape_verbose += f"{d}={s}, "
|
277
|
+
|
278
|
+
# add shape information
|
279
|
+
info_str += shape_verbose[:-2] + ")\n"
|
280
|
+
# add value information if not too much
|
281
|
+
if data.size < 7:
|
282
|
+
info_str += " " + \
|
283
|
+
str(np.squeeze(data[:])).replace("\n", "\n ") + "\n"
|
284
|
+
|
285
|
+
# Add variable-attributes to info string (e.g. 'Type' or 'Units)
|
286
|
+
for att_ in [a for a in self._file[key].ncattrs()]:
|
287
|
+
info_str += (key.replace('.', '_') + f'_{att_} : '
|
288
|
+
+ getattr(data, att_) + '\n')
|
289
|
+
|
290
|
+
# write to text file
|
291
|
+
if file is not None:
|
292
|
+
with open(file, 'w') as f_id:
|
293
|
+
f_id.write(info_str + "\n")
|
294
|
+
|
295
|
+
# print to console
|
296
|
+
print(info_str)
|
sofar/update_conventions.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
import contextlib
|
2
2
|
import os
|
3
|
+
import shutil
|
3
4
|
import re
|
4
5
|
import glob
|
5
6
|
import json
|
6
7
|
import requests
|
7
8
|
from bs4 import BeautifulSoup
|
9
|
+
from tempfile import TemporaryDirectory
|
8
10
|
|
9
11
|
|
10
12
|
def update_conventions(conventions_path=None, assume_yes=False):
|
@@ -20,9 +22,9 @@ def update_conventions(conventions_path=None, assume_yes=False):
|
|
20
22
|
https://www.sofaconventions.org/conventions/ and
|
21
23
|
https://www.sofaconventions.org/conventions/deprecated/.
|
22
24
|
2.
|
23
|
-
|
25
|
+
Notify which conventions would be added or updated.
|
24
26
|
3.
|
25
|
-
|
27
|
+
Convert csv files to json files to be read by sofar.
|
26
28
|
|
27
29
|
The csv and json files are stored at sofar/conventions. Sofar works only on
|
28
30
|
the json files. To get a list of all currently available SOFA conventions
|
@@ -41,28 +43,16 @@ def update_conventions(conventions_path=None, assume_yes=False):
|
|
41
43
|
``None``, which saves the conventions inside the sofar package.
|
42
44
|
Conventions saved under a different path can not be used by sofar. This
|
43
45
|
parameter was added mostly for testing and debugging.
|
44
|
-
|
46
|
+
assume_yes : bool, optional
|
45
47
|
|
46
|
-
``True``
|
47
|
-
Updating the conventions must be confirmed by typing "y".
|
48
48
|
``False``
|
49
|
+
Updating the conventions must be confirmed by typing "y".
|
50
|
+
``True``
|
49
51
|
The conventions are updated without confirmation.
|
50
52
|
|
51
|
-
The default is ``
|
53
|
+
The default is ``False``
|
52
54
|
"""
|
53
55
|
|
54
|
-
if not assume_yes:
|
55
|
-
# these lines were only tested manually. I was too lazy to write a test
|
56
|
-
# coping with keyboard input
|
57
|
-
print(("Are you sure that you want to update the conventions? "
|
58
|
-
"Read the documentation before continuing. "
|
59
|
-
"If updateing breaks sofar it has to be re-installed"
|
60
|
-
"(y/n)"))
|
61
|
-
response = input()
|
62
|
-
if response != "y":
|
63
|
-
print("Updating the conventions was canceled.")
|
64
|
-
return
|
65
|
-
|
66
56
|
# url for parsing and downloading the convention files
|
67
57
|
urls = ("https://www.sofaconventions.org/conventions/",
|
68
58
|
"https://www.sofaconventions.org/conventions/deprecated/")
|
@@ -93,80 +83,113 @@ def update_conventions(conventions_path=None, assume_yes=False):
|
|
93
83
|
if not os.path.isdir(os.path.join(conventions_path, "deprecated")):
|
94
84
|
os.mkdir(os.path.join(conventions_path, "deprecated"))
|
95
85
|
|
96
|
-
# Loop and download conventions if they changed
|
86
|
+
# Loop and download conventions to temporary directory if they changed
|
97
87
|
updated = False
|
98
|
-
for convention in conventions:
|
99
88
|
|
100
|
-
|
101
|
-
|
102
|
-
continue
|
103
|
-
|
104
|
-
# get filename and url
|
105
|
-
is_standardized = convention in standardized
|
106
|
-
standardized_csv = os.path.join(conventions_path, convention)
|
107
|
-
deprecated_csv = os.path.join(
|
108
|
-
conventions_path, "deprecated", convention)
|
109
|
-
url = (
|
110
|
-
f"{urls[0]}/{convention}"
|
111
|
-
if is_standardized
|
112
|
-
else f"{urls[1]}/{convention}"
|
113
|
-
)
|
89
|
+
update = []
|
90
|
+
deprecate = []
|
114
91
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
92
|
+
with TemporaryDirectory() as temp:
|
93
|
+
os.mkdir(os.path.join(temp, 'deprecated'))
|
94
|
+
for convention in conventions:
|
95
|
+
|
96
|
+
# exclude these conventions
|
97
|
+
if convention.startswith(("General_", "GeneralString_")):
|
98
|
+
continue
|
99
|
+
|
100
|
+
# get filename and url
|
101
|
+
is_standardized = convention in standardized
|
102
|
+
standardized_csv = os.path.join(conventions_path, convention)
|
103
|
+
deprecated_csv = os.path.join(
|
104
|
+
conventions_path, "deprecated", convention)
|
105
|
+
url = (
|
106
|
+
f"{urls[0]}/{convention}"
|
107
|
+
if is_standardized
|
108
|
+
else f"{urls[1]}/{convention}"
|
109
|
+
)
|
110
|
+
|
111
|
+
# download SOFA convention definitions to package directory
|
112
|
+
data = requests.get(url)
|
113
|
+
# remove windows style line breaks and trailing tabs
|
114
|
+
data = data.content.replace(b"\r\n", b"\n").replace(b"\t\n", b"\n")
|
115
|
+
|
116
|
+
# check if convention needs to be added or updated
|
117
|
+
if is_standardized and not os.path.isfile(standardized_csv):
|
118
|
+
# add new standardized convention
|
119
|
+
updated = True
|
120
|
+
with open(os.path.join(temp, convention), "wb") as file:
|
121
|
+
file.write(data)
|
122
|
+
print(f"- add convention: {convention[:-4]}")
|
123
|
+
update.append(convention)
|
124
|
+
if is_standardized and os.path.isfile(standardized_csv):
|
125
|
+
# check for update of a standardized convention
|
126
|
+
with open(standardized_csv, "rb") as file:
|
127
|
+
data_current = b"".join(file.readlines())
|
128
|
+
data_current = data_current.replace(
|
129
|
+
b"\r\n", b"\n").replace(b"\t\n", b"\n")
|
130
|
+
if data_current != data:
|
131
|
+
updated = True
|
132
|
+
with open(os.path.join(temp, convention), "wb") as file:
|
133
|
+
file.write(data)
|
134
|
+
print(f"- update convention: {convention[:-4]}")
|
135
|
+
update.append(convention)
|
136
|
+
elif not is_standardized and os.path.isfile(standardized_csv):
|
137
|
+
# deprecate standardized convention
|
134
138
|
updated = True
|
135
|
-
with open(
|
139
|
+
with open(os.path.join(temp, 'deprecated', convention), "wb") \
|
140
|
+
as file:
|
136
141
|
file.write(data)
|
137
|
-
print(f"-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
142
|
+
print(f"- deprecate convention: {convention[:-4]}")
|
143
|
+
deprecate.append(convention)
|
144
|
+
elif not is_standardized and os.path.isfile(deprecated_csv):
|
145
|
+
# check for update of a deprecated convention
|
146
|
+
with open(deprecated_csv, "rb") as file:
|
147
|
+
data_current = b"".join(file.readlines())
|
148
|
+
data_current = data_current.replace(
|
149
|
+
b"\r\n", b"\n").replace(b"\t\n", b"\n")
|
150
|
+
if data_current != data:
|
151
|
+
updated = True
|
152
|
+
with open(os.path.join(temp, 'deprecated', convention),
|
153
|
+
"wb") as file:
|
154
|
+
file.write(data)
|
155
|
+
print(f"- update deprecated convention: {convention[:-4]}")
|
156
|
+
update.append(os.path.join('deprecated', convention))
|
157
|
+
elif not is_standardized and not os.path.isfile(deprecated_csv):
|
158
|
+
# add new deprecation
|
153
159
|
updated = True
|
154
|
-
with open(
|
160
|
+
with open(os.path.join(temp, 'deprecated', convention), "wb") \
|
161
|
+
as file:
|
155
162
|
file.write(data)
|
156
|
-
print(f"-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
print(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
163
|
+
print(f"- add deprecated convention: {convention[:-4]}")
|
164
|
+
update.append(os.path.join('deprecated', convention))
|
165
|
+
|
166
|
+
if updated and not assume_yes:
|
167
|
+
# these lines were only tested manually. I was too lazy to write a
|
168
|
+
# test coping with keyboard input
|
169
|
+
print(("\nDo you want to update the conventions above? (y/n)\n"
|
170
|
+
"Read the documentation before continuing. "
|
171
|
+
"If updating breaks sofar it has to be re-installed"))
|
172
|
+
response = input()
|
173
|
+
if response != "y":
|
174
|
+
print("\nUpdating the conventions was canceled.")
|
175
|
+
return
|
176
|
+
|
177
|
+
if updated:
|
178
|
+
for convention in update:
|
179
|
+
shutil.copy(os.path.join(temp, convention),
|
180
|
+
os.path.join(conventions_path, convention))
|
181
|
+
for convention in deprecate:
|
182
|
+
os.remove(os.path.join(conventions_path, convention))
|
183
|
+
os.remove(
|
184
|
+
os.path.join(conventions_path, f"{convention[:-3]}json"))
|
185
|
+
shutil.copy(
|
186
|
+
os.path.join(temp, 'deprecated', convention),
|
187
|
+
os.path.join(conventions_path, 'deprecated', convention))
|
188
|
+
# compile json files from csv file
|
189
|
+
_compile_conventions(conventions_path)
|
190
|
+
print("... done.")
|
191
|
+
else:
|
192
|
+
print("... conventions already up to date.")
|
170
193
|
|
171
194
|
|
172
195
|
def _compile_conventions(conventions_path=None):
|
sofar/utils.py
CHANGED
@@ -143,7 +143,7 @@ def equals(sofa_a, sofa_b, verbose=True, exclude=None):
|
|
143
143
|
``'GLOBAL'``
|
144
144
|
Exclude all global attributes, i.e., fields starting with 'GLOBAL:'
|
145
145
|
``'DATE'``
|
146
|
-
Exclude date
|
146
|
+
Exclude date attributes, i.e., fields that contain 'Date'
|
147
147
|
``'ATTR'``
|
148
148
|
Exclude all attributes, i.e., fields that contain ':'
|
149
149
|
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021, The pyfar developers
|
2
4
|
|
3
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -17,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
21
|
SOFTWARE.
|
22
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: sofar
|
3
|
+
Version: 1.2.0
|
4
|
+
Summary: Maybe the most complete python package for SOFA files so far
|
5
|
+
Home-page: https://pyfar.org/
|
6
|
+
Download-URL: https://pypi.org/project/sofar/
|
7
|
+
Author: The pyfar developers
|
8
|
+
Author-email: info@pyfar.org
|
9
|
+
License: MIT license
|
10
|
+
Project-URL: Bug Tracker, https://github.com/pyfar/sofar/issues
|
11
|
+
Project-URL: Documentation, https://sofar.readthedocs.io/
|
12
|
+
Project-URL: Source Code, https://github.com/pyfar/sofar
|
13
|
+
Keywords: sofar
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
17
|
+
Classifier: Natural Language :: English
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
24
|
+
Requires-Python: >=3.8
|
25
|
+
Description-Content-Type: text/markdown
|
26
|
+
License-File: LICENSE
|
27
|
+
License-File: AUTHORS.rst
|
28
|
+
Requires-Dist: netCDF4
|
29
|
+
Requires-Dist: numpy>=1.14.0
|
30
|
+
Requires-Dist: beautifulsoup4
|
31
|
+
Requires-Dist: requests
|
32
|
+
Requires-Dist: packaging
|
33
|
+
|
34
|
+
<h1 align="center">
|
35
|
+
<img src="https://github.com/pyfar/gallery/raw/main/docs/resources/logos/pyfar_logos_fixed_size_sofar.png" width="300">
|
36
|
+
</h1><br>
|
37
|
+
|
38
|
+
[](https://badge.fury.io/py/sofar)
|
39
|
+
[](https://sofar.readthedocs.io/en/latest/?badge=latest)
|
40
|
+
[](https://circleci.com/gh/pyfar/sofar)
|
41
|
+
|
42
|
+
Sofar is maybe the most complete Python package for the SOFA file format so
|
43
|
+
far. SOFA files store spatially distributed acoustic data such as impulse
|
44
|
+
responses or transfer functions. They are defined by the AES69-2022 standard
|
45
|
+
(see references). These are the key features of sofar
|
46
|
+
|
47
|
+
- Read, edit, and write SOFA files
|
48
|
+
- Add custom attributes to SOFA files
|
49
|
+
- Full Verification of the content of a SOFA files against AES69-2022
|
50
|
+
- Upgrade data that uses outdated SOFA conventions
|
51
|
+
- Open license allows unrestricted use
|
52
|
+
- sofar is tested using continuous integration on
|
53
|
+
- Uses a complete definition of the AES69-2022 standard (see references) maintained at [sofa_conventions](https://github.com/pyfar/sofa_conventions)
|
54
|
+
|
55
|
+
Getting Started
|
56
|
+
===============
|
57
|
+
|
58
|
+
The [sofar and SOFA notebook](https://pyfar-gallery.readthedocs.io/en/latest/gallery/interactive/sofar_introduction.html)
|
59
|
+
gives an overview of the most important sofar functionality and is a good starting point. For processing and visualizing data
|
60
|
+
inside SOFA files, we recommend the [pyfar package](https://pyfar.readthedocs.io) that can read SOFA files through
|
61
|
+
`pyfar.io.read_sofa` and the in-depth examples contained in the
|
62
|
+
[pyfar example gallery](https://pyfar-gallery.readthedocs.io/en/latest/examples_gallery.html). Check out
|
63
|
+
[read the docs](https://sofar.readthedocs.io) for a complete documentation of sofar. A more detailed introduction to the SOFA
|
64
|
+
file format is given by Majdak et. al. 2022 (see references below). All information is also bundled at [pyfar.org](https://pyfar.org).
|
65
|
+
|
66
|
+
Installation
|
67
|
+
============
|
68
|
+
|
69
|
+
Use pip to install sofar
|
70
|
+
|
71
|
+
pip install sofar
|
72
|
+
|
73
|
+
|
74
|
+
(Requires Python >= 3.8)
|
75
|
+
|
76
|
+
If the installation fails, please check out the [help section](https://pyfar-gallery.readthedocs.io/en/latest/help).
|
77
|
+
|
78
|
+
Contributing
|
79
|
+
============
|
80
|
+
|
81
|
+
Refer to the [contribution guidelines](https://sofar.readthedocs.io/en/stable/contributing.html) for more information.
|
82
|
+
|
83
|
+
References
|
84
|
+
==========
|
85
|
+
|
86
|
+
AES69-2022: *AES standard for file exchange - Spatial acoustic data file
|
87
|
+
format*, Audio Engineering Society, Inc., New York, NY, USA.
|
88
|
+
(https://www.aes.org/publications/standards/search.cfm?docID=99)
|
89
|
+
|
90
|
+
P. Majdak, F. Zotter, F. Brinkmann, J. De Muynke, M. Mihocic, and M.
|
91
|
+
Noisternig, "Spatially Oriented Format for Acoustics 2.1: Introduction and
|
92
|
+
Recent Advances", *J. Audio Eng. Soc.*, vol. 70, no. 7/8, pp. 565-584,
|
93
|
+
Jul. 2022. DOI: https://doi.org/10.17743/jaes.2022.0026
|