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,127 @@
1
+ from sofar import SofaStream
2
+ from tempfile import TemporaryDirectory
3
+ import pytest
4
+ import netCDF4
5
+ import numpy as np
6
+ import os
7
+ import sofar as sf
8
+
9
+
10
+ def test_sofastream_output(temp_sofa_file):
11
+
12
+ with SofaStream(temp_sofa_file) as file:
13
+ obj = file
14
+ var = file.Data_IR
15
+ var_data = var[:]
16
+ var_attr = file.Data_SamplingRate_Units
17
+ att_data = file.GLOBAL_RoomType
18
+
19
+ # check SofaStream instance
20
+ isinstance(obj, SofaStream)
21
+ # check returned variable type
22
+ isinstance(var, netCDF4._netCDF4.Variable)
23
+ # variable values
24
+ isinstance(var_data, np.ma.core.MaskedArray)
25
+ np.testing.assert_array_equal(var_data.squeeze(),
26
+ np.array([[0, 1], [2, 3], [4, 5]]))
27
+ assert var_attr == 'hertz'
28
+ # attribute values
29
+ isinstance(att_data, str)
30
+ assert att_data == "free field"
31
+
32
+
33
+ def test_sofastream_attribute_error(temp_sofa_file):
34
+
35
+ with SofaStream(temp_sofa_file) as file:
36
+ with pytest.raises(
37
+ AttributeError,
38
+ match="Wrong_Attribute is not contained in SOFA-file"):
39
+ _ = file.Wrong_Attribute
40
+
41
+
42
+ def test_sofastream_inspect(capfd, temp_sofa_file):
43
+
44
+ tempdir = TemporaryDirectory()
45
+ inspect_file = os.path.join(tempdir.name, "info.txt")
46
+
47
+ with SofaStream(temp_sofa_file) as file:
48
+ file.inspect(inspect_file)
49
+ out, _ = capfd.readouterr()
50
+
51
+ sofa = sf.read_sofa(temp_sofa_file)
52
+ sofa.inspect()
53
+ out_sofa, _ = capfd.readouterr()
54
+
55
+ assert out_sofa == out
56
+
57
+ # check text file
58
+ with open(inspect_file, "r") as out_inspect:
59
+ text = out_inspect.readlines()
60
+ assert out == "".join(text)
61
+
62
+
63
+ def test_list_dimensions(capfd, tmp_path_factory):
64
+
65
+ filename = tmp_path_factory.mktemp("data") / "test_sofastream_dim.sofa"
66
+
67
+ # test FIR Data
68
+ sofa = sf.Sofa("GeneralFIR")
69
+ sf.write_sofa(filename, sofa)
70
+ with SofaStream(filename) as file:
71
+ _ = file.list_dimensions
72
+ out, _ = capfd.readouterr()
73
+ assert "N = 1 samples" in out
74
+
75
+ # test TF Data
76
+ sofa = sf.Sofa("GeneralTF")
77
+ sf.write_sofa(filename, sofa)
78
+ with SofaStream(filename) as file:
79
+ _ = file.list_dimensions
80
+ out, _ = capfd.readouterr()
81
+ assert "N = 1 frequencies" in out
82
+
83
+ # test SOS Data
84
+ sofa = sf.Sofa("SimpleFreeFieldHRSOS")
85
+ sf.write_sofa(filename, sofa)
86
+ with SofaStream(filename) as file:
87
+ _ = file.list_dimensions
88
+ out, _ = capfd.readouterr()
89
+ assert "N = 6 SOS coefficients" in out
90
+
91
+ # test non spherical harmonics data
92
+ sofa = sf.Sofa("GeneralFIR")
93
+ sf.write_sofa(filename, sofa)
94
+ with SofaStream(filename) as file:
95
+ _ = file.list_dimensions
96
+ out, _ = capfd.readouterr()
97
+ assert "E = 1 emitter" in out
98
+ assert "R = 1 receiver" in out
99
+
100
+ # test spherical harmonics data
101
+ sofa.EmitterPosition_Type = "spherical harmonics"
102
+ sofa.ReceiverPosition_Type = "spherical harmonics"
103
+ sofa.EmitterPosition_Units = "degree, degree, metre"
104
+ sofa.ReceiverPosition_Units = "degree, degree, metre"
105
+ sf.write_sofa(filename, sofa)
106
+ with SofaStream(filename) as file:
107
+ _ = file.list_dimensions
108
+ out, _ = capfd.readouterr()
109
+ assert "E = 1 emitter spherical harmonics coefficients" in out
110
+ assert "R = 1 receiver spherical harmonics coefficients" in out
111
+
112
+
113
+ def test_get_dimensions(tmp_path_factory):
114
+ """Test getting the size of dimensions."""
115
+ filename = tmp_path_factory.mktemp("data") / "test_sofastream_dim.sofa"
116
+
117
+ # test FIR Data
118
+ sofa = sf.Sofa("GeneralFIR")
119
+ sf.write_sofa(filename, sofa)
120
+
121
+ with SofaStream(filename) as file:
122
+ size = file.get_dimension('N')
123
+ assert size == 1
124
+
125
+ # test wrong dimension error
126
+ with pytest.raises(ValueError, match="Q is not a valid dimension"):
127
+ file.get_dimension("Q")
tests/test_utils.py ADDED
@@ -0,0 +1,250 @@
1
+ import shutil
2
+ import sofar as sf
3
+ from sofar.utils import _get_conventions
4
+ from sofar.update_conventions import _compile_conventions, _check_congruency
5
+ import os
6
+ import json
7
+ from tempfile import TemporaryDirectory
8
+ import pytest
9
+ import numpy as np
10
+ from copy import deepcopy
11
+ import warnings
12
+
13
+
14
+ def test_list_conventions(capfd):
15
+
16
+ # check output to console using pytest default fixture capfd
17
+ sf.list_conventions()
18
+ out, _ = capfd.readouterr()
19
+ assert "Available SOFA conventions:" in out
20
+
21
+
22
+ def test__get_conventions(capfd):
23
+
24
+ # check the return type
25
+ paths = _get_conventions(return_type="path")
26
+ assert isinstance(paths, list)
27
+ assert os.path.isfile(paths[0])
28
+ assert paths[0].endswith(".json")
29
+ out, _ = capfd.readouterr()
30
+ assert out == ""
31
+
32
+ paths = _get_conventions(return_type="path_source")
33
+ assert isinstance(paths, list)
34
+ assert os.path.isfile(paths[0])
35
+ assert paths[0].endswith(".csv")
36
+ out, _ = capfd.readouterr()
37
+ assert out == ""
38
+
39
+ names = _get_conventions(return_type="name")
40
+ assert isinstance(names, list)
41
+ assert not os.path.isfile(names[0])
42
+
43
+ names_versions = _get_conventions(return_type="name_version")
44
+ assert isinstance(names_versions, list)
45
+ assert isinstance(names_versions[0], tuple)
46
+
47
+ with pytest.raises(ValueError, match="return_type None is invalid"):
48
+ _get_conventions(return_type="None")
49
+
50
+
51
+ @pytest.mark.parametrize('branch', ['master', 'development'])
52
+ def test__congruency(capfd, branch):
53
+ """
54
+ Check if conventions from SOFAToolbox and sofaconventions.org are
55
+ identical.
56
+ """
57
+ out, _ = capfd.readouterr()
58
+ _check_congruency(branch=branch)
59
+ out, _ = capfd.readouterr()
60
+ if out != "":
61
+ warnings.warn(out, Warning, stacklevel=1)
62
+
63
+
64
+ def test_update_conventions(capfd):
65
+
66
+ # create temporary directory and copy existing conventions
67
+ temp_dir = TemporaryDirectory()
68
+ work_dir = os.path.join(temp_dir.name, "sofa_conventions", "conventions")
69
+ shutil.copytree(
70
+ os.path.join(
71
+ os.path.dirname(__file__), "..", "sofar", "sofa_conventions"),
72
+ os.path.join(temp_dir.name, "sofa_conventions"))
73
+
74
+ # delete standardized GeneralTF_2.0 to test adding
75
+ os.remove(os.path.join(work_dir, "GeneralTF_2.0.csv"))
76
+ os.remove(os.path.join(work_dir, "GeneralTF_2.0.json"))
77
+ # modify standardized GeneralFIR_1.0 to test updating
78
+ with open(os.path.join(work_dir, "GeneralFIR_1.0.csv"), "w") as fid:
79
+ fid.write("modified")
80
+ # move MultiSpeakerBRIR_0.3 to standardized to test deprecation
81
+ os.rename(
82
+ os.path.join(work_dir, "deprecated", "MultiSpeakerBRIR_0.3.csv"),
83
+ os.path.join(work_dir, "MultiSpeakerBRIR_0.3.csv"))
84
+ os.rename(
85
+ os.path.join(work_dir, "deprecated", "MultiSpeakerBRIR_0.3.json"),
86
+ os.path.join(work_dir, "MultiSpeakerBRIR_0.3.json"))
87
+ # modify deprecated convention to test updating
88
+ with open(os.path.join(work_dir, "deprecated",
89
+ "SimpleFreeFieldHRIR_0.4.csv"), "w") as fid:
90
+ fid.write("modified")
91
+ # delete deprecated GeneralTF_2.0 to test adding
92
+ os.remove(os.path.join(
93
+ work_dir, "deprecated", "SimpleFreeFieldTF_0.4.csv"))
94
+ os.remove(os.path.join(
95
+ work_dir, "deprecated", "SimpleFreeFieldTF_0.4.json"))
96
+
97
+ # first run to test if conventions were updated
98
+ sf.update_conventions(conventions_path=work_dir, assume_yes=True)
99
+ out, _ = capfd.readouterr()
100
+ assert "add convention: GeneralTF_2.0" in out
101
+ assert os.path.isfile(os.path.join(work_dir, "GeneralTF_2.0.csv"))
102
+ assert "update convention: GeneralFIR_1.0" in out
103
+ assert "deprecate convention: MultiSpeakerBRIR_0.3" in out
104
+ assert not os.path.isfile(
105
+ os.path.join(work_dir, "MultiSpeakerBRIR_0.3.csv"))
106
+ assert not os.path.isfile(
107
+ os.path.join(work_dir, "MultiSpeakerBRIR_0.3.json"))
108
+ assert os.path.isfile(
109
+ os.path.join(work_dir, "deprecated", "MultiSpeakerBRIR_0.3.csv"))
110
+ assert os.path.isfile(
111
+ os.path.join(work_dir, "deprecated", "MultiSpeakerBRIR_0.3.json"))
112
+ assert "update deprecated convention: SimpleFreeFieldHRIR_0.4" in out
113
+ assert "add deprecated convention: SimpleFreeFieldTF_0.4" in out
114
+ assert os.path.isfile(
115
+ os.path.join(work_dir, "deprecated", "SimpleFreeFieldTF_0.4.csv"))
116
+ assert os.path.isfile(
117
+ os.path.join(work_dir, "deprecated", "SimpleFreeFieldTF_0.4.json"))
118
+
119
+ # second run to make sure that up to date conventions are not overwritten
120
+ sf.update_conventions(conventions_path=work_dir, assume_yes=True)
121
+ out, _ = capfd.readouterr()
122
+ assert "add" not in out
123
+ assert "update" not in out
124
+ assert "deprecate" not in out
125
+ assert "already up to date" in out
126
+
127
+
128
+ def test__compile_conventions():
129
+ """Test compiling the json conventions from the csv files."""
130
+
131
+ # create temporary directory and copy existing source conventions
132
+ temp_dir = TemporaryDirectory()
133
+ shutil.copytree(
134
+ os.path.join(os.path.dirname(__file__), "..", "sofar",
135
+ "sofa_conventions", "conventions"),
136
+ os.path.join(temp_dir.name, "conventions"))
137
+
138
+ # compile conventions
139
+ _compile_conventions(os.path.join(temp_dir.name, "conventions"))
140
+
141
+ # get list of reference json files
142
+ paths_ref = _get_conventions("path")
143
+ paths_test = _get_conventions(
144
+ "path", os.path.join(temp_dir.name, "conventions"))
145
+
146
+ for path_ref, path_test in zip(paths_ref, paths_test):
147
+
148
+ # load reference conventions
149
+ with open(path_ref, "r") as file:
150
+ ref_data = json.load(file)
151
+
152
+ with open(path_test, "r") as file:
153
+ test_data = json.load(file)
154
+
155
+ # compare conventions
156
+ assert ref_data == test_data
157
+
158
+
159
+ def test_equals_global_parameters():
160
+
161
+ sofa_a = sf.Sofa("SimpleFreeFieldHRIR")
162
+
163
+ # check invalid
164
+ with pytest.raises(ValueError, match="exclude is"):
165
+ sf.equals(sofa_a, sofa_a, exclude="wrong")
166
+
167
+ # check identical objects
168
+ assert sf.equals(sofa_a, sofa_a)
169
+
170
+ # check different number of keys
171
+ sofa_b = deepcopy(sofa_a)
172
+ sofa_b.protected = False
173
+ delattr(sofa_b, "ReceiverPosition")
174
+ sofa_b.protected = True
175
+ with pytest.warns(UserWarning, match="not identical: sofa_a has"):
176
+ is_identical = sf.equals(sofa_a, sofa_b)
177
+ assert not is_identical
178
+
179
+ # check different keys
180
+ sofa_b = deepcopy(sofa_a)
181
+ sofa_b.protected = False
182
+ sofa_b.PositionReceiver = sofa_b.ReceiverPosition
183
+ delattr(sofa_b, "ReceiverPosition")
184
+ sofa_b.protected = True
185
+ with pytest.warns(UserWarning, match="not identical: sofa_a and sofa_b"):
186
+ is_identical = sf.equals(sofa_a, sofa_b)
187
+ assert not is_identical
188
+
189
+ # check mismatching data types
190
+ sofa_b = deepcopy(sofa_a)
191
+ sofa_b.protected = False
192
+ sofa_b._convention["ReceiverPosition"]["type"] = "int"
193
+ sofa_b.protected = True
194
+ with pytest.warns(UserWarning, match="not identical: ReceiverPosition"):
195
+ is_identical = sf.equals(sofa_a, sofa_b)
196
+ assert not is_identical
197
+
198
+ # check exclude GLOBAL attributes
199
+ sofa_b = deepcopy(sofa_a)
200
+ sofa_b.protected = False
201
+ delattr(sofa_b, "GLOBAL_Version")
202
+ sofa_b.protected = True
203
+ is_identical = sf.equals(sofa_a, sofa_b, exclude="GLOBAL")
204
+ assert is_identical
205
+
206
+ # check exclude Date attributes
207
+ sofa_b = deepcopy(sofa_a)
208
+ sofa_b.protected = False
209
+ delattr(sofa_b, "GLOBAL_DateModified")
210
+ sofa_b.protected = True
211
+ is_identical = sf.equals(sofa_a, sofa_b, exclude="DATE")
212
+ assert is_identical
213
+
214
+ # check exclude Date attributes
215
+ sofa_b = deepcopy(sofa_a)
216
+ sofa_b.protected = False
217
+ delattr(sofa_b, "GLOBAL_DateModified")
218
+ sofa_b.protected = True
219
+ is_identical = sf.equals(sofa_a, sofa_b, exclude="ATTR")
220
+ assert is_identical
221
+
222
+
223
+ @pytest.mark.parametrize(("value_a", "value_b", "attribute", "fails"), [
224
+ ("1", "2", "GLOBAL_SOFAConventionsVersion", True),
225
+ ([[1, 2]], [1, 2], "Data_IR", False),
226
+ ([[1, 2]], [1, 3], "Data_IR", True),
227
+ ("HD 650", ["HD 650"], "SourceModel", False),
228
+ ("HD 650", np.array(["HD 650"], dtype="U"), "SourceModel", False),
229
+ ("HD 650", np.array(["HD 650"], dtype="S"), "SourceModel", False),
230
+ ("HD 650", "HD-650", "SourceModel", True),
231
+ ])
232
+ def test_equals_attribute_values(value_a, value_b, attribute, fails):
233
+
234
+ # generate SOFA objects (SimpleHeadphoneIR has string variables)
235
+ sofa_a = sf.Sofa("SimpleHeadphoneIR")
236
+ sofa_a.protected = False
237
+ sofa_b = deepcopy(sofa_a)
238
+
239
+ # set parameters
240
+ setattr(sofa_a, attribute, value_a)
241
+ sofa_a.protected = True
242
+ setattr(sofa_b, attribute, value_b)
243
+ sofa_b.protected = True
244
+
245
+ # compare
246
+ if fails:
247
+ with pytest.warns(UserWarning):
248
+ assert not sf.equals(sofa_a, sofa_b)
249
+ else:
250
+ assert sf.equals(sofa_a, sofa_b)