sofar 1.2.1__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 (105) hide show
  1. docs/Makefile +20 -0
  2. docs/api_reference.rst +20 -0
  3. docs/conf.py +167 -0
  4. docs/contributing.rst +1 -0
  5. docs/history.rst +1 -0
  6. docs/index.rst +4 -0
  7. docs/make.bat +36 -0
  8. docs/readme.rst +1 -0
  9. docs/resources/conventions.py +162 -0
  10. docs/resources/working_with_sofa_HRIR_lateral.png +0 -0
  11. docs/resources/working_with_sofa_source_horizontal.png +0 -0
  12. docs/resources/working_with_sofa_source_lateral.png +0 -0
  13. docs/sofar.rst +82 -0
  14. sofar/__init__.py +28 -0
  15. sofar/io.py +427 -0
  16. sofar/sofa.py +1835 -0
  17. sofar/sofa_conventions/VERSION +1 -0
  18. sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.csv +46 -0
  19. sofar/sofa_conventions/conventions/AnnotatedEmitterAudio_0.2.json +353 -0
  20. sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.csv +46 -0
  21. sofar/sofa_conventions/conventions/AnnotatedReceiverAudio_0.2.json +353 -0
  22. sofar/sofa_conventions/conventions/FreeFieldDirectivityTF_1.1.csv +59 -0
  23. sofar/sofa_conventions/conventions/FreeFieldDirectivityTF_1.1.json +444 -0
  24. sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.csv +43 -0
  25. sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.json +333 -0
  26. sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.csv +44 -0
  27. sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.json +340 -0
  28. sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.csv +37 -0
  29. sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.json +270 -0
  30. sofar/sofa_conventions/conventions/GeneralFIR_1.0.csv +40 -0
  31. sofar/sofa_conventions/conventions/GeneralFIR_1.0.json +295 -0
  32. sofar/sofa_conventions/conventions/GeneralSOS_1.0.csv +40 -0
  33. sofar/sofa_conventions/conventions/GeneralSOS_1.0.json +306 -0
  34. sofar/sofa_conventions/conventions/GeneralTF-E_1.0.csv +38 -0
  35. sofar/sofa_conventions/conventions/GeneralTF-E_1.0.json +277 -0
  36. sofar/sofa_conventions/conventions/GeneralTF_1.0.csv +38 -0
  37. sofar/sofa_conventions/conventions/GeneralTF_1.0.json +277 -0
  38. sofar/sofa_conventions/conventions/GeneralTF_2.0.csv +38 -0
  39. sofar/sofa_conventions/conventions/GeneralTF_2.0.json +277 -0
  40. sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.csv +47 -0
  41. sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.json +369 -0
  42. sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.csv +43 -0
  43. sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.json +349 -0
  44. sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.csv +44 -0
  45. sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.json +340 -0
  46. sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.csv +43 -0
  47. sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.json +349 -0
  48. sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.csv +51 -0
  49. sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.json +396 -0
  50. sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.csv +78 -0
  51. sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.json +601 -0
  52. sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.csv +78 -0
  53. sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.json +601 -0
  54. sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.csv +46 -0
  55. sofar/sofa_conventions/conventions/deprecated/AnnotatedEmitterAudio_0.1.json +351 -0
  56. sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.csv +46 -0
  57. sofar/sofa_conventions/conventions/deprecated/AnnotatedReceiverAudio_0.1.json +351 -0
  58. sofar/sofa_conventions/conventions/deprecated/FreeFieldDirectivityTF_1.0.csv +58 -0
  59. sofar/sofa_conventions/conventions/deprecated/FreeFieldDirectivityTF_1.0.json +437 -0
  60. sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.csv +37 -0
  61. sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.json +270 -0
  62. sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.csv +48 -0
  63. sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.json +376 -0
  64. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.csv +43 -0
  65. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.json +333 -0
  66. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.csv +44 -0
  67. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.json +340 -0
  68. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.csv +44 -0
  69. sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.json +340 -0
  70. sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.csv +51 -0
  71. sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.json +396 -0
  72. sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.csv +51 -0
  73. sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.json +396 -0
  74. sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.csv +47 -0
  75. sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.json +360 -0
  76. sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.csv +47 -0
  77. sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.json +360 -0
  78. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.csv +47 -0
  79. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.1.json +366 -0
  80. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.csv +51 -0
  81. sofar/sofa_conventions/conventions/deprecated/SingleTrackedAudio_0.2.json +397 -0
  82. sofar/sofa_conventions/rules/deprecations.json +13 -0
  83. sofar/sofa_conventions/rules/rules.json +819 -0
  84. sofar/sofa_conventions/rules/unit_aliases.json +11 -0
  85. sofar/sofa_conventions/rules/upgrade.json +226 -0
  86. sofar/sofa_conventions/write_upgrade_rules.py +139 -0
  87. sofar/sofa_conventions/write_verification_data.py +313 -0
  88. sofar/sofa_conventions/write_verification_rules.py +356 -0
  89. sofar/sofastream.py +301 -0
  90. sofar/update_conventions.py +449 -0
  91. sofar/utils.py +316 -0
  92. sofar-1.2.1.dist-info/LICENSE +22 -0
  93. sofar-1.2.1.dist-info/METADATA +136 -0
  94. sofar-1.2.1.dist-info/RECORD +105 -0
  95. sofar-1.2.1.dist-info/WHEEL +5 -0
  96. sofar-1.2.1.dist-info/top_level.txt +3 -0
  97. tests/__init__.py +0 -0
  98. tests/conftest.py +27 -0
  99. tests/test_deprecations.py +19 -0
  100. tests/test_io.py +349 -0
  101. tests/test_sofa.py +353 -0
  102. tests/test_sofa_upgrade_conventions.py +111 -0
  103. tests/test_sofa_verify.py +480 -0
  104. tests/test_sofastream.py +127 -0
  105. tests/test_utils.py +250 -0
@@ -0,0 +1,111 @@
1
+ import sofar as sf
2
+ from sofar.utils import _get_conventions
3
+ import pytest
4
+ import os
5
+ import json
6
+
7
+ # load deprecation rules
8
+ base = os.path.join(
9
+ os.path.dirname(__file__), "..", "sofar", "sofa_conventions", "rules")
10
+
11
+ with open(os.path.join(base, "upgrade.json"), "r") as file:
12
+ upgrade = json.load(file)
13
+
14
+ # load convention paths
15
+ paths = _get_conventions("paths")
16
+
17
+
18
+ def test_printouts(capfd):
19
+ """
20
+ Test console print for calling upgrade with up to data and deprecated data.
21
+ """
22
+
23
+ # test printouts with up to data convention -------------------------------
24
+ sofa = sf.Sofa("SimpleFreeFieldHRIR")
25
+ sofa.upgrade_convention()
26
+ out, _ = capfd.readouterr()
27
+ assert "is up to date" in out
28
+
29
+ # test printouts with outdated conventions --------------------------------
30
+ sofa = sf.Sofa("SimpleFreeFieldTF", verify=False)
31
+ out, _ = capfd.readouterr()
32
+
33
+ # calling with default arguments to get information only
34
+ sofa.upgrade_convention()
35
+ out, _ = capfd.readouterr()
36
+ assert "can be upgraded" in out
37
+ assert "SimpleFreeFieldHRTF v1.0" in out
38
+ assert "invalid" not in out
39
+
40
+ # calling with invalid target
41
+ sofa.upgrade_convention("SimpleFreeFieldHRTF_0.1")
42
+ out, _ = capfd.readouterr()
43
+ assert "can be upgraded" in out
44
+ assert "invalid" in out
45
+ assert "SimpleFreeFieldHRTF v1.0" in out
46
+
47
+ # calling with valid target (nothing to move, remove, no message)
48
+ sofa.upgrade_convention("SimpleFreeFieldHRTF_1.0")
49
+ out, _ = capfd.readouterr()
50
+ assert "can be upgraded" not in out
51
+ assert "invalid" not in out
52
+ assert "Upgrading SimpleFreeFieldTF v1.0" in out
53
+ assert "to SimpleFreeFieldHRTF v1.0" in out
54
+ assert "No data to move" in out
55
+ assert "No data to remove" in out
56
+ assert "All mandatory data contained" in out
57
+
58
+ # calling with valid target (including message)
59
+ sofa = sf.Sofa("SimpleFreeFieldHRIR", version="0.4", verify=False)
60
+ out, _ = capfd.readouterr()
61
+ sofa.upgrade_convention("SimpleFreeFieldHRIR_1.0")
62
+ out, _ = capfd.readouterr()
63
+ assert "Consider to add the optional data 'SourceUp'" in out
64
+
65
+ # calling with valid target (things to move)
66
+ sofa = sf.Sofa("MultiSpeakerBRIR", verify=False)
67
+ out, _ = capfd.readouterr()
68
+ sofa.upgrade_convention("SingleRoomMIMOSRIR_1.0")
69
+ out, _ = capfd.readouterr()
70
+ assert "Moving Data_IR" in out
71
+
72
+
73
+ @pytest.mark.parametrize("path", paths)
74
+ def test_upgrade_conventions(path, capfd):
75
+
76
+ # extract information for testing
77
+ convention, version = os.path.basename(path).split("_")
78
+ version = version[:-5]
79
+ deprecated = "deprecated" in path
80
+
81
+ # get SOFA object and targets for upgrading
82
+ sofa = sf.Sofa(convention, version=version, verify=False)
83
+ out, _ = capfd.readouterr()
84
+ targets = sofa.upgrade_convention()
85
+ out, _ = capfd.readouterr()
86
+
87
+ # don't verify conventions that might require user action after
88
+ if os.path.basename(path) in [
89
+ "FreeFieldDirectivityTF_1.0.json",
90
+ "SingleTrackedAudio_0.1.json",
91
+ "SingleTrackedAudio_0.2.json"]:
92
+ # FreeFieldDirectivityTF_1.0
93
+ # - optional dependency GLOBAL_EmitterDescription
94
+ # might need to be added
95
+ # SingleTrackedAudio_0.x
96
+ # - can be updated to multiple conventions depending on the content
97
+ verify = False
98
+ else:
99
+ verify = True
100
+
101
+ if targets:
102
+ for target in targets:
103
+ sofa.upgrade_convention(target, verify=verify)
104
+ out, _ = capfd.readouterr()
105
+ assert "Upgrading" in out
106
+ elif deprecated:
107
+ sofa.upgrade_convention()
108
+ out, _ = capfd.readouterr()
109
+ assert "is missing upgrade rules" in out
110
+ else:
111
+ assert not deprecated
@@ -0,0 +1,480 @@
1
+ """Tests for sofar.Sofa.verify()."""
2
+ import sofar as sf
3
+ from sofar.utils import _complete_sofa
4
+ import os
5
+ from glob import glob
6
+ import pytest
7
+ import numpy as np
8
+ import warnings
9
+
10
+ # get verification rules
11
+ _, unit_aliases, _, _ = sf.Sofa._verification_rules()
12
+
13
+ # directory containing the verification data
14
+ basedir = os.path.join(os.path.dirname(__file__), "..", "sofar",
15
+ "sofa_conventions", "data")
16
+
17
+ # files for testing the verification rules
18
+ restricted_values = glob(
19
+ os.path.join(basedir, "restricted_values", "*.sofa"))
20
+ general_dependencies = glob(
21
+ os.path.join(basedir, "general_dependencies", "*.sofa"))
22
+ specific_dependencies = glob(
23
+ os.path.join(basedir, "specific_dependencies", "*.sofa"))
24
+ deprecations = glob(
25
+ os.path.join(basedir, "deprecations", "*.sofa"))
26
+ restricted_dimensions = glob(
27
+ os.path.join(basedir, "restricted_dimensions", "*.sofa"))
28
+
29
+
30
+ # general tests ---------------------------------------------------------------
31
+ def test_verify_value():
32
+ # example alias for testing as returned by Sofa._verification_rules()
33
+ unit_aliases = {"meter": "metre",
34
+ "degrees": "degree"}
35
+
36
+ # Simple pass: no restriction on value
37
+ assert sf.Sofa._verify_value("goofy", None, unit_aliases, "Some_Units")
38
+
39
+ # simple pass: single unit
40
+ assert sf.Sofa._verify_value(
41
+ "meter", ["metre"], unit_aliases, "Some_Units")
42
+
43
+ # complex pass: list of units
44
+ assert sf.Sofa._verify_value("degrees, degrees, meter",
45
+ ["degree, degree, metre"],
46
+ unit_aliases, "Some_Units")
47
+
48
+ # complex pass: list of units with other separators allowed by AES69
49
+ assert sf.Sofa._verify_value("degrees,degrees, meter",
50
+ ["degree, degree, metre"],
51
+ unit_aliases, "Some_Units")
52
+
53
+ # simple fail: single unit
54
+ assert not sf.Sofa._verify_value("centimeter", ["metre"],
55
+ unit_aliases, "Some_Units")
56
+
57
+ # complex fail: list of units
58
+ assert not sf.Sofa._verify_value("rad, rad, metre",
59
+ ["degree, degree, metre"],
60
+ unit_aliases, "Some_Units")
61
+
62
+
63
+ def test_issue_handling(capfd):
64
+ """Test different methods for handling issues during verification."""
65
+
66
+ error_msg = "\nERRORS\n------\n"
67
+ warning_msg = "\nWARNINGS\n--------\n"
68
+
69
+ # no issue
70
+ issue_handling = "raise"
71
+ error_occurred, issues = sf.Sofa._verify_handle_issues(
72
+ warning_msg, error_msg, issue_handling)
73
+ assert not error_occurred
74
+ assert issues is None
75
+
76
+ # raise
77
+ issue_handling = "raise"
78
+ with pytest.warns(UserWarning, match="warning"):
79
+ sf.Sofa._verify_handle_issues(
80
+ warning_msg + "warning", error_msg, issue_handling)
81
+ with pytest.raises(ValueError, match="error"):
82
+ sf.Sofa._verify_handle_issues(
83
+ warning_msg, error_msg + "error", issue_handling)
84
+
85
+ # print warning
86
+ issue_handling = "print"
87
+ with warnings.catch_warnings():
88
+ warnings.simplefilter('error')
89
+ _, issues = sf.Sofa._verify_handle_issues(
90
+ warning_msg + "warning", error_msg, issue_handling)
91
+ out, _ = capfd.readouterr()
92
+ assert "warning" in issues
93
+ assert 'warning' in out
94
+ assert "ERROR" not in issues
95
+ assert "ERROR" not in out
96
+ # print error
97
+ _, issues = sf.Sofa._verify_handle_issues(
98
+ warning_msg, error_msg + "error", issue_handling)
99
+ out, _ = capfd.readouterr()
100
+ assert "error" in issues
101
+ assert 'error' in out
102
+ assert "WARNING" not in issues
103
+ assert "WARNING" not in out
104
+
105
+ # test invalid data for netCDF attribute
106
+ issue_handling = "ignore"
107
+ sofa = sf.Sofa("GeneralFIR")
108
+ sofa.GLOBAL_Comment = [1, 2, 3]
109
+ issues = sofa.verify(issue_handling=issue_handling)
110
+
111
+ assert issues is None
112
+ assert capfd.readouterr() == ("", "")
113
+
114
+
115
+ def test_case_insensitivity():
116
+ """
117
+ Reading applications shall ignore the case of units and types of coordinate
118
+ systems.
119
+ """
120
+
121
+ # data type (must be case sensitive) --------------------------------------
122
+ sofa = sf.Sofa("SimpleFreeFieldHRIR")
123
+ sofa.protected = False
124
+ sofa.GLOBAL_DataType = "fir"
125
+ sofa.protected = True
126
+ with pytest.raises(ValueError, match="GLOBAL_DataType is fir"):
127
+ sofa.verify()
128
+
129
+ # room type ---------------------------------------------------------------
130
+ sofa = sf.Sofa("FreeFieldHRIR")
131
+ sofa.GLOBAL_RoomType = "Free field"
132
+ assert sofa.verify() is None
133
+
134
+ # units -------------------------------------------------------------------
135
+ # example alias for testing as returned by Sofa._verification_rules()
136
+ unit_aliases = {"meter": "metre",
137
+ "degrees": "degree"}
138
+
139
+ # case insensitive testing
140
+ assert sf.Sofa._verify_value(
141
+ "Meter", ["meter"], unit_aliases, "Some_Units")
142
+ assert sf.Sofa._verify_value("DegrEes, dEgreeS, MeTer",
143
+ ["degree, degree, metre"],
144
+ unit_aliases, "Some_Units")
145
+
146
+ sofa = sf.Sofa("FreeFieldDirectivityTF")
147
+ sofa.N_Units = "HertZ"
148
+ assert sofa.verify(issue_handling="return", mode="read") is None
149
+
150
+ # coordinate types --------------------------------------------------------
151
+ sofa = sf.Sofa("SimpleFreeFieldHRIR")
152
+ sofa.ListenerPosition_Type = "Cartesian"
153
+ assert sofa.verify(issue_handling="return") is None
154
+
155
+
156
+ # 1. check if the mandatory attributes are contained --------------------------
157
+ def test_missing_default_attributes(capfd):
158
+
159
+ # test missing default attribute
160
+ sofa = sf.Sofa("GeneralTF")
161
+ sofa.protected = False
162
+ delattr(sofa, "GLOBAL_Conventions")
163
+ sofa.protected = True
164
+
165
+ # pytest.raises error
166
+ with pytest.raises(ValueError, match="Detected missing mandatory data"):
167
+ sofa.verify(issue_handling="raise")
168
+
169
+ # prints warning and adds data
170
+ sofa.verify(issue_handling="print")
171
+ out, _ = capfd.readouterr()
172
+
173
+ assert "Added mandatory data with default values" in out
174
+ assert sofa.GLOBAL_Conventions == "SOFA"
175
+
176
+
177
+ # 2. verify data type ---------------------------------------------------------
178
+ def test_data_types(capfd):
179
+
180
+ # test invalid data for netCDF attribute
181
+ sofa = sf.Sofa("GeneralFIR")
182
+ sofa.GLOBAL_Comment = [1, 2, 3]
183
+ with pytest.raises(ValueError, match="- GLOBAL_Comment must be string"):
184
+ sofa.verify()
185
+
186
+ # test invalid data for netCDF double variable
187
+ sofa = sf.Sofa("GeneralFIR")
188
+ sofa.Data_IR = np.array("test")
189
+ with pytest.raises(ValueError, match="- Data_IR must be int or float"):
190
+ sofa.verify()
191
+
192
+ sofa.Data_IR = "1"
193
+ with pytest.raises(ValueError, match="- Data_IR must be int or float"):
194
+ sofa.verify()
195
+
196
+ sofa.Data_IR = 1+1j
197
+ with pytest.raises(ValueError, match="- Data_IR must be int or float"):
198
+ sofa.verify()
199
+
200
+ # test invalid data with issue_handling "print" and "return"
201
+ sofa = sf.Sofa("GeneralFIR")
202
+ sofa.Data_IR = np.array("test")
203
+ out, _ = capfd.readouterr()
204
+ issues = sofa.verify(issue_handling="print")
205
+ out, _ = capfd.readouterr()
206
+ assert issues is None
207
+ assert "- Data_IR must be int or float" in out
208
+
209
+ issues = sofa.verify(issue_handling="return")
210
+ out, _ = capfd.readouterr()
211
+ assert "- Data_IR must be int or float" in issues
212
+ assert "- Data_IR must be int or float" not in out
213
+
214
+ # test valid data
215
+ sofa.Data_IR = np.array([1])
216
+ sofa.verify()
217
+ sofa.Data_IR = [1]
218
+ sofa.verify()
219
+ sofa.Data_IR = 1
220
+ sofa.verify()
221
+
222
+ # test invalid data for netCDF attribute
223
+ sofa = sf.Sofa("GeneralFIR")
224
+ sofa.GLOBAL_History = 1
225
+ with pytest.raises(ValueError, match="- GLOBAL_History must be string"):
226
+ sofa.verify()
227
+
228
+ # test invalid data for netCDF string variable
229
+ sofa = sf.Sofa("SimpleHeadphoneIR")
230
+ sofa.SourceModel = 1
231
+ with pytest.raises(ValueError, match="- SourceModel must be string"):
232
+ sofa.verify()
233
+
234
+ sofa.SourceModel = np.array(1)
235
+ with pytest.raises(ValueError, match="- SourceModel must be U or S"):
236
+ sofa.verify()
237
+
238
+ # test valid data
239
+ sofa.SourceModel = ["test"]
240
+ sofa.verify()
241
+ sofa.SourceModel = np.array(["test"])
242
+ sofa.verify()
243
+
244
+
245
+ # 3. Verify names of entries --------------------------------------------------
246
+ def test_wrong_name():
247
+
248
+ # attribute with missing variable
249
+ sofa = sf.Sofa("GeneralTF")
250
+ sofa.protected = False
251
+ sofa.IR_Type = "pressure"
252
+ sofa._custom = {"IR_Type": {"default": None,
253
+ "flags": None,
254
+ "dimensions": None,
255
+ "type": "attribute",
256
+ "comment": ""}}
257
+ sofa.protected = True
258
+
259
+ with pytest.raises(ValueError, match="Detected attributes with missing"):
260
+ sofa.verify()
261
+
262
+ # attribute with no underscore
263
+ sofa = sf.Sofa("GeneralTF")
264
+ sofa.protected = False
265
+ sofa.IRType = "pressure"
266
+ sofa._custom = {"IRType": {"default": None,
267
+ "flags": None,
268
+ "dimensions": None,
269
+ "type": "attribute",
270
+ "comment": ""}}
271
+ sofa.protected = True
272
+
273
+ with pytest.raises(ValueError,
274
+ match="Detected attribute names with too many"):
275
+ sofa.verify()
276
+
277
+ # variable with underscore
278
+ sofa = sf.Sofa("GeneralTF")
279
+ sofa.protected = False
280
+ sofa.IR_Data = 1
281
+ sofa._custom = {"IR_Data": {"default": None,
282
+ "flags": None,
283
+ "dimensions": "IM",
284
+ "type": "double",
285
+ "comment": ""}}
286
+ sofa.protected = True
287
+
288
+ with pytest.raises(ValueError,
289
+ match="Detected variable names with too many"):
290
+ sofa.verify()
291
+
292
+ # variables with reserved names
293
+ sofa = sf.Sofa("GeneralTF")
294
+ sofa.add_variable("APIfunk", 1, "double", "I")
295
+ with pytest.raises(ValueError, match="with reserved key words"):
296
+ sofa.verify()
297
+
298
+ sofa = sf.Sofa("GeneralTF")
299
+ sofa.add_variable("PRIVATEtreasure", 1, "double", "I")
300
+ with pytest.raises(ValueError, match="with reserved key words"):
301
+ sofa.verify()
302
+
303
+ sofa = sf.Sofa("GeneralTF")
304
+ sofa.add_variable("GLOBALdata", 1, "double", "I")
305
+ with pytest.raises(ValueError, match="with reserved key words"):
306
+ sofa.verify()
307
+
308
+
309
+ def test_custom_data_name():
310
+ """
311
+ Custom entries can not have the names of data contained in the convention.
312
+ """
313
+
314
+ sofa = sf.Sofa("GeneralTF")
315
+ # add variable Origin, although GLOBAL_Origin exists
316
+ sofa.add_variable("Origin", 1, "double", 'I')
317
+ with pytest.raises(ValueError,
318
+ match="custom variable or attribute with reserved names"):
319
+ sofa.verify()
320
+
321
+
322
+ # 4 + 5. get and verify dimensions of data ------------------------------------
323
+ def test_wrong_shape():
324
+
325
+ # test attribute with wrong shape
326
+ sofa = sf.Sofa("GeneralTF")
327
+ sofa.ListenerPosition = 1
328
+ with pytest.raises(ValueError, match=("- ListenerPosition has shape")):
329
+ sofa.verify()
330
+
331
+
332
+ # 6. check restrictions on the content of SOFA files --------------------------
333
+ def test_rules_error_messages():
334
+ """
335
+ Test the exact error messages raised by violated rules from rules.json. The
336
+ remaining test check only if errors are raised.
337
+ """
338
+
339
+ # wrong value
340
+ sofa = _complete_sofa()
341
+ sofa.GLOBAL_RoomType = "pentagon"
342
+ error = "GLOBAL_RoomType is pentagon but must be free field, reverberant"
343
+ with pytest.raises(ValueError, match=error):
344
+ sofa.verify()
345
+
346
+ # missing general dependency
347
+ sofa = _complete_sofa()
348
+ sofa.delete("ListenerView_Type")
349
+ error = "ListenerView_Type must be given if ListenerView is in SOFA object"
350
+ with pytest.raises(ValueError, match=error):
351
+ sofa.verify()
352
+
353
+ # wrong dimensions
354
+ sofa = sf.Sofa("SimpleFreeFieldHRSOS")
355
+ sofa.Data_SOS = np.zeros((1, 2, 1))
356
+ error = ("Dimension N is of size 1 but must be an integer multiple of 6 "
357
+ "greater 0 if GLOBAL_DataType is SOS")
358
+ with pytest.raises(ValueError, match=error):
359
+ sofa.verify()
360
+
361
+ # missing specific dependency
362
+ sofa = _complete_sofa()
363
+ sofa.GLOBAL_RoomType = "reverberant"
364
+ sofa.delete("GLOBAL_RoomDescription")
365
+ error = ("GLOBAL_RoomDescription must be given "
366
+ "if GLOBAL_RoomType is reverberant")
367
+ with pytest.raises(ValueError, match=error):
368
+ sofa.verify()
369
+
370
+ # wrong value for specific dependency
371
+ sofa = _complete_sofa()
372
+ sofa.ListenerPosition_Type = "spherical"
373
+ error = ("ListenerPosition_Units is metre but must be degree, degree, "
374
+ "metre if ListenerPositionType is spherical")
375
+
376
+
377
+ @pytest.mark.parametrize("file", restricted_values)
378
+ def test_restricted_values(file):
379
+ """Test all rules that restrict data to certain values."""
380
+
381
+ try:
382
+ # load file without verification
383
+ sofa = sf.read_sofa(file, verify=False)
384
+ # test verification
385
+ with pytest.raises(ValueError, match="invalid-value"):
386
+ sofa.verify()
387
+ except ValueError as VE:
388
+ if str(VE) != "Convention 'invalid-value' does not exist":
389
+ raise VE
390
+
391
+
392
+ @pytest.mark.parametrize("file", general_dependencies)
393
+ def test_general_dependencies(file):
394
+ """Test all rules that restrict data to certain values."""
395
+
396
+ # load file without verification
397
+ sofa = sf.read_sofa(file, verify=False)
398
+
399
+ # test verification
400
+ with pytest.raises(ValueError, match="missing|must be given"):
401
+ sofa.verify()
402
+
403
+
404
+ @pytest.mark.parametrize("file", specific_dependencies)
405
+ def test_specific_dependencies(file):
406
+
407
+ # load file without verification
408
+ sofa = sf.read_sofa(file, verify=False)
409
+
410
+ # extract match for error message ('invalid-value' or 'missing')
411
+ match = file[file.rfind("=")+1:file.rfind(".")]
412
+
413
+ if match == "missing":
414
+ match += "|must be given"
415
+
416
+ with pytest.raises(ValueError, match=match):
417
+ sofa.verify()
418
+
419
+
420
+ @pytest.mark.parametrize("file", restricted_dimensions)
421
+ def test_restricted_dimensions(file):
422
+
423
+ # load file without verification
424
+ sofa = sf.read_sofa(file, verify=False)
425
+
426
+ # extract data for error message from filename
427
+ match = f"Dimension {file[-8]} is of size {file[-6]} but must be"
428
+
429
+ # test verification
430
+ with pytest.raises(ValueError, match=match):
431
+ sofa.verify()
432
+
433
+
434
+ # 7. check write only restrictions --------------------------------------------
435
+ def test_read_and_write_mode():
436
+
437
+ # Unit with uppercase is ok when reading but not ok when writing
438
+ sofa = sf.Sofa("SimpleFreeFieldHRIR")
439
+ sofa.ListenerPosition_Units = "Meter"
440
+
441
+ assert sofa.verify(mode="read", issue_handling="return") is None
442
+ with pytest.raises(ValueError, match="lower case letters when writing"):
443
+ sofa.verify(mode="write")
444
+
445
+
446
+ # 8. check deprecations -------------------------------------------------------
447
+ @pytest.mark.parametrize("file", deprecations)
448
+ def test_deprecations(file):
449
+ """
450
+ Test if deprecations raise warnings in read mode and errors in write mode.
451
+ """
452
+
453
+ # read file without verification
454
+ sofa = sf.read_sofa(file, verify=False)
455
+
456
+ # check if deprecated and substitute convention exist in sofar
457
+ conventions = sf.utils._get_conventions("name")
458
+ deprecated = sofa.GLOBAL_SOFAConventions
459
+
460
+ if deprecated not in conventions:
461
+ return
462
+
463
+ # check warnings and errors
464
+ sofa = sf.Sofa(deprecated, verify=False)
465
+
466
+ msg = ("Detected deprecations:\n"
467
+ f"- GLOBAL_SOFAConventions is {deprecated}, which is deprecated. ")
468
+
469
+ with pytest.warns(UserWarning, match=msg):
470
+ sofa.verify(mode="read")
471
+
472
+ with pytest.raises(ValueError, match=msg):
473
+ sofa.verify(mode="write")
474
+
475
+
476
+ def test_preliminary_conventions_version():
477
+ """Test if using a convention version < 1.0 issues a warning."""
478
+
479
+ with pytest.warns(UserWarning, match="Detected preliminary"):
480
+ sf.Sofa("SingleRoomDRIR", version="0.3")