pyNIBS 0.2024.8__py3-none-any.whl → 0.2026.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.
- pynibs/__init__.py +26 -14
- pynibs/coil/__init__.py +6 -0
- pynibs/{coil.py → coil/coil.py} +213 -543
- pynibs/coil/export.py +508 -0
- pynibs/congruence/__init__.py +4 -1
- pynibs/congruence/congruence.py +37 -45
- pynibs/congruence/ext_metrics.py +40 -11
- pynibs/congruence/stimulation_threshold.py +1 -2
- pynibs/expio/Mep.py +120 -370
- pynibs/expio/__init__.py +10 -0
- pynibs/expio/brainsight.py +34 -37
- pynibs/expio/cobot.py +25 -25
- pynibs/expio/exp.py +10 -7
- pynibs/expio/fit_funs.py +3 -0
- pynibs/expio/invesalius.py +70 -0
- pynibs/expio/localite.py +190 -91
- pynibs/expio/neurone.py +139 -0
- pynibs/expio/signal_ced.py +345 -2
- pynibs/expio/visor.py +16 -15
- pynibs/freesurfer.py +34 -33
- pynibs/hdf5_io/hdf5_io.py +149 -132
- pynibs/hdf5_io/xdmf.py +35 -31
- pynibs/mesh/__init__.py +1 -1
- pynibs/mesh/mesh_struct.py +77 -92
- pynibs/mesh/transformations.py +121 -21
- pynibs/mesh/utils.py +191 -99
- pynibs/models/_TMS.py +2 -1
- pynibs/muap.py +1 -2
- pynibs/neuron/__init__.py +10 -0
- pynibs/neuron/models/mep.py +566 -0
- pynibs/neuron/neuron_regression.py +98 -8
- pynibs/optimization/__init__.py +12 -2
- pynibs/optimization/{optimization.py → coil_opt.py} +157 -133
- pynibs/optimization/multichannel.py +1174 -24
- pynibs/optimization/workhorses.py +7 -8
- pynibs/regression/__init__.py +4 -2
- pynibs/regression/dual_node_detection.py +229 -219
- pynibs/regression/regression.py +92 -61
- pynibs/roi/__init__.py +4 -1
- pynibs/roi/roi_structs.py +19 -21
- pynibs/roi/{roi.py → roi_utils.py} +56 -33
- pynibs/subject.py +24 -14
- pynibs/util/__init__.py +20 -4
- pynibs/util/dosing.py +4 -5
- pynibs/util/quality_measures.py +39 -38
- pynibs/util/rotations.py +116 -9
- pynibs/util/{simnibs.py → simnibs_io.py} +29 -19
- pynibs/util/{util.py → utils.py} +20 -22
- pynibs/visualization/para.py +4 -4
- pynibs/visualization/render_3D.py +4 -4
- pynibs-0.2026.1.dist-info/METADATA +105 -0
- pynibs-0.2026.1.dist-info/RECORD +69 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/WHEEL +1 -1
- pyNIBS-0.2024.8.dist-info/METADATA +0 -723
- pyNIBS-0.2024.8.dist-info/RECORD +0 -107
- pynibs/data/configuration_exp0.yaml +0 -59
- pynibs/data/configuration_linear_MEP.yaml +0 -61
- pynibs/data/configuration_linear_RT.yaml +0 -61
- pynibs/data/configuration_sigmoid4.yaml +0 -68
- pynibs/data/network mapping configuration/configuration guide.md +0 -238
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +0 -42
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +0 -43
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +0 -43
- pynibs/data/network mapping configuration/output_documentation.md +0 -185
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +0 -77
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +0 -1281
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +0 -1281
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +0 -19
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +0 -14
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +0 -6373
- pynibs/tests/data/Xdmf.dtd +0 -89
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +0 -145
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +0 -1434
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +0 -47
- pynibs/tests/data/create_subject_testsub.py +0 -332
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +0 -474
- pynibs/tests/test_elements2nodes.py +0 -100
- pynibs/tests/test_hdf5_io/test_xdmf.py +0 -61
- pynibs/tests/test_mesh_transformations.py +0 -123
- pynibs/tests/test_mesh_utils.py +0 -143
- pynibs/tests/test_nnav_imports.py +0 -101
- pynibs/tests/test_quality_measures.py +0 -117
- pynibs/tests/test_regressdata.py +0 -289
- pynibs/tests/test_roi.py +0 -17
- pynibs/tests/test_rotations.py +0 -86
- pynibs/tests/test_subject.py +0 -71
- pynibs/tests/test_util.py +0 -24
- /pynibs/{regression/score_types.py → neuron/models/m1_montbrio.py} +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info/licenses}/LICENSE +0 -0
- {pyNIBS-0.2024.8.dist-info → pynibs-0.2026.1.dist-info}/top_level.txt +0 -0
pynibs/regression/regression.py
CHANGED
|
@@ -3,10 +3,10 @@ import time
|
|
|
3
3
|
import h5py
|
|
4
4
|
import inspect
|
|
5
5
|
import warnings
|
|
6
|
-
import multiprocessing
|
|
7
6
|
import pandas as pd
|
|
8
7
|
import numpy as np
|
|
9
8
|
import scipy.stats
|
|
9
|
+
import multiprocessing
|
|
10
10
|
from lmfit import Model
|
|
11
11
|
from scipy.linalg import svd
|
|
12
12
|
from functools import partial
|
|
@@ -21,11 +21,13 @@ class Element(object):
|
|
|
21
21
|
"""
|
|
22
22
|
Fit Element object class
|
|
23
23
|
"""
|
|
24
|
-
def __init__(self, x, y, ele_id, fun=
|
|
24
|
+
def __init__(self, x, y, ele_id, fun=None, score_type="R2", select_signed_data=False, constants=None,
|
|
25
25
|
**kwargs):
|
|
26
26
|
"""
|
|
27
27
|
Initializes Fit Element instance
|
|
28
28
|
"""
|
|
29
|
+
if fun is None:
|
|
30
|
+
fun=pynibs.expio.sigmoid4
|
|
29
31
|
self.x = x
|
|
30
32
|
self.y = y
|
|
31
33
|
self.y_passed = self.y
|
|
@@ -129,15 +131,28 @@ class Element(object):
|
|
|
129
131
|
|
|
130
132
|
elif self.fun == pynibs.expio.fit_funs.exp0:
|
|
131
133
|
if self.limits == {}:
|
|
132
|
-
self.set_limits({"x0": [0,
|
|
134
|
+
self.set_limits({"x0": [0, 100], "r": [1e-12, 100]})
|
|
133
135
|
else:
|
|
134
136
|
self.set_limits(self.limits)
|
|
135
137
|
if self.init_vals == {}:
|
|
136
138
|
self.set_init_vals({"x0": 10, "r": .1})
|
|
137
139
|
else:
|
|
138
140
|
self.set_init_vals(self.init_vals)
|
|
141
|
+
|
|
142
|
+
if self.random_vals_init_range == {}:
|
|
143
|
+
self.random_vals_init_range = {"x0": [0, 100], "r": [0, .2], "y0": [0, 10]}
|
|
144
|
+
|
|
145
|
+
elif self.fun == pynibs.expio.fit_funs.exp:
|
|
146
|
+
if self.limits == {}:
|
|
147
|
+
self.set_limits({"x0": [0, 100], "r": [1e-12, 100], "y0": [1e-12, 100]})
|
|
148
|
+
else:
|
|
149
|
+
self.set_limits(self.limits)
|
|
150
|
+
if self.init_vals == {}:
|
|
151
|
+
self.set_init_vals({"x0": 10, "r": .1, "y0": 1e-12})
|
|
152
|
+
else:
|
|
153
|
+
self.set_init_vals(self.init_vals)
|
|
139
154
|
if self.random_vals_init_range == {}:
|
|
140
|
-
self.random_vals_init_range = {"x0": [0,
|
|
155
|
+
self.random_vals_init_range = {"x0": [0, 100], "r": [0, 1], "y0": [0, 10]}
|
|
141
156
|
|
|
142
157
|
elif self.fun in [pynibs.expio.fit_funs.sigmoid, pynibs.expio.fit_funs.sigmoid_log,
|
|
143
158
|
pynibs.expio.fit_funs.sigmoid4, pynibs.expio.fit_funs.sigmoid4_log]:
|
|
@@ -148,7 +163,8 @@ class Element(object):
|
|
|
148
163
|
|
|
149
164
|
if self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min <= 0:
|
|
150
165
|
y0 = 1e-3
|
|
151
|
-
elif (self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min > 0) or
|
|
166
|
+
elif ((self.fun == pynibs.expio.fit_funs.sigmoid4_log and y_min > 0) or
|
|
167
|
+
self.fun == pynibs.expio.fit_funs.sigmoid4):
|
|
152
168
|
y0 = y_min
|
|
153
169
|
else:
|
|
154
170
|
y0 = 0
|
|
@@ -237,6 +253,17 @@ class Element(object):
|
|
|
237
253
|
|
|
238
254
|
if "a" not in self.random_vals_init_range:
|
|
239
255
|
self.random_vals_init_range["a"] = [0, 1]
|
|
256
|
+
elif self.fun == pynibs.expio.fit_funs.step:
|
|
257
|
+
if self.limits == {}:
|
|
258
|
+
self.set_limits({"a": [-100, 100]})
|
|
259
|
+
else:
|
|
260
|
+
self.set_limits(self.limits)
|
|
261
|
+
if self.init_vals == {}:
|
|
262
|
+
self.set_init_vals({"a": 40})
|
|
263
|
+
else:
|
|
264
|
+
self.set_init_vals(self.init_vals)
|
|
265
|
+
if self.random_vals_init_range == {}:
|
|
266
|
+
self.random_vals_init_range = {"a": [0, 200]}
|
|
240
267
|
else:
|
|
241
268
|
raise NotImplementedError(self.fun)
|
|
242
269
|
|
|
@@ -271,7 +298,7 @@ class Element(object):
|
|
|
271
298
|
r2_pos = 1 - np.var(residual) / np.var(self.y[mask_pos])
|
|
272
299
|
else:
|
|
273
300
|
p_pos = 1
|
|
274
|
-
r2_pos = np.
|
|
301
|
+
r2_pos = np.nan
|
|
275
302
|
b_pos = -1
|
|
276
303
|
|
|
277
304
|
# fit negative data (perform the regression when we have at least 20 data points to stabilize results)
|
|
@@ -283,11 +310,11 @@ class Element(object):
|
|
|
283
310
|
r2_neg = 1 - np.var(residual) / np.var(self.y[mask_neg])
|
|
284
311
|
else:
|
|
285
312
|
p_neg = 1
|
|
286
|
-
r2_neg = np.
|
|
313
|
+
r2_neg = np.nan
|
|
287
314
|
b_neg = 1
|
|
288
315
|
|
|
289
316
|
# only use data with p < 0.001 and when slopes show an increase of y-data with increasing |x|-data otherwise,
|
|
290
|
-
# set status to False to indicate that the fit is omitted and set score to
|
|
317
|
+
# set status to False to indicate that the fit is omitted and set score to nan
|
|
291
318
|
pos_valid = False
|
|
292
319
|
neg_valid = False
|
|
293
320
|
|
|
@@ -314,7 +341,7 @@ class Element(object):
|
|
|
314
341
|
self.r2_lin = r2_neg
|
|
315
342
|
else:
|
|
316
343
|
self.status = False
|
|
317
|
-
self.score = np.
|
|
344
|
+
self.score = np.nan
|
|
318
345
|
|
|
319
346
|
self.var_y = np.var(self.y)
|
|
320
347
|
self.norm_y = np.linalg.norm(self.y)
|
|
@@ -375,8 +402,8 @@ def workhorse_element_init(ele_id, e_matrix, mep, fun, score_type, select_signed
|
|
|
375
402
|
return element
|
|
376
403
|
|
|
377
404
|
|
|
378
|
-
def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs.expio.fit_funs.sigmoid4, n_cpu=4,
|
|
379
|
-
n_refit=50, zap_idx=None, return_fits=False, score_type="R2",
|
|
405
|
+
def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs.expio.fit_funs.sigmoid4, n_cpu=4,
|
|
406
|
+
con=None, n_refit=50, zap_idx=None, return_fits=False, score_type="R2",
|
|
380
407
|
verbose=False, pool=None, refit_discontinuities=True, select_signed_data=False,
|
|
381
408
|
mp_context="fork", **kwargs):
|
|
382
409
|
"""
|
|
@@ -395,7 +422,7 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
|
|
|
395
422
|
(n_zaps,) List containing the element indices the fit is performed for.
|
|
396
423
|
element_list : list of Element object instances, optional
|
|
397
424
|
[n_ele] pynibs.Element objects ot skip initialization here.
|
|
398
|
-
fun : pynibs.exp.Mep, default: pynibs.sigmoid4
|
|
425
|
+
fun : pynibs.exp.Mep, default: pynibs.expio.sigmoid4
|
|
399
426
|
A pynibs.exp.Mep function (exp0, sigmoid, sigmoid4, ...).
|
|
400
427
|
n_cpu : int, default: 4
|
|
401
428
|
Number of threads, if n_cpu=1 no parallel pool will be opened and all calculations are done in serial.
|
|
@@ -517,19 +544,18 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
|
|
|
517
544
|
) for ele_id in elm_idx_list]
|
|
518
545
|
|
|
519
546
|
else:
|
|
547
|
+
# element_list = tqdm.tqdm(pool.imap(workhorse_init_partial, elm_idx_list), total=len(elm_idx_list))
|
|
520
548
|
element_list = pool.map(workhorse_init_partial, elm_idx_list)
|
|
521
549
|
stop = time.time()
|
|
522
550
|
if verbose:
|
|
523
551
|
print(f"Initialized {len(elm_idx_list)} elements: {stop - start:2.2f} s")
|
|
524
552
|
|
|
525
553
|
# run fit
|
|
526
|
-
####################################################################
|
|
527
|
-
start = time.time()
|
|
528
|
-
|
|
529
554
|
if n_cpu <= 1:
|
|
530
555
|
for ele in element_list:
|
|
531
556
|
ele.run_fit(max_nfev=10 * len(ele.x))
|
|
532
557
|
else:
|
|
558
|
+
# element_list = tqdm.tqdm(pool.imap(workhorse_partial, element_list), total=len(element_list))
|
|
533
559
|
element_list = pool.map(workhorse_partial, element_list)
|
|
534
560
|
|
|
535
561
|
stop = time.time()
|
|
@@ -596,11 +622,11 @@ def regress_data(e_matrix, mep, elm_idx_list=None, element_list=None, fun=pynibs
|
|
|
596
622
|
if len(element_list) > 1:
|
|
597
623
|
score = np.array([ele.score for ele in element_list])
|
|
598
624
|
not_fitted_elms = np.array([idx for idx, ele in enumerate(element_list) if np.isnan(ele.score)])
|
|
599
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=score,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
625
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=score,
|
|
626
|
+
con=con,
|
|
627
|
+
neighbor=True,
|
|
628
|
+
deviation_factor=2,
|
|
629
|
+
not_fitted_elms=not_fitted_elms)
|
|
604
630
|
element_list_disc = [element_list[i_ele] for i_ele in idx_disc]
|
|
605
631
|
|
|
606
632
|
if len(idx_disc) > 0:
|
|
@@ -1145,7 +1171,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
|
|
|
1145
1171
|
param_hints[p]['value'] = constants_elmwise[p][i]
|
|
1146
1172
|
make_params()
|
|
1147
1173
|
|
|
1148
|
-
# perform fit (for some reason, sigmoid4_log function generates
|
|
1174
|
+
# perform fit (for some reason, sigmoid4_log function generates nan, which I can not reproduce)
|
|
1149
1175
|
# I catch the fitting error (ValueError) and set an r2 score of -1, such that it will go into the refit
|
|
1150
1176
|
|
|
1151
1177
|
e_elm = e_matrix[:, elm_idx]
|
|
@@ -1201,7 +1227,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
|
|
|
1201
1227
|
# Electric field matrix
|
|
1202
1228
|
# mep : np.ndarray of float [n_zaps]
|
|
1203
1229
|
# Motor evoked potentials for every stimulation
|
|
1204
|
-
# zap_idx : np.
|
|
1230
|
+
# zap_idx : np.ndarray [n_zaps], default: None
|
|
1205
1231
|
# Indices of zaps the congruence factor is calculated with (default: all)
|
|
1206
1232
|
# fun : str
|
|
1207
1233
|
# A function name of pynibs.exp.Mep (exp0, sigmoid)
|
|
@@ -1306,7 +1332,7 @@ def fit_elms(elm_idx_list, e_matrix, mep, zap_idx=None,
|
|
|
1306
1332
|
#
|
|
1307
1333
|
# make_params()
|
|
1308
1334
|
#
|
|
1309
|
-
# # perform fit (for some reason, sigmoid4_log function generates
|
|
1335
|
+
# # perform fit (for some reason, sigmoid4_log function generates nan, which I can not reproduce)
|
|
1310
1336
|
# # I catch the fitting error (ValueError) and set an r2 score of -1, such that it will go into the refit
|
|
1311
1337
|
#
|
|
1312
1338
|
# try:
|
|
@@ -1439,7 +1465,7 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
|
|
|
1439
1465
|
if verbose:
|
|
1440
1466
|
print(" > Using provided pool object")
|
|
1441
1467
|
if n_cpu > 1:
|
|
1442
|
-
elm_idx_list_chunks = pynibs.compute_chunks(elm_idx_list, n_cpu)
|
|
1468
|
+
elm_idx_list_chunks = pynibs.util.utils.compute_chunks(elm_idx_list, n_cpu)
|
|
1443
1469
|
elif len(elm_idx_list) == 1:
|
|
1444
1470
|
elm_idx_list_chunks = [elm_idx_list]
|
|
1445
1471
|
elif n_cpu == 1:
|
|
@@ -1530,8 +1556,9 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
|
|
|
1530
1556
|
# max_vals_refit[p][idx_re] * (np.random.rand() - 0.5)
|
|
1531
1557
|
|
|
1532
1558
|
if n_cpu > 1:
|
|
1533
|
-
elm_idx_list_chunks_zero = pynibs.
|
|
1534
|
-
|
|
1559
|
+
elm_idx_list_chunks_zero = pynibs.util.utils.compute_chunks(
|
|
1560
|
+
np.array(elm_idx_list)[idx_refit].tolist(),
|
|
1561
|
+
n_cpu)
|
|
1535
1562
|
elif len(elm_idx_list[idx_refit]) == 1:
|
|
1536
1563
|
elm_idx_list_chunks_zero = [elm_idx_list[idx_refit]]
|
|
1537
1564
|
else: # n_cpu == 1:
|
|
@@ -1584,10 +1611,10 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
|
|
|
1584
1611
|
# find discontinuities and refit
|
|
1585
1612
|
##################################################################
|
|
1586
1613
|
if refit_discontinuities and len(c) > 1:
|
|
1587
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c,
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1614
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c,
|
|
1615
|
+
con=con,
|
|
1616
|
+
neighbor=True,
|
|
1617
|
+
deviation_factor=2)
|
|
1591
1618
|
idx_disc = np.array(idx_disc)
|
|
1592
1619
|
|
|
1593
1620
|
if len(idx_disc) > 0:
|
|
@@ -1602,8 +1629,8 @@ def nl_hdf5(elm_idx_list=None, fn_reg_hdf5=None, qoi_path_hdf5=None, e_matrix=No
|
|
|
1602
1629
|
init_vals[p][idx_re] = best_values[idx_ne][p]
|
|
1603
1630
|
|
|
1604
1631
|
if n_cpu > 1:
|
|
1605
|
-
elm_idx_list_chunks_disc = pynibs.compute_chunks(np.array(elm_idx_list)[idx_disc].tolist(),
|
|
1606
|
-
|
|
1632
|
+
elm_idx_list_chunks_disc = pynibs.util.utils.compute_chunks(np.array(elm_idx_list)[idx_disc].tolist(),
|
|
1633
|
+
n_cpu)
|
|
1607
1634
|
elif len(elm_idx_list[idx_disc]) == 1:
|
|
1608
1635
|
elm_idx_list_chunks_disc = [elm_idx_list[idx_disc]]
|
|
1609
1636
|
else: # n_cpu == 1:
|
|
@@ -1845,7 +1872,7 @@ def nl_hdf5_single_core_write(i, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
|
|
|
1845
1872
|
mep : pandas.DataFrame, optional
|
|
1846
1873
|
The motor evoked potential (MEP) data.
|
|
1847
1874
|
fun : function, default: sigmoid4
|
|
1848
|
-
The non-linear optimization function to use (default: pynibs.sigmoid4).
|
|
1875
|
+
The non-linear optimization function to use (default: pynibs.expio.sigmoid4).
|
|
1849
1876
|
con : object, optional
|
|
1850
1877
|
Constraints for optimization, if applicable.
|
|
1851
1878
|
n_refit : int, default: 50
|
|
@@ -1973,8 +2000,8 @@ def get_bad_elms(x, y, method="lstsq", verbose=False):
|
|
|
1973
2000
|
raise NotImplementedError(f'Method {method} unknown.')
|
|
1974
2001
|
|
|
1975
2002
|
if verbose:
|
|
1976
|
-
print(
|
|
1977
|
-
|
|
2003
|
+
print(f"Proc{str_pref}: > {len(idx)}/{x.shape[1]} "
|
|
2004
|
+
f"bad elms removed from fitting ({time.time() - start:2.4} s).")
|
|
1978
2005
|
|
|
1979
2006
|
return idx
|
|
1980
2007
|
|
|
@@ -2086,7 +2113,7 @@ def nl_hdf5_single_core(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
|
|
|
2086
2113
|
# get e field stats
|
|
2087
2114
|
e_stats_dicts = None
|
|
2088
2115
|
if return_e_field_stats:
|
|
2089
|
-
mc = pynibs.mutual_coherence(e_matrix.transpose())
|
|
2116
|
+
mc = pynibs.util.utils.mutual_coherence(e_matrix.transpose())
|
|
2090
2117
|
_, sv_rat, _ = svd(e_matrix)
|
|
2091
2118
|
sv_rat = np.max(sv_rat) / np.min(sv_rat)
|
|
2092
2119
|
e_stats_dicts = {'mc': mc,
|
|
@@ -2189,9 +2216,9 @@ def nl_hdf5_single_core(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=N
|
|
|
2189
2216
|
|
|
2190
2217
|
# find discontinuities and refit
|
|
2191
2218
|
|
|
2192
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con, neighbor=True,
|
|
2193
|
-
|
|
2194
|
-
|
|
2219
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con, neighbor=True,
|
|
2220
|
+
deviation_factor=2, min_val=1e-12,
|
|
2221
|
+
not_fitted_elms=bad_elm_idx)
|
|
2195
2222
|
idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
|
|
2196
2223
|
|
|
2197
2224
|
if len(idx_disc) > 0:
|
|
@@ -2271,7 +2298,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2271
2298
|
(n_zaps, n_ele) Electric field matrix.
|
|
2272
2299
|
mep : np.ndarray of float
|
|
2273
2300
|
(n_zaps) Motor evoked potentials for every stimulation.
|
|
2274
|
-
zap_idx : np.
|
|
2301
|
+
zap_idx : np.ndarray, optional
|
|
2275
2302
|
(n_zaps) Indices of zaps the congruence factor is calculated with (default: all).
|
|
2276
2303
|
fun : function object
|
|
2277
2304
|
A function of pynibs.exp.Mep (exp0, sigmoid).
|
|
@@ -2297,6 +2324,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2297
2324
|
* "R2": R2 score (Model variance / Total variance); linear fits: [0, 1], 1 ... perfect fit
|
|
2298
2325
|
* "SR": Relative standard error of regression (1 - Error 2-norm / Data 2-norm); [-Inf, 1], 1 ... perfect fit
|
|
2299
2326
|
* "rho": Spearman correlation coefficient [-1, 1]; finds any monotonous correlation (0 means no correlation)
|
|
2327
|
+
|
|
2300
2328
|
return_progress : bool, default: False
|
|
2301
2329
|
Return c maps for all steps to allow visualization over e-fitting over timesteps.
|
|
2302
2330
|
smooth_data : bool, default: False
|
|
@@ -2307,18 +2335,21 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2307
2335
|
Returns
|
|
2308
2336
|
-------
|
|
2309
2337
|
dict:
|
|
2338
|
+
A dictionary containing the following elements:
|
|
2310
2339
|
|
|
2311
2340
|
* r2 : np.ndarray of float
|
|
2312
2341
|
(n_roi, n_qoi) R2 for each element in elm_idx_list.
|
|
2313
2342
|
* best_values: list of dict
|
|
2314
|
-
Fit information, if
|
|
2343
|
+
Fit information, if requested.
|
|
2315
2344
|
* stats : dict
|
|
2316
|
-
If
|
|
2317
|
-
|
|
2345
|
+
If requested:
|
|
2346
|
+
|
|
2347
|
+
``mc``: float
|
|
2318
2348
|
Mutual coherence for e fields.
|
|
2319
|
-
|
|
2349
|
+
``sv_rat`` : float
|
|
2320
2350
|
SVD singular value ratio.
|
|
2321
2351
|
* progress : cmaps for each step.
|
|
2352
|
+
|
|
2322
2353
|
"""
|
|
2323
2354
|
starttime = time.time()
|
|
2324
2355
|
|
|
@@ -2380,7 +2411,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2380
2411
|
e_stats_dicts = None
|
|
2381
2412
|
if return_e_field_stats:
|
|
2382
2413
|
stats_start = time.time()
|
|
2383
|
-
mc = pynibs.mutual_coherence(e_matrix.transpose())
|
|
2414
|
+
mc = pynibs.util.utils.mutual_coherence(e_matrix.transpose())
|
|
2384
2415
|
|
|
2385
2416
|
sv_rat = svd(e_matrix, check_finite=False, compute_uv=False)
|
|
2386
2417
|
sv_rat = np.max(sv_rat) / np.min(sv_rat)
|
|
@@ -2436,7 +2467,7 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2436
2467
|
if verbose:
|
|
2437
2468
|
print(f"Proc{str_pref}: > Initial c-factor map done. ({stop - start:2.2f} s)")
|
|
2438
2469
|
|
|
2439
|
-
# pick the best elements and compute their
|
|
2470
|
+
# pick the best elements and compute their neighbors' fits
|
|
2440
2471
|
n_top = int(len(start_sample) * .2)
|
|
2441
2472
|
|
|
2442
2473
|
elms_done = start_sample[np.argpartition(c[start_sample], -n_top)[-n_top:]]
|
|
@@ -2497,13 +2528,13 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2497
2528
|
# refit already here some elements
|
|
2498
2529
|
not_fitted_elms = c[np.array(list(elms_done))] <= 0
|
|
2499
2530
|
# get random neighbor to refit
|
|
2500
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c[np.array(list(elms_done))],
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2531
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c[np.array(list(elms_done))],
|
|
2532
|
+
con=con[np.array(list(elms_done))],
|
|
2533
|
+
neighbor=True,
|
|
2534
|
+
deviation_factor=3, min_val=1e-12,
|
|
2535
|
+
not_fitted_elms=not_fitted_elms,
|
|
2536
|
+
crit='randmax',
|
|
2537
|
+
neigh_style='point')
|
|
2507
2538
|
idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
|
|
2508
2539
|
# set init values from neighbors best values
|
|
2509
2540
|
for p in params:
|
|
@@ -2547,10 +2578,10 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2547
2578
|
|
|
2548
2579
|
# refit the discontinues elemens
|
|
2549
2580
|
not_fitted_elms = c == 0
|
|
2550
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2581
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
|
|
2582
|
+
deviation_factor=3, min_val=1e-12,
|
|
2583
|
+
not_fitted_elms=not_fitted_elms, crit='max',
|
|
2584
|
+
neigh_style='point')
|
|
2554
2585
|
idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
|
|
2555
2586
|
|
|
2556
2587
|
disc_fit_i, last_disc_idx = 1, idx_disc
|
|
@@ -2580,10 +2611,10 @@ def stepdown_approach(zap_idx, elm_idx_list, fn_reg_hdf5=None, qoi_path_hdf5=Non
|
|
|
2580
2611
|
|
|
2581
2612
|
# prepare the next iteration
|
|
2582
2613
|
last_disc_n = len(idx_disc)
|
|
2583
|
-
idx_disc, idx_neighbor = pynibs.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2614
|
+
idx_disc, idx_neighbor = pynibs.mesh.get_indices_discontinuous_data(data=c, con=con[elm_idx_list], neighbor=True,
|
|
2615
|
+
deviation_factor=3, min_val=1e-12,
|
|
2616
|
+
not_fitted_elms=not_fitted_elms, crit='max',
|
|
2617
|
+
neigh_style='point')
|
|
2587
2618
|
idx_disc = np.setdiff1d(idx_disc, bad_elm_idx)
|
|
2588
2619
|
if np.all(last_disc_idx == idx_disc):
|
|
2589
2620
|
break
|
pynibs/roi/__init__.py
CHANGED
pynibs/roi/roi_structs.py
CHANGED
|
@@ -6,7 +6,6 @@ import math
|
|
|
6
6
|
import scipy
|
|
7
7
|
import skimage
|
|
8
8
|
import tqdm
|
|
9
|
-
import trimesh
|
|
10
9
|
import warnings
|
|
11
10
|
import numpy as np
|
|
12
11
|
import nibabel as nib
|
|
@@ -560,7 +559,6 @@ class RegionOfInterestSurface:
|
|
|
560
559
|
"""
|
|
561
560
|
Initialize RegionOfInterestSurface class instance
|
|
562
561
|
"""
|
|
563
|
-
|
|
564
562
|
self.node_coord_up = np.empty(0)
|
|
565
563
|
self.node_coord_mid = np.empty(0)
|
|
566
564
|
self.node_coord_low = np.empty(0)
|
|
@@ -702,7 +700,7 @@ class RegionOfInterestSurface:
|
|
|
702
700
|
tri_center_coord_low : np.ndarray of float [N_roi_tri x 3]
|
|
703
701
|
Coordinates (x, y, z) of triangle center of lower epsilon layer of ROI surface
|
|
704
702
|
fn_mask : str
|
|
705
|
-
Filename for freesurfer mask. If given, this is used instead of
|
|
703
|
+
Filename for freesurfer mask. If given, this is used instead of \\*_ROIs
|
|
706
704
|
X_ROI : list of float
|
|
707
705
|
Region of interest [Xmin, Xmax], whole X range if empty [0,0] or None
|
|
708
706
|
(left - right)
|
|
@@ -720,6 +718,7 @@ class RegionOfInterestSurface:
|
|
|
720
718
|
make_GM_WM_surface(self, gm_surf_fname, wm_surf_fname, delta, X_ROI, Y_ROI, Z_ROI)
|
|
721
719
|
make_GM_WM_surface(self, gm_surf_fname, wm_surf_fname, delta, mask_fn, layer=3)
|
|
722
720
|
"""
|
|
721
|
+
import trimesh
|
|
723
722
|
self.gm_surf_fname = gm_surf_fname
|
|
724
723
|
self.wm_surf_fname = wm_surf_fname
|
|
725
724
|
self.midlayer_surf_fname = midlayer_surf_fname
|
|
@@ -893,12 +892,12 @@ class RegionOfInterestSurface:
|
|
|
893
892
|
mesh.export(roi_fn)
|
|
894
893
|
|
|
895
894
|
roi_refined_fn = os.path.join(self.mesh_folder, "roi", "tmp", "roi_refined.stl")
|
|
896
|
-
pynibs.refine_surface(fn_surf=roi_fn,
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
895
|
+
pynibs.mesh.refine_surface(fn_surf=roi_fn,
|
|
896
|
+
fn_surf_refined=roi_refined_fn,
|
|
897
|
+
center=[0, 0, 0],
|
|
898
|
+
radius=np.inf,
|
|
899
|
+
verbose=True,
|
|
900
|
+
repair=False)
|
|
902
901
|
|
|
903
902
|
roi = trimesh.load(roi_refined_fn)
|
|
904
903
|
con_cropped_reform = roi.faces
|
|
@@ -912,12 +911,12 @@ class RegionOfInterestSurface:
|
|
|
912
911
|
faces=con_cropped_reform)
|
|
913
912
|
mesh.export(roi_fn)
|
|
914
913
|
|
|
915
|
-
pynibs.refine_surface(fn_surf=roi_fn,
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
914
|
+
pynibs.mesh.refine_surface(fn_surf=roi_fn,
|
|
915
|
+
fn_surf_refined=roi_refined_fn,
|
|
916
|
+
center=[0, 0, 0],
|
|
917
|
+
radius=np.inf,
|
|
918
|
+
verbose=True,
|
|
919
|
+
repair=False)
|
|
921
920
|
|
|
922
921
|
roi.append(trimesh.load(roi_refined_fn))
|
|
923
922
|
|
|
@@ -1004,7 +1003,7 @@ class RegionOfInterestSurface:
|
|
|
1004
1003
|
RegionOfInterestSurface.tet_idx_node_coord_mid : np.ndarray
|
|
1005
1004
|
(N_tri) Tetrahedra indices of TetrahedraLinear object instance where the nodes of the middle
|
|
1006
1005
|
surface are.
|
|
1007
|
-
|
|
1006
|
+
"""
|
|
1008
1007
|
# determine tetrahedra indices of triangle center points of upper, middle and lower surface
|
|
1009
1008
|
if self.tri_center_coord_low != [] and self.tri_center_coord_up != []:
|
|
1010
1009
|
points = [self.tri_center_coord_low,
|
|
@@ -1049,7 +1048,7 @@ class RegionOfInterestSurface:
|
|
|
1049
1048
|
ele_idx : np.ndarray of float
|
|
1050
1049
|
[approx. fraction * n_ele] Element indices of the subsampled surface; sorted.
|
|
1051
1050
|
"""
|
|
1052
|
-
|
|
1051
|
+
import trimesh
|
|
1053
1052
|
mesh = trimesh.Trimesh(
|
|
1054
1053
|
vertices=self.node_coord_mid,
|
|
1055
1054
|
faces=self.node_number_list
|
|
@@ -1079,7 +1078,6 @@ class RegionOfInterestSurface:
|
|
|
1079
1078
|
ele_idx : ndarray of float
|
|
1080
1079
|
(n_ele) Element indices of the subsampled surface.
|
|
1081
1080
|
"""
|
|
1082
|
-
|
|
1083
1081
|
# load sphere surface
|
|
1084
1082
|
sphere_coords, sphere_faces = nib.freesurfer.io.read_geometry(fn_sphere)
|
|
1085
1083
|
|
|
@@ -1140,7 +1138,6 @@ class RegionOfInterestVolume:
|
|
|
1140
1138
|
the ROI mesh is not the same as the head mesh, the triangle center of the ROI mesh are always lying in a
|
|
1141
1139
|
tetrahedra of the head mesh (these indices are given in this case).
|
|
1142
1140
|
"""
|
|
1143
|
-
|
|
1144
1141
|
def __init__(self):
|
|
1145
1142
|
""" Initialize RegionOfInterestVolume class instance """
|
|
1146
1143
|
|
|
@@ -1165,10 +1162,12 @@ class RegionOfInterestVolume:
|
|
|
1165
1162
|
|
|
1166
1163
|
- type = 'box': [Xmin, Xmax] (in mm), whole X range if empty [0,0] or None (left - right)
|
|
1167
1164
|
- type = 'sphere': origin [x,y,z]
|
|
1165
|
+
|
|
1168
1166
|
y_roi: list of float
|
|
1169
1167
|
|
|
1170
1168
|
- type = 'box': [Ymin, Ymax] (in mm), whole Y range if empty [0,0] or None (anterior - posterior)
|
|
1171
1169
|
- type = 'sphere': radius (in mm)
|
|
1170
|
+
|
|
1172
1171
|
z_roi: list of float
|
|
1173
1172
|
|
|
1174
1173
|
- type = 'box': [Zmin, Zmax] (in mm), whole Z range if empty [0,0] or None (inferior - superior)
|
|
@@ -1189,12 +1188,11 @@ class RegionOfInterestVolume:
|
|
|
1189
1188
|
lying in
|
|
1190
1189
|
RegionOfInterestVolume.tet_idx_triangle_center : np.ndarray [N_tri_roi]
|
|
1191
1190
|
Tetrahedra indices of TetrahedraLinear object instance where the center points of the ROI triangle are
|
|
1192
|
-
|
|
1191
|
+
If the ROI is directly generated from the msh instance using "make_roi_volume_from_msh", these
|
|
1193
1192
|
indices are the triangle indices of the head mesh since the ROI mesh and the head mesh are overlapping. If
|
|
1194
1193
|
the ROI mesh is not the same as the head mesh, the triangle center of the ROI mesh are always. a
|
|
1195
1194
|
tetrahedra of the head mesh (these indices are given in this case)
|
|
1196
1195
|
"""
|
|
1197
|
-
|
|
1198
1196
|
if volume_type == 'box':
|
|
1199
1197
|
roi_mask_bool = (msh.points[:, 0] > min(x_roi)) & (msh.points[:, 0] < max(x_roi)) & \
|
|
1200
1198
|
(msh.points[:, 1] > min(y_roi)) & (msh.points[:, 1] < max(y_roi)) & \
|