sofar 1.1.3__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.
Files changed (35) hide show
  1. sofar/__init__.py +4 -4
  2. sofar/io.py +18 -10
  3. sofar/sofa.py +53 -37
  4. sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.csv +46 -0
  5. sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.json +353 -0
  6. sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.csv +46 -0
  7. sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.json +353 -0
  8. sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.csv +46 -0
  9. sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.json +351 -0
  10. sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.csv +46 -0
  11. sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.json +351 -0
  12. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.csv +47 -0
  13. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.json +366 -0
  14. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.csv +51 -0
  15. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.json +397 -0
  16. sofar/sofa_conventions/rules/deprecations.json +2 -1
  17. sofar/sofa_conventions/rules/rules.json +21 -2
  18. sofar/sofa_conventions/rules/upgrade.json +36 -0
  19. sofar/sofastream.py +296 -0
  20. sofar/update_conventions.py +112 -92
  21. sofar/utils.py +1 -1
  22. {sofar-1.1.3.dist-info → sofar-1.2.0.dist-info}/LICENSE +4 -1
  23. sofar-1.2.0.dist-info/METADATA +93 -0
  24. {sofar-1.1.3.dist-info → sofar-1.2.0.dist-info}/RECORD +34 -19
  25. {sofar-1.1.3.dist-info → sofar-1.2.0.dist-info}/WHEEL +1 -1
  26. tests/conftest.py +27 -0
  27. tests/test_io.py +9 -5
  28. tests/test_sofa.py +1 -1
  29. tests/test_sofa_upgrade_conventions.py +10 -1
  30. tests/test_sofa_verify.py +3 -3
  31. tests/test_sofastream.py +126 -0
  32. tests/test_utils.py +18 -8
  33. sofar-1.1.3.dist-info/METADATA +0 -91
  34. {sofar-1.1.3.dist-info → sofar-1.2.0.dist-info}/AUTHORS.rst +0 -0
  35. {sofar-1.1.3.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)
@@ -1,9 +1,12 @@
1
1
  import contextlib
2
2
  import os
3
+ import shutil
4
+ import re
3
5
  import glob
4
6
  import json
5
7
  import requests
6
8
  from bs4 import BeautifulSoup
9
+ from tempfile import TemporaryDirectory
7
10
 
8
11
 
9
12
  def update_conventions(conventions_path=None, assume_yes=False):
@@ -19,9 +22,9 @@ def update_conventions(conventions_path=None, assume_yes=False):
19
22
  https://www.sofaconventions.org/conventions/ and
20
23
  https://www.sofaconventions.org/conventions/deprecated/.
21
24
  2.
22
- Convert csv files to json files to be read by sofar.
25
+ Notify which conventions would be added or updated.
23
26
  3.
24
- Notify which conventions were newly added or updated.
27
+ Convert csv files to json files to be read by sofar.
25
28
 
26
29
  The csv and json files are stored at sofar/conventions. Sofar works only on
27
30
  the json files. To get a list of all currently available SOFA conventions
@@ -40,28 +43,16 @@ def update_conventions(conventions_path=None, assume_yes=False):
40
43
  ``None``, which saves the conventions inside the sofar package.
41
44
  Conventions saved under a different path can not be used by sofar. This
42
45
  parameter was added mostly for testing and debugging.
43
- response : bool, optional
46
+ assume_yes : bool, optional
44
47
 
45
- ``True``
46
- Updating the conventions must be confirmed by typing "y".
47
48
  ``False``
49
+ Updating the conventions must be confirmed by typing "y".
50
+ ``True``
48
51
  The conventions are updated without confirmation.
49
52
 
50
- The default is ``True``
53
+ The default is ``False``
51
54
  """
52
55
 
53
- if not assume_yes:
54
- # these lines were only tested manually. I was too lazy to write a test
55
- # coping with keyboard input
56
- print(("Are you sure that you want to update the conventions? "
57
- "Read the documentation before continuing. "
58
- "If updateing breaks sofar it has to be re-installed"
59
- "(y/n)"))
60
- response = input()
61
- if response != "y":
62
- print("Updating the conventions was canceled.")
63
- return
64
-
65
56
  # url for parsing and downloading the convention files
66
57
  urls = ("https://www.sofaconventions.org/conventions/",
67
58
  "https://www.sofaconventions.org/conventions/deprecated/")
@@ -92,80 +83,113 @@ def update_conventions(conventions_path=None, assume_yes=False):
92
83
  if not os.path.isdir(os.path.join(conventions_path, "deprecated")):
93
84
  os.mkdir(os.path.join(conventions_path, "deprecated"))
94
85
 
95
- # Loop and download conventions if they changed
86
+ # Loop and download conventions to temporary directory if they changed
96
87
  updated = False
97
- for convention in conventions:
98
88
 
99
- # exclude these conventions
100
- if convention.startswith(("General_", "GeneralString_")):
101
- continue
102
-
103
- # get filename and url
104
- is_standardized = convention in standardized
105
- standardized_csv = os.path.join(conventions_path, convention)
106
- deprecated_csv = os.path.join(
107
- conventions_path, "deprecated", convention)
108
- url = (
109
- f"{urls[0]}/{convention}"
110
- if is_standardized
111
- else f"{urls[1]}/{convention}"
112
- )
89
+ update = []
90
+ deprecate = []
113
91
 
114
- # download SOFA convention definitions to package directory
115
- data = requests.get(url)
116
- # remove windows style line breaks and trailing tabs
117
- data = data.content.replace(b"\r\n", b"\n").replace(b"\t\n", b"\n")
118
-
119
- # check if convention needs to be added or updated
120
- if is_standardized and not os.path.isfile(standardized_csv):
121
- # add new standardized convention
122
- updated = True
123
- with open(standardized_csv, "wb") as file:
124
- file.write(data)
125
- print(f"- added convention: {convention[:-4]}")
126
- if is_standardized and os.path.isfile(standardized_csv):
127
- # check for update of a standardized convention
128
- with open(standardized_csv, "rb") as file:
129
- data_current = b"".join(file.readlines())
130
- data_current = data_current.replace(
131
- b"\r\n", b"\n").replace(b"\t\n", b"\n")
132
- if data_current != data:
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
133
119
  updated = True
134
- with open(standardized_csv, "wb") as file:
120
+ with open(os.path.join(temp, convention), "wb") as file:
135
121
  file.write(data)
136
- print(f"- updated convention: {convention[:-4]}")
137
- elif not is_standardized and os.path.isfile(standardized_csv):
138
- # deprecate standardized convention
139
- updated = True
140
- with open(deprecated_csv, "wb") as file:
141
- file.write(data)
142
- os.remove(standardized_csv)
143
- os.remove(f"{standardized_csv[:-3]}json")
144
- print(f"- deprecated convention: {convention[:-4]}")
145
- elif not is_standardized and os.path.isfile(deprecated_csv):
146
- # check for update of a deprecated convention
147
- with open(deprecated_csv, "rb") as file:
148
- data_current = b"".join(file.readlines())
149
- data_current = data_current.replace(
150
- b"\r\n", b"\n").replace(b"\t\n", b"\n")
151
- if data_current != 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
152
138
  updated = True
153
- with open(deprecated_csv, "wb") as file:
139
+ with open(os.path.join(temp, 'deprecated', convention), "wb") \
140
+ as file:
154
141
  file.write(data)
155
- print(f"- updated deprecated convention: {convention[:-4]}")
156
- elif not is_standardized and not os.path.isfile(deprecated_csv):
157
- # add new deprecation
158
- updated = True
159
- with open(deprecated_csv, "wb") as file:
160
- file.write(data)
161
- print(f"- added deprecated convention: {convention[:-4]}")
162
-
163
- if updated:
164
- # compile json files from csv file
165
- _compile_conventions(conventions_path)
166
- print("... done.")
167
- else:
168
- print("... conventions already up to date.")
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
159
+ updated = True
160
+ with open(os.path.join(temp, 'deprecated', convention), "wb") \
161
+ as file:
162
+ file.write(data)
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.")
169
193
 
170
194
 
171
195
  def _compile_conventions(conventions_path=None):
@@ -377,13 +401,9 @@ def _check_congruency(save_dir=None, branch="master"):
377
401
 
378
402
  # get file names of conventions from github
379
403
  url = urls_check[1]
380
- page = requests.get(url).json()
381
- sofatoolbox = []
382
- for content in page["payload"]["tree"]["items"]:
383
- if content["contentType"] == "file" and \
384
- content["path"].startswith("SOFAtoolbox/conventions") and \
385
- content["name"].endswith("csv"):
386
- sofatoolbox.append(content["name"])
404
+ page = requests.get(url).text
405
+ sofatoolbox = re.findall(
406
+ r'"SOFAtoolbox/conventions/([^"]+\.csv)"', page)
387
407
 
388
408
  if not sofatoolbox:
389
409
  raise ValueError(f"Did not find any conventions at {url}")
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 attributs, i.e., fields that contain '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
- Copyright (c) [2021] [The pyfar developers]
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
+ [![PyPI version](https://badge.fury.io/py/sofar.svg)](https://badge.fury.io/py/sofar)
39
+ [![Documentation Status](https://readthedocs.org/projects/sofar/badge/?version=latest)](https://sofar.readthedocs.io/en/latest/?badge=latest)
40
+ [![CircleCI](https://circleci.com/gh/pyfar/sofar.svg?style=shield)](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