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.
- docs/Makefile +20 -0
- docs/api_reference.rst +20 -0
- docs/conf.py +167 -0
- docs/contributing.rst +1 -0
- docs/history.rst +1 -0
- docs/index.rst +4 -0
- docs/make.bat +36 -0
- docs/readme.rst +1 -0
- docs/resources/conventions.py +162 -0
- docs/resources/working_with_sofa_HRIR_lateral.png +0 -0
- docs/resources/working_with_sofa_source_horizontal.png +0 -0
- docs/resources/working_with_sofa_source_lateral.png +0 -0
- docs/sofar.rst +82 -0
- sofar/__init__.py +28 -0
- sofar/io.py +427 -0
- sofar/sofa.py +1835 -0
- sofar/sofa_conventions/VERSION +1 -0
- 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/FreeFieldDirectivityTF_1.1.csv +59 -0
- sofar/sofa_conventions/conventions/FreeFieldDirectivityTF_1.1.json +444 -0
- sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.csv +43 -0
- sofar/sofa_conventions/conventions/FreeFieldHRIR_1.0.json +333 -0
- sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.csv +44 -0
- sofar/sofa_conventions/conventions/FreeFieldHRTF_1.0.json +340 -0
- sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.csv +37 -0
- sofar/sofa_conventions/conventions/GeneralFIR-E_2.0.json +270 -0
- sofar/sofa_conventions/conventions/GeneralFIR_1.0.csv +40 -0
- sofar/sofa_conventions/conventions/GeneralFIR_1.0.json +295 -0
- sofar/sofa_conventions/conventions/GeneralSOS_1.0.csv +40 -0
- sofar/sofa_conventions/conventions/GeneralSOS_1.0.json +306 -0
- sofar/sofa_conventions/conventions/GeneralTF-E_1.0.csv +38 -0
- sofar/sofa_conventions/conventions/GeneralTF-E_1.0.json +277 -0
- sofar/sofa_conventions/conventions/GeneralTF_1.0.csv +38 -0
- sofar/sofa_conventions/conventions/GeneralTF_1.0.json +277 -0
- sofar/sofa_conventions/conventions/GeneralTF_2.0.csv +38 -0
- sofar/sofa_conventions/conventions/GeneralTF_2.0.json +277 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.csv +47 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRIR_1.0.json +369 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.csv +43 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRSOS_1.0.json +349 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.csv +44 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldHRTF_1.0.json +340 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.csv +43 -0
- sofar/sofa_conventions/conventions/SimpleFreeFieldSOS_1.0.json +349 -0
- sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.csv +51 -0
- sofar/sofa_conventions/conventions/SimpleHeadphoneIR_1.0.json +396 -0
- sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.csv +78 -0
- sofar/sofa_conventions/conventions/SingleRoomMIMOSRIR_1.0.json +601 -0
- sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.csv +78 -0
- sofar/sofa_conventions/conventions/SingleRoomSRIR_1.0.json +601 -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/FreeFieldDirectivityTF_1.0.csv +58 -0
- sofar/sofa_conventions/conventions/deprecated/FreeFieldDirectivityTF_1.0.json +437 -0
- sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.csv +37 -0
- sofar/sofa_conventions/conventions/deprecated/GeneralFIRE_1.0.json +270 -0
- sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.csv +48 -0
- sofar/sofa_conventions/conventions/deprecated/MultiSpeakerBRIR_0.3.json +376 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.csv +43 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldHRIR_0.4.json +333 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.csv +44 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_0.4.json +340 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.csv +44 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleFreeFieldTF_1.0.json +340 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.csv +51 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.1.json +396 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.csv +51 -0
- sofar/sofa_conventions/conventions/deprecated/SimpleHeadphoneIR_0.2.json +396 -0
- sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.csv +47 -0
- sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.2.json +360 -0
- sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.csv +47 -0
- sofar/sofa_conventions/conventions/deprecated/SingleRoomDRIR_0.3.json +360 -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 +13 -0
- sofar/sofa_conventions/rules/rules.json +819 -0
- sofar/sofa_conventions/rules/unit_aliases.json +11 -0
- sofar/sofa_conventions/rules/upgrade.json +226 -0
- sofar/sofa_conventions/write_upgrade_rules.py +139 -0
- sofar/sofa_conventions/write_verification_data.py +313 -0
- sofar/sofa_conventions/write_verification_rules.py +356 -0
- sofar/sofastream.py +301 -0
- sofar/update_conventions.py +449 -0
- sofar/utils.py +316 -0
- sofar-1.2.1.dist-info/LICENSE +22 -0
- sofar-1.2.1.dist-info/METADATA +136 -0
- sofar-1.2.1.dist-info/RECORD +105 -0
- sofar-1.2.1.dist-info/WHEEL +5 -0
- sofar-1.2.1.dist-info/top_level.txt +3 -0
- tests/__init__.py +0 -0
- tests/conftest.py +27 -0
- tests/test_deprecations.py +19 -0
- tests/test_io.py +349 -0
- tests/test_sofa.py +353 -0
- tests/test_sofa_upgrade_conventions.py +111 -0
- tests/test_sofa_verify.py +480 -0
- tests/test_sofastream.py +127 -0
- 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")
|