pyNIBS 0.2024.8__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 (107) hide show
  1. pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
  2. pyNIBS-0.2024.8.dist-info/METADATA +723 -0
  3. pyNIBS-0.2024.8.dist-info/RECORD +107 -0
  4. pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
  5. pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
  6. pynibs/__init__.py +34 -0
  7. pynibs/coil.py +1367 -0
  8. pynibs/congruence/__init__.py +15 -0
  9. pynibs/congruence/congruence.py +1108 -0
  10. pynibs/congruence/ext_metrics.py +257 -0
  11. pynibs/congruence/stimulation_threshold.py +318 -0
  12. pynibs/data/configuration_exp0.yaml +59 -0
  13. pynibs/data/configuration_linear_MEP.yaml +61 -0
  14. pynibs/data/configuration_linear_RT.yaml +61 -0
  15. pynibs/data/configuration_sigmoid4.yaml +68 -0
  16. pynibs/data/network mapping configuration/configuration guide.md +238 -0
  17. pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
  18. pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
  19. pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
  20. pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
  21. pynibs/data/network mapping configuration/output_documentation.md +185 -0
  22. pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
  23. pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
  24. pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
  25. pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
  26. pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
  27. pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
  28. pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
  29. pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
  30. pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
  31. pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
  32. pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
  33. pynibs/expio/Mep.py +1518 -0
  34. pynibs/expio/__init__.py +8 -0
  35. pynibs/expio/brainsight.py +979 -0
  36. pynibs/expio/brainvis.py +71 -0
  37. pynibs/expio/cobot.py +239 -0
  38. pynibs/expio/exp.py +1876 -0
  39. pynibs/expio/fit_funs.py +287 -0
  40. pynibs/expio/localite.py +1987 -0
  41. pynibs/expio/signal_ced.py +51 -0
  42. pynibs/expio/visor.py +624 -0
  43. pynibs/freesurfer.py +502 -0
  44. pynibs/hdf5_io/__init__.py +10 -0
  45. pynibs/hdf5_io/hdf5_io.py +1857 -0
  46. pynibs/hdf5_io/xdmf.py +1542 -0
  47. pynibs/mesh/__init__.py +3 -0
  48. pynibs/mesh/mesh_struct.py +1394 -0
  49. pynibs/mesh/transformations.py +866 -0
  50. pynibs/mesh/utils.py +1103 -0
  51. pynibs/models/_TMS.py +211 -0
  52. pynibs/models/__init__.py +0 -0
  53. pynibs/muap.py +392 -0
  54. pynibs/neuron/__init__.py +2 -0
  55. pynibs/neuron/neuron_regression.py +284 -0
  56. pynibs/neuron/util.py +58 -0
  57. pynibs/optimization/__init__.py +5 -0
  58. pynibs/optimization/multichannel.py +278 -0
  59. pynibs/optimization/opt_mep.py +152 -0
  60. pynibs/optimization/optimization.py +1445 -0
  61. pynibs/optimization/workhorses.py +698 -0
  62. pynibs/pckg/__init__.py +0 -0
  63. pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
  64. pynibs/pckg/libeep/__init__.py +0 -0
  65. pynibs/pckg/libeep/pyeep.so +0 -0
  66. pynibs/regression/__init__.py +11 -0
  67. pynibs/regression/dual_node_detection.py +2375 -0
  68. pynibs/regression/regression.py +2984 -0
  69. pynibs/regression/score_types.py +0 -0
  70. pynibs/roi/__init__.py +2 -0
  71. pynibs/roi/roi.py +895 -0
  72. pynibs/roi/roi_structs.py +1233 -0
  73. pynibs/subject.py +1009 -0
  74. pynibs/tensor_scaling.py +144 -0
  75. pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
  76. pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
  77. pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
  78. pynibs/tests/data/Xdmf.dtd +89 -0
  79. pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
  80. pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
  81. pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
  82. pynibs/tests/data/create_subject_testsub.py +332 -0
  83. pynibs/tests/data/data.hdf5 +0 -0
  84. pynibs/tests/data/geo.hdf5 +0 -0
  85. pynibs/tests/test_coil.py +474 -0
  86. pynibs/tests/test_elements2nodes.py +100 -0
  87. pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
  88. pynibs/tests/test_mesh_transformations.py +123 -0
  89. pynibs/tests/test_mesh_utils.py +143 -0
  90. pynibs/tests/test_nnav_imports.py +101 -0
  91. pynibs/tests/test_quality_measures.py +117 -0
  92. pynibs/tests/test_regressdata.py +289 -0
  93. pynibs/tests/test_roi.py +17 -0
  94. pynibs/tests/test_rotations.py +86 -0
  95. pynibs/tests/test_subject.py +71 -0
  96. pynibs/tests/test_util.py +24 -0
  97. pynibs/tms_pulse.py +34 -0
  98. pynibs/util/__init__.py +4 -0
  99. pynibs/util/dosing.py +233 -0
  100. pynibs/util/quality_measures.py +562 -0
  101. pynibs/util/rotations.py +340 -0
  102. pynibs/util/simnibs.py +763 -0
  103. pynibs/util/util.py +727 -0
  104. pynibs/visualization/__init__.py +2 -0
  105. pynibs/visualization/para.py +4372 -0
  106. pynibs/visualization/plot_2D.py +137 -0
  107. pynibs/visualization/render_3D.py +347 -0
@@ -0,0 +1,289 @@
1
+ import os
2
+ import pynibs
3
+ import numpy as np
4
+ import unittest
5
+ import yaml
6
+ from inspect import getmembers, isfunction, getfullargspec
7
+
8
+ import pynibs
9
+
10
+
11
+ class TestRegressData(unittest.TestCase):
12
+ """
13
+ Test pynibs.regress_data()
14
+ """
15
+ np.random.seed(1)
16
+
17
+ n_elm_small = 10
18
+ # n_elm_large = 100000
19
+ n_mep_small = 10
20
+ # n_mep_large = 100
21
+ n_signed = 25 # because function run_select_signed_data needs > 20 data points
22
+
23
+ e_small = np.zeros((n_mep_small, n_elm_small))
24
+ e_small[:] = 1
25
+ e_small *= np.linspace(0.1, 5, n_mep_small)[:, np.newaxis]
26
+ e_small *= np.linspace(0.1, 5, n_elm_small)
27
+
28
+ e_signed = np.zeros((n_signed, n_signed))
29
+ e_signed[:] = 1
30
+ e_signed[0] = -1
31
+ e_signed *= np.linspace(0.1, 5, n_signed)[:, np.newaxis]
32
+ e_signed *= np.linspace(0.1, 5, n_signed)
33
+
34
+ mep_small = np.linspace(0.1, 5, n_mep_small)
35
+
36
+ mep_signed = np.linspace(0.1, 5, n_signed)
37
+
38
+ con_small = np.array([[i, i + 1, i + 2] for i in range(n_elm_small)])
39
+
40
+ def test_raise_assert_if_no_con(self):
41
+ """
42
+ When refit_discontinuities=True, con cannot be None
43
+ """
44
+ with self.assertRaises(AssertionError):
45
+ pynibs.regress_data(self.e_small, self.mep_small)
46
+
47
+ def test_all_default(self):
48
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False)
49
+ assert r2.max() > .9
50
+ assert r2.shape == (self.n_elm_small,)
51
+
52
+ def test_return_fits(self):
53
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
54
+ return_fits=True)
55
+ assert r2.max() > .9
56
+ assert r2.shape == (self.n_elm_small,)
57
+ assert len(fits) == self.n_elm_small
58
+ assert type(fits[0]) == dict
59
+ for k in ['x0', 'r', 'amp', 'y0']:
60
+ assert k in fits[0]
61
+
62
+ def test_elm_idx_list(self):
63
+ elm_idx_list = list(range(0, int(self.n_elm_small / 2)))
64
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
65
+ elm_idx_list=elm_idx_list)
66
+ assert r2.max() > .9
67
+ assert r2.shape == (len(elm_idx_list),)
68
+
69
+ def test_zap_idx(self):
70
+ zap_idx = range(int(self.n_mep_small / 2), self.n_mep_small)
71
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
72
+ zap_idx=zap_idx)
73
+ assert r2.max() > .9
74
+ assert r2.shape == (self.n_elm_small,)
75
+
76
+ def test_element_list_linear(self):
77
+ """
78
+ Gives regress_data() an element_list AND fun=linear
79
+ """
80
+ element_list = [pynibs.Element(x=self.e_small[:, ele_id],
81
+ y=self.mep_small,
82
+ ele_id=ele_id,
83
+ fun=pynibs.expio.fit_funs.linear,
84
+ score_type="R2",
85
+ select_signed_data=False,
86
+ constants=None) for ele_id in range(self.n_elm_small)]
87
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
88
+ element_list=element_list, return_fits=True)
89
+ assert r2.max() > .9
90
+ assert r2.shape == (self.n_elm_small,)
91
+ # let's check if really linear fits have been used
92
+ assert 'm' in fits[0]
93
+ assert 'n' in fits[0]
94
+
95
+ def test_refit_discontinuities(self):
96
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=True, n_cpu=1,
97
+ con=self.con_small)
98
+ assert r2.max() > .9
99
+ assert r2.shape == (self.n_elm_small,)
100
+
101
+ def test_n_refit(self):
102
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
103
+ n_refit=0)
104
+ assert r2.max() > .9
105
+ assert r2.shape == (self.n_elm_small,)
106
+
107
+ def test_workhorses(self): # and verbose
108
+ # Suppress output message in run_select_signed_data()
109
+ # suppress_text = io.StringIO()
110
+ # sys.stdout = suppress_text
111
+
112
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=4,
113
+ n_refit=0, verbose=True)
114
+ assert r2.max() > .9
115
+ assert r2.shape == (self.n_elm_small,)
116
+
117
+ # Enable output messages again
118
+ # sys.stdout = sys.__stdout__
119
+
120
+ def test_score_type(self):
121
+ sr = pynibs.regress_data(self.e_small, self.mep_small, score_type='SR', refit_discontinuities=False, n_cpu=1,
122
+ n_refit=0)
123
+ assert sr.max() > .9
124
+ assert sr.shape == (self.n_elm_small,)
125
+
126
+ rho = pynibs.regress_data(self.e_small, self.mep_small, score_type='rho', refit_discontinuities=False, n_cpu=1,
127
+ n_refit=0)
128
+ assert rho.max() > .9
129
+ assert rho.shape == (self.n_elm_small, 2)
130
+
131
+ with self.assertRaises(NotImplementedError):
132
+ pynibs.regress_data(self.e_small, self.mep_small, score_type='abc', refit_discontinuities=False, n_cpu=1,
133
+ n_refit=0)
134
+
135
+ def test_select_signed_data(self):
136
+ # Suppress output message in run_select_signed_data()
137
+ # suppress_text = io.StringIO()
138
+ # sys.stdout = suppress_text
139
+
140
+ # Test with mostly positive data
141
+ r2_pos = pynibs.regress_data(self.e_signed, self.mep_signed, select_signed_data=True,
142
+ refit_discontinuities=False, n_refit=0)
143
+ assert r2_pos.max() > 0.9
144
+ assert r2_pos.shape == (self.n_signed,)
145
+
146
+ # Test with mostly negative data
147
+ r2_neg = pynibs.regress_data(-self.e_signed, self.mep_signed, select_signed_data=True,
148
+ refit_discontinuities=False, n_refit=0)
149
+ assert r2_neg.max() > 0.9
150
+ assert r2_neg.shape == (self.n_signed,)
151
+
152
+ # Enable output messages again
153
+ # sys.stdout = sys.__stdout__
154
+
155
+ # ***** Testing all regression functions: *****
156
+
157
+ def test_dummy_fun(self):
158
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
159
+ n_refit=0, fun=pynibs.expio.fit_funs.dummy_fun, return_fits=True)
160
+ assert r2.shape == (self.n_elm_small,)
161
+ assert 'a' in fits[0]
162
+
163
+ def test_sigmoid(self):
164
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
165
+ n_refit=0, fun=pynibs.expio.fit_funs.sigmoid, return_fits=True)
166
+ assert r2.max() > 0.90
167
+ assert r2.shape == (self.n_elm_small,)
168
+ assert 'x0' in fits[0]
169
+ assert 'r' in fits[0]
170
+ assert 'amp' in fits[0]
171
+
172
+ def test_sigmoid_negative_values(self):
173
+ r2 = pynibs.regress_data(-self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
174
+ n_refit=0, fun=pynibs.expio.fit_funs.sigmoid)
175
+ assert r2.max() > 0.90
176
+ assert r2.shape == (self.n_elm_small,)
177
+
178
+ def test_sigmoid_log(self):
179
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
180
+ n_refit=0, fun=pynibs.expio.fit_funs.sigmoid_log, return_fits=True)
181
+ assert r2.max() > 0.9
182
+ assert r2.shape == (self.n_elm_small,)
183
+ assert 'x0' in fits[0]
184
+ assert 'r' in fits[0]
185
+ assert 'amp' in fits[0]
186
+
187
+ def test_sigmoid4_log(self):
188
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
189
+ n_refit=0, fun=pynibs.expio.fit_funs.sigmoid4_log, return_fits=True)
190
+ assert r2.max() > 0.9
191
+ assert r2.shape == (self.n_elm_small,)
192
+ assert 'x0' in fits[0]
193
+ assert 'r' in fits[0]
194
+ assert 'amp' in fits[0]
195
+ assert 'y0' in fits[0]
196
+
197
+ def test_exp(self):
198
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
199
+ n_refit=0, fun=pynibs.expio.fit_funs.exp0, return_fits=True)
200
+ assert r2.max() > 0.90
201
+ assert r2.shape == (self.n_elm_small,)
202
+ assert 'x0' in fits[0]
203
+ assert 'r' in fits[0]
204
+
205
+ def test_raise_assert_if_false_fun(self):
206
+ with self.assertRaises(NotImplementedError):
207
+ pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
208
+ n_refit=0, fun=pynibs.expio.fit_funs.linear_log)
209
+
210
+ # ***** Testing use of yaml-configuration files: *****
211
+
212
+ def test_yaml_sigmoid4(self):
213
+ yaml_config = os.path.join(pynibs.__datadir__, 'configuration_sigmoid4.yaml')
214
+ with open(yaml_config, "r") as yamlfile:
215
+ config = yaml.load(yamlfile, Loader=yaml.FullLoader)
216
+ r2 = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
217
+ n_refit=0, fun=pynibs.expio.fit_funs.sigmoid4, **config)
218
+ assert r2.max() > 0.90
219
+ assert r2.shape == (self.n_elm_small,)
220
+
221
+ def test_yaml_linear_MEP(self):
222
+ yaml_config = os.path.join(pynibs.__datadir__, 'configuration_linear_MEP.yaml')
223
+ with open(yaml_config, "r") as yamlfile:
224
+ config = yaml.load(yamlfile, Loader=yaml.FullLoader)
225
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
226
+ n_refit=0, fun=pynibs.expio.fit_funs.linear, return_fits=True, **config)
227
+ assert r2.max() > 0.90
228
+ assert r2.shape == (self.n_elm_small,)
229
+ assert 'm' in fits[0]
230
+ assert 'n' in fits[0]
231
+
232
+ def test_yaml_linear_RT(self):
233
+ yaml_config = os.path.join(pynibs.__datadir__, 'configuration_linear_RT.yaml')
234
+ with open(yaml_config, "r") as yamlfile:
235
+ config = yaml.load(yamlfile, Loader=yaml.FullLoader)
236
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
237
+ n_refit=0, fun=pynibs.expio.fit_funs.linear, return_fits=True, **config)
238
+ assert r2.max() > 0.90
239
+ assert r2.shape == (self.n_elm_small,)
240
+ assert 'm' in fits[0]
241
+ assert 'n' in fits[0]
242
+
243
+ def test_yaml_exp(self):
244
+ yaml_config = os.path.join(pynibs.__datadir__, 'configuration_exp0.yaml')
245
+ with open(yaml_config, "r") as yamlfile:
246
+ config = yaml.load(yamlfile, Loader=yaml.FullLoader)
247
+ r2, fits = pynibs.regress_data(self.e_small, self.mep_small, refit_discontinuities=False, n_cpu=1,
248
+ n_refit=0, fun=pynibs.expio.fit_funs.exp0, return_fits=True, **config)
249
+ assert r2.max() > 0.90
250
+ assert r2.shape == (self.n_elm_small,)
251
+ assert 'x0' in fits[0]
252
+ assert 'r' in fits[0]
253
+
254
+
255
+ class TestSingleFit(unittest.TestCase):
256
+
257
+ def setUp(self):
258
+ self.x = x = np.array(range(1, 100))
259
+ self.args = {'a': 1,
260
+ 'x0': 0,
261
+ 'r': .2,
262
+ 'amp': 5,
263
+ 'y0': 0,
264
+ 'm': 5,
265
+ 'n': 1}
266
+
267
+ def test_single_fit(self):
268
+ for fun_id, fun in getmembers(pynibs.expio.fit_funs):
269
+ if isfunction(fun):
270
+ if fun_id == 'dummy_fun':
271
+ continue
272
+
273
+ fun_params = getfullargspec(fun)[0]
274
+ print(f"Testing {fun_id} with: {fun_params}")
275
+ fun_args = {k: self.args[k] for k in fun_params if k != 'x'}
276
+ y = fun(self.x, **fun_args)
277
+
278
+ fit = pynibs.single_fit(self.x, y, fun)
279
+ for k in fun_params:
280
+ if k != 'x':
281
+ print(
282
+ f"\t{k}: org: {self.args[k]} | "
283
+ f"fit: {fit.params[k].value} | "
284
+ f"{np.isclose(self.args[k], fit.params[k].value, atol=.1)}")
285
+ print(f"R2: {fit.rsquared}")
286
+
287
+
288
+ if __name__ == '__main__':
289
+ unittest.main()
@@ -0,0 +1,17 @@
1
+ import unittest
2
+ import pynibs
3
+
4
+
5
+ class TestRegIonOfInterestSurface(unittest.TestCase):
6
+
7
+ def test_regionofinterestsurface(self):
8
+ roi = pynibs.RegionOfInterestSurface()
9
+ for field in ["X_ROI", "Y_ROI", "Z_ROI"]:
10
+ assert hasattr(roi, field)
11
+ for field in ["x_roi", "x_roi", "x_roi"]:
12
+ assert not hasattr(roi, field)
13
+
14
+
15
+ class TestRegionOfInterestVolume(unittest.TestCase):
16
+ def test_regionofinterestvolume(self):
17
+ roi = pynibs.RegionOfInterestVolume()
@@ -0,0 +1,86 @@
1
+ import unittest
2
+ import pynibs
3
+ import numpy as np
4
+ from scipy.spatial.transform import Rotation as R
5
+
6
+
7
+ class TestUtilsRotationsbases2rotmat(unittest.TestCase):
8
+ mat = np.array(([1., 0., 0., 0.],
9
+ [0., 1., 0., 0.],
10
+ [0., 0., 1., 0.],
11
+ [10., 20., 30., 1.])).T
12
+
13
+ def test_bases2rotmat(self):
14
+ v1 = np.array(([0, 0, 1], [0, 1, 0], [1, 0, 0]))
15
+ v2 = np.array(([1, 0, 0], [0, 1, 0], [0, 0, 1]))
16
+ res = R.from_matrix(pynibs.bases2rotmat(v1, v2)).as_euler('xyz', degrees=True)
17
+ assert (res == np.array([180., 0., 180.])).all()
18
+
19
+ def test_roatate_matsimnibs_euler_no_rot(self):
20
+ for axis in ['x', 'y', 'z']:
21
+ assert (self.mat == pynibs.rotate_matsimnibs_euler(axis=axis, angle=0, matsimnibs=self.mat,
22
+ metric='rad')).all()
23
+ assert (self.mat == pynibs.rotate_matsimnibs_euler(axis=axis, angle=0, matsimnibs=self.mat,
24
+ metric='deg')).all()
25
+
26
+ assert np.allclose(
27
+ pynibs.rotate_matsimnibs_euler(axis=axis, angle=2 * np.pi, matsimnibs=self.mat, metric='rad'),
28
+ self.mat)
29
+ assert np.allclose(pynibs.rotate_matsimnibs_euler(axis=axis, angle=360, matsimnibs=self.mat, metric='deg'),
30
+ self.mat)
31
+
32
+ def test_identity(self):
33
+ # Test if the function returns identity matrix for the same input
34
+ identity = np.eye(3)
35
+ result = pynibs.bases2rotmat(identity, identity)
36
+ np.testing.assert_array_almost_equal(result, identity)
37
+
38
+ def test_rotation(self):
39
+ # Test if the function correctly calculates rotation matrix
40
+ rot = R.from_euler('xyz', [80, 0, 0], degrees=True)
41
+ base = np.eye(3)
42
+ target = rot.as_matrix()
43
+ result = pynibs.bases2rotmat(target, base)
44
+ np.testing.assert_array_almost_equal(result, target)
45
+
46
+ def test_rotate_matsimnibs_euler_180(self):
47
+ assert np.allclose(pynibs.rotate_matsimnibs_euler(axis='x', angle=np.pi, matsimnibs=self.mat, metric='rad'),
48
+ np.array([[1., 0., 0., 10.],
49
+ [0., -1., -0., 20.],
50
+ [0., 0., -1., 30.],
51
+ [0., 0., 0., 1.]])
52
+ )
53
+ assert np.allclose(pynibs.rotate_matsimnibs_euler(axis='y', angle=np.pi, matsimnibs=self.mat, metric='rad'),
54
+ np.array([[-1., 0., 0., 10.],
55
+ [0., 1., 0., 20.],
56
+ [-0., 0., -1., 30.],
57
+ [0., 0., 0., 1.]]))
58
+ assert np.allclose(pynibs.rotate_matsimnibs_euler(axis='z', angle=np.pi, matsimnibs=self.mat, metric='rad'),
59
+ np.array([[-1., -0., 0., 10.],
60
+ [0., -1., 0., 20.],
61
+ [0., 0., 1., 30.],
62
+ [0., 0., 0., 1.]]))
63
+
64
+
65
+ class TestUtilsRotationsrotmatfromvecs(unittest.TestCase):
66
+ def test_rotmat_from_vecs(self):
67
+ vec1 = np.array([1, 0, 0])
68
+ vec2 = np.array([0, 1, 0])
69
+ expected_result = R.from_euler('z', 90, degrees=True)
70
+ result = pynibs.rotmat_from_vecs(vec1, vec2)
71
+ np.testing.assert_array_almost_equal(result.as_matrix(), expected_result.as_matrix())
72
+
73
+
74
+ class TestUtilsRotationsrotMattoEulerAngles(unittest.TestCase):
75
+ def test_rotation_matrix_to_euler_angles(self):
76
+ # Define some Euler angles
77
+ euler_angles = np.array([np.pi / 2, np.pi / 3, np.pi / 4])
78
+
79
+ # Convert the Euler angles to a rotation matrix
80
+ rotation_matrix = pynibs.euler_angles_to_rotation_matrix(euler_angles)
81
+
82
+ # Pass the rotation matrix to the function and get the output Euler angles
83
+ output_euler_angles = pynibs.rotation_matrix_to_euler_angles(rotation_matrix)
84
+
85
+ # Check that the output Euler angles match the original Euler angles
86
+ np.testing.assert_array_almost_equal(output_euler_angles, euler_angles)
@@ -0,0 +1,71 @@
1
+ import unittest
2
+ import tempfile
3
+ import pynibs
4
+ import sys
5
+ import shutil
6
+ import os
7
+
8
+
9
+ class TestSubject(unittest.TestCase):
10
+ """
11
+ Test pynibs.subject.* functions.
12
+ """
13
+ subject_id = 'testsub'
14
+ mesh_names = ['charm_refm1_sm', 'headreco', 'headreco_refined_m1']
15
+ create_subject_fn = "create_subject_testsub.py"
16
+ create_subject_fn_org = os.path.join(pynibs.__testdatadir__, create_subject_fn)
17
+
18
+ # get temporary folder and move create_subject.py theree
19
+ folder = tempfile.TemporaryDirectory()
20
+ fn_create_subject = os.path.join(folder.name, create_subject_fn)
21
+ fn_subject_object = os.path.join(folder.name, f"{subject_id}.hdf5")
22
+ shutil.copyfile(create_subject_fn_org, fn_create_subject)
23
+
24
+ def test_01_create_subject(self):
25
+ cmd = f"{sys.executable} {self.fn_create_subject}"
26
+ assert os.system(cmd) == 0, f"Creating subject failed"
27
+
28
+ def test_02_load_subject(self):
29
+ subject = pynibs.load_subject(self.fn_subject_object)
30
+ for mesh_id in self.mesh_names:
31
+ assert mesh_id in subject.mesh.keys(), f"{mesh_id} not found in {self.fn_subject_object}"
32
+
33
+ def test_03_load_subject_h5(self):
34
+ no_hdf5_suffix = os.path.join(self.folder.name, 'test.h5')
35
+ shutil.copyfile(self.fn_subject_object, no_hdf5_suffix)
36
+ with self.assertRaises(NotImplementedError):
37
+ pynibs.load_subject(no_hdf5_suffix)
38
+ subject = pynibs.load_subject(no_hdf5_suffix, filetype='hdf5')
39
+ for mesh_id in self.mesh_names:
40
+ assert mesh_id in subject.mesh.keys(), f"{mesh_id} not found in {self.fn_subject_object}"
41
+
42
+ def test_04_save_subject(self):
43
+ subject = pynibs.load_subject(self.fn_subject_object)
44
+ fn_new = self.fn_subject_object.replace('.hdf5', '_new.hdf5')
45
+ pynibs.save_subject(subject_id=self.subject_id,
46
+ subject_folder=self.folder.name,
47
+ fname=fn_new,
48
+ mri_dict=subject.mri,
49
+ mesh_dict=subject.mesh,
50
+ roi_dict=subject.roi,
51
+ exp_dict=subject.exp,
52
+ ps_dict=subject.ps,
53
+ overwrite=True,
54
+ check_file_exist=False,
55
+ verbose=False)
56
+ subject = pynibs.load_subject(fn_new)
57
+ for mesh_id in self.mesh_names:
58
+ assert mesh_id in subject.mesh.keys(), f"{mesh_id} not found in {self.fn_subject_object}"
59
+
60
+ # def test_06_add_stuff(self):
61
+ # subject = pynibs.load_subject(self.fn_subject_object)
62
+ # mri = subject.mri[0]
63
+ # subject.add_mri_info(mri)
64
+ #
65
+ # exp = {'TMS_localite': subject.exp['TMS_localite']}
66
+ # exp['fn_coil'] = exp['TMS_localite']['fn_coil'][0][0]
67
+ # subject.add_experiment_info(exp)
68
+
69
+ def test_05_print_subject(self):
70
+ subject = pynibs.load_subject(self.fn_subject_object)
71
+ print(subject)
@@ -0,0 +1,24 @@
1
+ import pynibs
2
+ import unittest
3
+ from scipy.spatial.transform import Rotation as rot
4
+ import numpy as np
5
+
6
+
7
+ class TestIntersectionVecPlan(unittest.TestCase):
8
+ def test_intersection(self):
9
+ r = rot.from_rotvec([45, 0, 0], degrees=True)
10
+
11
+ # Define plane
12
+ plane_n = np.array([0, 0, 1])
13
+ plane_p = np.array([0, 0, 20]) # Any point on the plane
14
+
15
+ # Define ray
16
+ ray_dir = r.apply([0, 0, 1]) # np.array([0, -1, -1])
17
+ ray_origin = np.array([0, 0, 0]) # Any point along the ray
18
+
19
+ ret = pynibs.intersection_vec_plan(ray_dir, ray_origin, plane_n, plane_p, eps=1e-6)
20
+ assert np.all(np.isclose(np.array([0., -20., 20.]), np.array(ret)))
21
+
22
+
23
+ if __name__ == '__main__':
24
+ unittest.main()
pynibs/tms_pulse.py ADDED
@@ -0,0 +1,34 @@
1
+ import numpy as np
2
+
3
+
4
+ def biphasic_pulse(t, R=0.0338, L=15.5*1e-6, C=193.6*1e-6, alpha=1089.8, f=2900):
5
+ """
6
+ Returns normalized single biphasic pulse waveform of electric field (first derivative of coil current)
7
+
8
+ Parameters
9
+ ----------
10
+ t: ndarray of float [n_t]
11
+ Time array in seconds
12
+ R: float, optional, default: 0.0338 Ohm
13
+ Resistance of coil in (Ohm)
14
+ L: float, optional, default: 15.5*1e-6 H
15
+ Inductance of coil in (H)
16
+ C: float, optional, default: 193.6*1e-6
17
+ Capacitance of coil in (F)
18
+ alpha: float, optional, default: 1089.8 1/s
19
+ Damping coefficient in (1/s)
20
+ f: float, optional, default: 2900 Hz
21
+ Frequency in (Hz)
22
+
23
+ Returns
24
+ -------
25
+ e: ndarray of float [n_t]
26
+ Normalized electric field time course (can be scaled with electric field)
27
+ """
28
+
29
+ omega = 2 * np.pi * f
30
+ i = 1/(omega*L) * np.exp(-alpha*t) * np.sin(omega*t)
31
+ e = np.gradient(i)
32
+ e = e/np.max(e)
33
+
34
+ return i, e
@@ -0,0 +1,4 @@
1
+ from .util import *
2
+ from .quality_measures import *
3
+ from .rotations import *
4
+ from .simnibs import *