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/expio/localite.py
CHANGED
|
@@ -20,6 +20,7 @@ def get_tms_elements(xml_paths, verbose=False):
|
|
|
20
20
|
----------
|
|
21
21
|
xml_paths : list of str or str
|
|
22
22
|
Paths to coil0-file and optionally coil1-file. If there is no coil1-file, use empty string.
|
|
23
|
+
Paths to coil0-file and optionally coil1-file. If there is no coil1-file, use empty string.
|
|
23
24
|
verbose : bool, default: False
|
|
24
25
|
Flag indicating verbosity.
|
|
25
26
|
|
|
@@ -99,8 +100,8 @@ def get_tms_elements(xml_paths, verbose=False):
|
|
|
99
100
|
xml_rv = coil0_tm.find('ResponseValues')
|
|
100
101
|
xml_va = xml_rv.findall('Value')
|
|
101
102
|
|
|
102
|
-
# if valueA is
|
|
103
|
-
if xml_va[0].get('response') == '
|
|
103
|
+
# if valueA is nan, compute dI/dt with amplitudeA
|
|
104
|
+
if xml_va[0].get('response') == 'nan':
|
|
104
105
|
current_lst.append(str(round(float(xml_va[2].get('response')) * 1.4461))) # was 1.38
|
|
105
106
|
else:
|
|
106
107
|
current_lst.append(xml_va[0].get('response'))
|
|
@@ -179,7 +180,7 @@ def match_instrument_marker_string(xml_paths, condition_order):
|
|
|
179
180
|
-------
|
|
180
181
|
coil_cond_lst : list of str
|
|
181
182
|
Strings containing the right conditions.
|
|
182
|
-
|
|
183
|
+
drop_idx : list of int
|
|
183
184
|
Indices of trigger markers that were dropped.
|
|
184
185
|
"""
|
|
185
186
|
drop_idx = []
|
|
@@ -568,7 +569,6 @@ def read_triggermarker(fn_xml):
|
|
|
568
569
|
m[im_index1, im_index2, 0] = (float(im_ma.get('data' + str(im_index1) + str(im_index2))))
|
|
569
570
|
|
|
570
571
|
# check if coil position is untracked
|
|
571
|
-
np.eye(4)
|
|
572
572
|
if (m[:, :, 0] == np.eye(4)).all():
|
|
573
573
|
print(f"Untracked coil position found for idx {m_nnav.shape[2]}.")
|
|
574
574
|
m[:3, :4, 0] = np.nan
|
|
@@ -640,7 +640,7 @@ def merge_exp_data_localite(subject, coil_outlier_corr_cond, remove_coil_skin_di
|
|
|
640
640
|
im_lst.append(im_lst[0])
|
|
641
641
|
|
|
642
642
|
# handle coil serial numbers
|
|
643
|
-
coil_sn_lst = pynibs.get_coil_sn_lst(fn_coil)
|
|
643
|
+
coil_sn_lst = pynibs.expio.get_coil_sn_lst(fn_coil)
|
|
644
644
|
|
|
645
645
|
# get TMS pulse onset
|
|
646
646
|
tms_pulse_time = subject.exp[exp_idx]['tms_pulse_time']
|
|
@@ -678,7 +678,7 @@ def merge_exp_data_localite(subject, coil_outlier_corr_cond, remove_coil_skin_di
|
|
|
678
678
|
len_conds.append(len(dict_lst) - len_conds[-1])
|
|
679
679
|
|
|
680
680
|
# convert list of dict to dict of list
|
|
681
|
-
results_dct = pynibs.list2dict(dict_lst)
|
|
681
|
+
results_dct = pynibs.util.utils.list2dict(dict_lst)
|
|
682
682
|
|
|
683
683
|
# check if we have a single pulse TMS experiments where every pulse is one condition
|
|
684
684
|
single_pulse_experiment = np.zeros(len(len_conds))
|
|
@@ -744,25 +744,25 @@ def merge_exp_data_localite(subject, coil_outlier_corr_cond, remove_coil_skin_di
|
|
|
744
744
|
#######################################################
|
|
745
745
|
if coil_outlier_corr_cond:
|
|
746
746
|
print("Removing coil position outliers")
|
|
747
|
-
results_dct = pynibs.coil_outlier_correction_cond(exp=results_dct,
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
747
|
+
results_dct = pynibs.expio.coil_outlier_correction_cond(exp=results_dct,
|
|
748
|
+
outlier_angle=5.,
|
|
749
|
+
outlier_loc=3.,
|
|
750
|
+
fn_exp_out=fn_exp_hdf5)
|
|
751
751
|
|
|
752
752
|
# perform coil <-> head distance correction
|
|
753
753
|
###########################################
|
|
754
754
|
if coil_distance_corr:
|
|
755
755
|
print("Performing coil <-> head distance correction")
|
|
756
|
-
results_dct = pynibs.coil_distance_correction(exp=results_dct,
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
756
|
+
results_dct = pynibs.expio.coil_distance_correction(exp=results_dct,
|
|
757
|
+
fn_geo_hdf5=fn_mesh_hdf5,
|
|
758
|
+
remove_coil_skin_distance_outlier=remove_coil_skin_distance_outlier,
|
|
759
|
+
fn_plot=os.path.split(fn_exp_hdf5)[0])
|
|
760
760
|
|
|
761
761
|
# plot finally used mep data
|
|
762
762
|
############################
|
|
763
763
|
if plot:
|
|
764
764
|
print("Creating MEP plots ...")
|
|
765
|
-
sampling_rate = pynibs.get_mep_sampling_rate(cfs_paths[0])
|
|
765
|
+
sampling_rate = pynibs.expio.get_mep_sampling_rate(cfs_paths[0])
|
|
766
766
|
|
|
767
767
|
# Compute start and stop idx according to sampling rate
|
|
768
768
|
start_mep = int((20 / 1000.) * sampling_rate)
|
|
@@ -848,12 +848,9 @@ def merge_exp_data_localite(subject, coil_outlier_corr_cond, remove_coil_skin_di
|
|
|
848
848
|
df_phys_data_postproc_emg = pd.DataFrame.from_dict(phys_data_postproc_emg)
|
|
849
849
|
|
|
850
850
|
# save in .hdf5 file
|
|
851
|
-
df_stim_data.to_hdf(fn_exp_hdf5, "stim_data")
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
# df_stim_data.columns
|
|
855
|
-
df_phys_data_postproc_emg.to_hdf(fn_exp_hdf5, "phys_data/postproc/EMG")
|
|
856
|
-
df_phys_data_raw_emg.to_hdf(fn_exp_hdf5, "phys_data/raw/EMG")
|
|
851
|
+
df_stim_data.to_hdf(fn_exp_hdf5, key="stim_data")
|
|
852
|
+
df_phys_data_postproc_emg.to_hdf(fn_exp_hdf5, key="phys_data/postproc/EMG")
|
|
853
|
+
df_phys_data_raw_emg.to_hdf(fn_exp_hdf5, key="phys_data/raw/EMG")
|
|
857
854
|
|
|
858
855
|
with h5py.File(fn_exp_hdf5, "a") as f:
|
|
859
856
|
f.create_dataset(name="stim_data/info/channels", data=np.array(channels).astype("|S"))
|
|
@@ -909,7 +906,7 @@ def merge_exp_data_rt(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
909
906
|
im_lst.append(im_lst[0])
|
|
910
907
|
|
|
911
908
|
# handle coil serial numbers
|
|
912
|
-
coil_sn_lst = pynibs.get_coil_sn_lst(fn_coil)
|
|
909
|
+
coil_sn_lst = pynibs.expio.get_coil_sn_lst(fn_coil)
|
|
913
910
|
|
|
914
911
|
# get TMS pulse onset
|
|
915
912
|
tms_pulse_time = subject.exp[exp_idx]['tms_pulse_time']
|
|
@@ -938,7 +935,7 @@ def merge_exp_data_rt(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
938
935
|
len_conds.append(len(dict_lst) - len_conds[-1])
|
|
939
936
|
|
|
940
937
|
# convert list of dict to dict of list
|
|
941
|
-
results_dct = pynibs.list2dict(dict_lst)
|
|
938
|
+
results_dct = pynibs.util.utils.list2dict(dict_lst)
|
|
942
939
|
|
|
943
940
|
# check if we have a single pulse TMS experiments where every pulse is one condition
|
|
944
941
|
single_pulse_experiment = np.zeros(len(len_conds))
|
|
@@ -992,19 +989,19 @@ def merge_exp_data_rt(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
992
989
|
#######################################################
|
|
993
990
|
if coil_outlier_corr_cond:
|
|
994
991
|
print("Removing coil position outliers")
|
|
995
|
-
results_dct = pynibs.coil_outlier_correction_cond(exp=results_dct,
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
992
|
+
results_dct = pynibs.expio.coil_outlier_correction_cond(exp=results_dct,
|
|
993
|
+
outlier_angle=5.,
|
|
994
|
+
outlier_loc=3.,
|
|
995
|
+
fn_exp_out=fn_exp_hdf5)
|
|
999
996
|
|
|
1000
997
|
# perform coil <-> head distance correction
|
|
1001
998
|
###########################################
|
|
1002
999
|
if coil_distance_corr:
|
|
1003
1000
|
print("Performing coil <-> head distance correction")
|
|
1004
|
-
results_dct = pynibs.coil_distance_correction(exp=results_dct,
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1001
|
+
results_dct = pynibs.expio.coil_distance_correction(exp=results_dct,
|
|
1002
|
+
fn_geo_hdf5=fn_mesh_hdf5,
|
|
1003
|
+
remove_coil_skin_distance_outlier=remove_coil_skin_distance_outlier,
|
|
1004
|
+
fn_plot=os.path.split(fn_exp_hdf5)[0])
|
|
1008
1005
|
|
|
1009
1006
|
# plot finally used rt data
|
|
1010
1007
|
############################
|
|
@@ -1018,7 +1015,7 @@ def merge_exp_data_rt(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1018
1015
|
average_y = []
|
|
1019
1016
|
for ind in range(len(y) - avg_window + 1):
|
|
1020
1017
|
average_y.append(np.mean(y[ind:ind + avg_window]))
|
|
1021
|
-
# insert
|
|
1018
|
+
# insert nan to match lengths
|
|
1022
1019
|
for ind in range(avg_window - 1):
|
|
1023
1020
|
average_y.insert(0, np.nan)
|
|
1024
1021
|
|
|
@@ -1058,8 +1055,8 @@ def merge_exp_data_rt(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1058
1055
|
df_behave_data = pd.DataFrame.from_dict(behave_dict)
|
|
1059
1056
|
|
|
1060
1057
|
# save in .hdf5 file
|
|
1061
|
-
df_stim_data.to_hdf(fn_exp_hdf5, "stim_data")
|
|
1062
|
-
df_behave_data.to_hdf(fn_exp_hdf5, "behavioral_data")
|
|
1058
|
+
df_stim_data.to_hdf(fn_exp_hdf5, key="stim_data")
|
|
1059
|
+
df_behave_data.to_hdf(fn_exp_hdf5, key="behavioral_data")
|
|
1063
1060
|
|
|
1064
1061
|
|
|
1065
1062
|
def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance_outlier, coil_distance_corr,
|
|
@@ -1112,7 +1109,7 @@ def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1112
1109
|
im_lst.append(im_lst[0])
|
|
1113
1110
|
|
|
1114
1111
|
# handle coil serial numbers
|
|
1115
|
-
coil_sn_lst = pynibs.get_coil_sn_lst(fn_coil)
|
|
1112
|
+
coil_sn_lst = pynibs.util.utlis.get_coil_sn_lst(fn_coil)
|
|
1116
1113
|
|
|
1117
1114
|
# get TMS pulse onset
|
|
1118
1115
|
tms_pulse_time = subject.exp[exp_idx]['tms_pulse_time']
|
|
@@ -1141,7 +1138,7 @@ def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1141
1138
|
len_conds.append(len(dict_lst) - len_conds[-1])
|
|
1142
1139
|
|
|
1143
1140
|
# convert list of dict to dict of list
|
|
1144
|
-
results_dct = pynibs.list2dict(dict_lst)
|
|
1141
|
+
results_dct = pynibs.util.utils.list2dict(dict_lst)
|
|
1145
1142
|
|
|
1146
1143
|
# check if we have a single pulse TMS experiments where every pulse is one condition
|
|
1147
1144
|
single_pulse_experiment = np.zeros(len(len_conds))
|
|
@@ -1201,19 +1198,19 @@ def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1201
1198
|
#######################################################
|
|
1202
1199
|
if coil_outlier_corr_cond:
|
|
1203
1200
|
print("Removing coil position outliers")
|
|
1204
|
-
results_dct = pynibs.coil_outlier_correction_cond(exp=results_dct,
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1201
|
+
results_dct = pynibs.expio.coil_outlier_correction_cond(exp=results_dct,
|
|
1202
|
+
outlier_angle=5.,
|
|
1203
|
+
outlier_loc=3.,
|
|
1204
|
+
fn_exp_out=fn_exp_hdf5)
|
|
1208
1205
|
|
|
1209
1206
|
# perform coil <-> head distance correction
|
|
1210
1207
|
###########################################
|
|
1211
1208
|
if coil_distance_corr:
|
|
1212
1209
|
print("Performing coil <-> head distance correction")
|
|
1213
|
-
results_dct = pynibs.coil_distance_correction(exp=results_dct,
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1210
|
+
results_dct = pynibs.expio.coil_distance_correction(exp=results_dct,
|
|
1211
|
+
fn_geo_hdf5=fn_mesh_hdf5,
|
|
1212
|
+
remove_coil_skin_distance_outlier=remove_coil_skin_distance_outlier,
|
|
1213
|
+
fn_plot=os.path.split(fn_exp_hdf5)[0])
|
|
1217
1214
|
|
|
1218
1215
|
# plot finally used ft data
|
|
1219
1216
|
############################
|
|
@@ -1227,7 +1224,7 @@ def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1227
1224
|
average_y = []
|
|
1228
1225
|
for ind in range(len(y) - avg_window + 1):
|
|
1229
1226
|
average_y.append(np.mean(y[ind:ind + avg_window]))
|
|
1230
|
-
# insert
|
|
1227
|
+
# insert nan to match lengths
|
|
1231
1228
|
for ind in range(avg_window - 1):
|
|
1232
1229
|
average_y.insert(0, np.nan)
|
|
1233
1230
|
|
|
@@ -1267,8 +1264,8 @@ def merge_exp_data_ft(subject, coil_outlier_corr_cond, remove_coil_skin_distance
|
|
|
1267
1264
|
df_behave_data = pd.DataFrame.from_dict(behave_dict)
|
|
1268
1265
|
|
|
1269
1266
|
# save in .hdf5 file
|
|
1270
|
-
df_stim_data.to_hdf(fn_exp_hdf5, "stim_data")
|
|
1271
|
-
df_behave_data.to_hdf(fn_exp_hdf5, "behavioral_data")
|
|
1267
|
+
df_stim_data.to_hdf(fn_exp_hdf5, key="stim_data")
|
|
1268
|
+
df_behave_data.to_hdf(fn_exp_hdf5, key="behavioral_data")
|
|
1272
1269
|
|
|
1273
1270
|
|
|
1274
1271
|
def match_behave_and_triggermarker(mep_time_lst, xml_paths, bnd_factor=0.99 / 2, isi=None):
|
|
@@ -1301,7 +1298,7 @@ def match_behave_and_triggermarker(mep_time_lst, xml_paths, bnd_factor=0.99 / 2,
|
|
|
1301
1298
|
# _, mep_time_lst_tmp = get_mep_elements(cfs_path, tms_pulse_time)
|
|
1302
1299
|
# mep_time_lst.extend(mep_time_lst_tmp)
|
|
1303
1300
|
|
|
1304
|
-
_, tms_ts_lst, _, tms_idx_invalid =
|
|
1301
|
+
_, tms_ts_lst, _, tms_idx_invalid = get_tms_elements(xml_paths, verbose=True)
|
|
1305
1302
|
|
|
1306
1303
|
# get timestamp difference of mep measurements
|
|
1307
1304
|
if isi is None:
|
|
@@ -1474,7 +1471,7 @@ def combine_nnav_mep(xml_paths, cfs_paths, im, coil_sn,
|
|
|
1474
1471
|
'patient_id'
|
|
1475
1472
|
"""
|
|
1476
1473
|
# get arrays and lists
|
|
1477
|
-
coil_array, ts_tms_lst, current_lst, tms_idx_invalid =
|
|
1474
|
+
coil_array, ts_tms_lst, current_lst, tms_idx_invalid = get_tms_elements(xml_paths, verbose=False)
|
|
1478
1475
|
|
|
1479
1476
|
# get MEP amplitudes from .cfs files
|
|
1480
1477
|
time_mep_lst, mep_latencies = [], []
|
|
@@ -1489,14 +1486,14 @@ def combine_nnav_mep(xml_paths, cfs_paths, im, coil_sn,
|
|
|
1489
1486
|
# calc MEP amplitudes and MEP onset times from .cfs file
|
|
1490
1487
|
p2p_array_tmp, time_mep_lst_tmp, \
|
|
1491
1488
|
mep_raw_data_tmp, mep_filt_data_tmp, \
|
|
1492
|
-
mep_raw_data_time, mep_latency = pynibs.get_mep_elements(mep_fn=cfs_path,
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1489
|
+
mep_raw_data_time, mep_latency = pynibs.expio.signal_ced.get_mep_elements(mep_fn=cfs_path,
|
|
1490
|
+
tms_pulse_time=tms_pulse_time,
|
|
1491
|
+
drop_mep_idx=drop_mep_idx,
|
|
1492
|
+
cfs_data_column=cfs_data_column,
|
|
1493
|
+
channels=channels,
|
|
1494
|
+
plot=plot,
|
|
1495
|
+
start_mep=start_mep,
|
|
1496
|
+
end_mep=end_mep)
|
|
1500
1497
|
|
|
1501
1498
|
# add .cfs onsets from subject object and add onset of last mep from last .cfs file
|
|
1502
1499
|
if mep_onsets is not None:
|
|
@@ -1525,7 +1522,7 @@ def combine_nnav_mep(xml_paths, cfs_paths, im, coil_sn,
|
|
|
1525
1522
|
bnd_factor=0.99 / 2) # 0.99/2
|
|
1526
1523
|
|
|
1527
1524
|
if cfs_paths[0].endswith("cfs"):
|
|
1528
|
-
experiment_date_time = pynibs.get_time_date(cfs_paths)
|
|
1525
|
+
experiment_date_time = pynibs.expio.get_time_date(cfs_paths)
|
|
1529
1526
|
else:
|
|
1530
1527
|
experiment_date_time = "N/A"
|
|
1531
1528
|
|
|
@@ -1543,20 +1540,20 @@ def combine_nnav_mep(xml_paths, cfs_paths, im, coil_sn,
|
|
|
1543
1540
|
else:
|
|
1544
1541
|
# get conditions from instrument markers
|
|
1545
1542
|
if os.path.isfile(im[0]):
|
|
1546
|
-
coil_cond_lst, drop_idx =
|
|
1543
|
+
coil_cond_lst, drop_idx = match_instrument_marker_file(xml_paths, im[0])
|
|
1547
1544
|
else:
|
|
1548
|
-
coil_cond_lst, drop_idx =
|
|
1545
|
+
coil_cond_lst, drop_idx = match_instrument_marker_string(xml_paths, im)
|
|
1549
1546
|
|
|
1550
1547
|
# coordinate transform (for coil_0, coil_1, coil_mean)
|
|
1551
1548
|
for idx in range(coil_array.shape[0]):
|
|
1552
1549
|
# move axis, calculate and move back
|
|
1553
1550
|
m_simnibs = np.moveaxis(coil_array[idx, :, :, :], 0, 2)
|
|
1554
|
-
m_simnibs = pynibs.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1551
|
+
m_simnibs = pynibs.expio.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1552
|
+
fn_conform_nii=nii_conform_path,
|
|
1553
|
+
m_nnav=m_simnibs,
|
|
1554
|
+
nnav_system=nnav_system,
|
|
1555
|
+
mesh_approach=mesh_approach,
|
|
1556
|
+
temp_dir=temp_dir)
|
|
1560
1557
|
|
|
1561
1558
|
coil_array[idx, :, :, :] = np.moveaxis(m_simnibs, 2, 0)
|
|
1562
1559
|
|
|
@@ -1680,7 +1677,7 @@ def combine_nnav_rt(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1680
1677
|
"""
|
|
1681
1678
|
|
|
1682
1679
|
# get arrays and lists
|
|
1683
|
-
coil_array, ts_tms_lst, current_lst, tms_idx_invalid =
|
|
1680
|
+
coil_array, ts_tms_lst, current_lst, tms_idx_invalid = get_tms_elements(xml_paths, verbose=False)
|
|
1684
1681
|
|
|
1685
1682
|
# get RT from .csv files
|
|
1686
1683
|
time_mep_lst = []
|
|
@@ -1692,10 +1689,10 @@ def combine_nnav_rt(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1692
1689
|
|
|
1693
1690
|
for idx, behavior_path in enumerate(behavior_paths):
|
|
1694
1691
|
# get RT and trial onsets from .csv file
|
|
1695
|
-
rt_array_tmp, trial_onset_lst_tmp, mean_isi = pynibs.get_trial_data_from_csv(behavior_fn=behavior_path,
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1692
|
+
rt_array_tmp, trial_onset_lst_tmp, mean_isi = pynibs.expio.get_trial_data_from_csv(behavior_fn=behavior_path,
|
|
1693
|
+
drop_trial_idx=drop_trial_idx,
|
|
1694
|
+
cond=cond,
|
|
1695
|
+
only_corr=True)
|
|
1699
1696
|
|
|
1700
1697
|
time_mep_lst.extend(trial_onset_lst_tmp)
|
|
1701
1698
|
|
|
@@ -1729,20 +1726,20 @@ def combine_nnav_rt(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1729
1726
|
else:
|
|
1730
1727
|
# get conditions from instrument markers
|
|
1731
1728
|
if os.path.isfile(im[0]):
|
|
1732
|
-
coil_cond_lst, drop_idx =
|
|
1729
|
+
coil_cond_lst, drop_idx = match_instrument_marker_file(xml_paths, im[0])
|
|
1733
1730
|
else:
|
|
1734
|
-
coil_cond_lst, drop_idx =
|
|
1731
|
+
coil_cond_lst, drop_idx = match_instrument_marker_string(xml_paths, im)
|
|
1735
1732
|
|
|
1736
1733
|
# coordinate transform (for coil_0, coil_1, coil_mean)
|
|
1737
1734
|
for idx in range(coil_array.shape[0]):
|
|
1738
1735
|
# move axis, calculate and move back
|
|
1739
1736
|
m_simnibs = np.moveaxis(coil_array[idx, :, :, :], 0, 2)
|
|
1740
|
-
m_simnibs = pynibs.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1737
|
+
m_simnibs = pynibs.expio.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1738
|
+
fn_conform_nii=nii_conform_path,
|
|
1739
|
+
m_nnav=m_simnibs,
|
|
1740
|
+
nnav_system=nnav_system,
|
|
1741
|
+
mesh_approach=mesh_approach,
|
|
1742
|
+
temp_dir=temp_dir)
|
|
1746
1743
|
|
|
1747
1744
|
coil_array[idx, :, :, :] = np.moveaxis(m_simnibs, 2, 0)
|
|
1748
1745
|
|
|
@@ -1862,7 +1859,7 @@ def combine_nnav_ft(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1862
1859
|
"""
|
|
1863
1860
|
|
|
1864
1861
|
# get arrays and lists
|
|
1865
|
-
coil_array, ts_tms_lst, current_lst, tms_idx =
|
|
1862
|
+
coil_array, ts_tms_lst, current_lst, tms_idx = get_tms_elements(xml_paths, verbose=False)
|
|
1866
1863
|
|
|
1867
1864
|
# get finger tapping data from .csv files
|
|
1868
1865
|
time_ft_lst = []
|
|
@@ -1874,9 +1871,9 @@ def combine_nnav_ft(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1874
1871
|
|
|
1875
1872
|
for idx, behavior_path in enumerate(behavior_paths):
|
|
1876
1873
|
# get finger tapping data and trial onsets from .csv file
|
|
1877
|
-
ft_array_tmp, trial_onset_lst_tmp, mean_isi =
|
|
1878
|
-
|
|
1879
|
-
|
|
1874
|
+
ft_array_tmp, trial_onset_lst_tmp, mean_isi = get_ft_data_from_csv(behavior_fn=behavior_path,
|
|
1875
|
+
drop_trial_idx=drop_trial_idx,
|
|
1876
|
+
cond=cond)
|
|
1880
1877
|
|
|
1881
1878
|
time_ft_lst.extend(trial_onset_lst_tmp)
|
|
1882
1879
|
|
|
@@ -1910,20 +1907,20 @@ def combine_nnav_ft(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1910
1907
|
else:
|
|
1911
1908
|
# get conditions from instrument markers
|
|
1912
1909
|
if os.path.isfile(im[0]):
|
|
1913
|
-
coil_cond_lst, drop_idx =
|
|
1910
|
+
coil_cond_lst, drop_idx = match_instrument_marker_file(xml_paths, im[0])
|
|
1914
1911
|
else:
|
|
1915
|
-
coil_cond_lst, drop_idx =
|
|
1912
|
+
coil_cond_lst, drop_idx = match_instrument_marker_string(xml_paths, im)
|
|
1916
1913
|
|
|
1917
1914
|
# coordinate transform (for coil_0, coil_1, coil_mean)
|
|
1918
1915
|
for idx in range(coil_array.shape[0]):
|
|
1919
1916
|
# move axis, calculate and move back
|
|
1920
1917
|
m_simnibs = np.moveaxis(coil_array[idx, :, :, :], 0, 2)
|
|
1921
|
-
m_simnibs = pynibs.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1918
|
+
m_simnibs = pynibs.expio.nnav2simnibs(fn_exp_nii=nii_exp_path[0],
|
|
1919
|
+
fn_conform_nii=nii_conform_path,
|
|
1920
|
+
m_nnav=m_simnibs,
|
|
1921
|
+
nnav_system=nnav_system,
|
|
1922
|
+
mesh_approach=mesh_approach,
|
|
1923
|
+
temp_dir=temp_dir)
|
|
1927
1924
|
|
|
1928
1925
|
coil_array[idx, :, :, :] = np.moveaxis(m_simnibs, 2, 0)
|
|
1929
1926
|
|
|
@@ -1985,3 +1982,105 @@ def combine_nnav_ft(xml_paths, behavior_paths, im, coil_sn,
|
|
|
1985
1982
|
idx += 1
|
|
1986
1983
|
|
|
1987
1984
|
return dict_lst
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
def hdf5_from_xml(fn_xml):
|
|
1988
|
+
"""
|
|
1989
|
+
Convert a Localite XML TriggerMarker file to HDF5 and XDMF format.
|
|
1990
|
+
|
|
1991
|
+
Parameters
|
|
1992
|
+
----------
|
|
1993
|
+
fn_xml : str
|
|
1994
|
+
Path to the XML file.
|
|
1995
|
+
|
|
1996
|
+
Returns
|
|
1997
|
+
-------
|
|
1998
|
+
<file> .xdmf/.hdf5 tuple
|
|
1999
|
+
"""
|
|
2000
|
+
assert fn_xml.endswith('.xml'), "File must be an XML file"
|
|
2001
|
+
fn_hdf5 = fn_xml.replace('.xml', '.hdf5')
|
|
2002
|
+
fn_xdmf = fn_xml.replace('.xml', '.xdmf')
|
|
2003
|
+
|
|
2004
|
+
for markertype in ['TriggerMarker', 'InstrumentMarker']:
|
|
2005
|
+
m_nnav = get_marker(fn_xml, markertype=markertype)[0]
|
|
2006
|
+
if len(m_nnav):
|
|
2007
|
+
break
|
|
2008
|
+
assert len(m_nnav), f"Could not find any markers in {fn_xml}"
|
|
2009
|
+
# m_nnav, didt, stim_int, descr, rec_time = read_triggermarker(fn_xml)
|
|
2010
|
+
centers = m_nnav[:,:3, 3]
|
|
2011
|
+
n_coils = centers.shape[0]
|
|
2012
|
+
|
|
2013
|
+
m0 = np.squeeze(m_nnav[:, :3, 0]).T
|
|
2014
|
+
m1 = np.squeeze(m_nnav[:, :3, 1]).T
|
|
2015
|
+
m2 = np.squeeze(m_nnav[:, :3, 2]).T
|
|
2016
|
+
|
|
2017
|
+
with h5py.File(fn_hdf5, 'w') as f_hdf5:
|
|
2018
|
+
f_hdf5.create_dataset('centers', data=centers.astype(np.float64))
|
|
2019
|
+
f_hdf5.create_dataset('m0', data=m0.astype(np.float64))
|
|
2020
|
+
f_hdf5.create_dataset('m1', data=m1.astype(np.float64))
|
|
2021
|
+
f_hdf5.create_dataset('m2', data=m2.astype(np.float64))
|
|
2022
|
+
fn_hdf5 = os.path.split(fn_hdf5)[1]
|
|
2023
|
+
with open(fn_xdmf, 'w') as f_xdmf:
|
|
2024
|
+
f_xdmf.write('<?xml version="1.0"?>\n')
|
|
2025
|
+
f_xdmf.write('<!DOCTYPE Xdmf SYSTEM "Xdmf.dtd" []>\n')
|
|
2026
|
+
f_xdmf.write('<Xdmf Version="2.0" xmlns:xi="http://www.w3.org/2001/XInclude">\n')
|
|
2027
|
+
f_xdmf.write('<Domain>\n')
|
|
2028
|
+
f_xdmf.write('<Grid\nCollectionType="Spatial"\nGridType="Collection"\nName="Collection">\n')
|
|
2029
|
+
|
|
2030
|
+
# one grid for coil dipole nodes...store data hdf5.
|
|
2031
|
+
#######################################################
|
|
2032
|
+
f_xdmf.write('<Grid Name="stimsites" GridType="Uniform">\n')
|
|
2033
|
+
f_xdmf.write(f'<Topology NumberOfElements="{n_coils}" TopologyType="Polyvertex" Name="Tri">\n')
|
|
2034
|
+
f_xdmf.write(f'<DataItem Format="XML" Dimensions="{n_coils} 1">\n')
|
|
2035
|
+
# f.write(hdf5_fn + ':' + path + '/triangle_number_list\n')
|
|
2036
|
+
np.savetxt(f_xdmf, list(range(centers.shape[0])), fmt='%d', delimiter=' ') # 1 2 3 4 ... N_Points
|
|
2037
|
+
f_xdmf.write('</DataItem>\n')
|
|
2038
|
+
f_xdmf.write('</Topology>\n')
|
|
2039
|
+
|
|
2040
|
+
# nodes
|
|
2041
|
+
f_xdmf.write('<Geometry GeometryType="XYZ">\n')
|
|
2042
|
+
f_xdmf.write(f'<DataItem Format="HDF" Dimensions="{n_coils} 3">\n')
|
|
2043
|
+
f_xdmf.write(f'{fn_hdf5}:/centers\n')
|
|
2044
|
+
f_xdmf.write('</DataItem>\n')
|
|
2045
|
+
f_xdmf.write('</Geometry>\n')
|
|
2046
|
+
|
|
2047
|
+
# data
|
|
2048
|
+
# dipole magnitude
|
|
2049
|
+
# the 4 vectors
|
|
2050
|
+
for i in range(3):
|
|
2051
|
+
f_xdmf.write(f'<Attribute Name="dir_{i}" AttributeType="Vector" Center="Cell">\n')
|
|
2052
|
+
f_xdmf.write(f'<DataItem Format="HDF" Dimensions="{n_coils} 3">\n')
|
|
2053
|
+
f_xdmf.write(f"{fn_hdf5}:/m{i}\n")
|
|
2054
|
+
f_xdmf.write('</DataItem>\n')
|
|
2055
|
+
f_xdmf.write('</Attribute>\n\n')
|
|
2056
|
+
|
|
2057
|
+
# angles
|
|
2058
|
+
# f_xdmf.write('<Attribute Name="angles" AttributeType="Scalar" Center="Cell">\n')
|
|
2059
|
+
# f_xdmf.write(f'<DataItem Format="HDF" Dimensions="{centers.shape[0]} 1">\n')
|
|
2060
|
+
# f_xdmf.write(f'{fn_hdf5}:/angles\n')
|
|
2061
|
+
# f_xdmf.write('</DataItem>\n')
|
|
2062
|
+
# f_xdmf.write('</Attribute>\n\n')
|
|
2063
|
+
|
|
2064
|
+
# data
|
|
2065
|
+
# if data_dict is not None:
|
|
2066
|
+
# f_xdmf.write('<Attribute Name="data" AttributeType="Scalar" Center="Cell">\n')
|
|
2067
|
+
# f_xdmf.write('<DataItem Format="HDF" Dimensions="' + str(data.shape[0]) + ' 1">\n')
|
|
2068
|
+
# f_xdmf.write(f'{fn_hdf5}:/data\n')
|
|
2069
|
+
# f_xdmf.write('</DataItem>\n')
|
|
2070
|
+
# f_xdmf.write('</Attribute>\n\n')
|
|
2071
|
+
|
|
2072
|
+
# site idx
|
|
2073
|
+
# f_xdmf.write('<Attribute Name="sites_idx" AttributeType="Scalar" Center="Cell">\n')
|
|
2074
|
+
# f_xdmf.write(f'<DataItem Format="HDF" Dimensions="{centers.shape[0]} 1">\n')
|
|
2075
|
+
# f_xdmf.write(f'{fn_hdf5}:/sites_idx\n')
|
|
2076
|
+
# f_xdmf.write('</DataItem>\n')
|
|
2077
|
+
# f_xdmf.write('</Attribute>\n\n')
|
|
2078
|
+
|
|
2079
|
+
f_xdmf.write('</Grid>\n')
|
|
2080
|
+
# end coil dipole data
|
|
2081
|
+
|
|
2082
|
+
# footer
|
|
2083
|
+
f_xdmf.write('</Grid>\n')
|
|
2084
|
+
f_xdmf.write('</Domain>\n')
|
|
2085
|
+
f_xdmf.write('</Xdmf>\n')
|
|
2086
|
+
f_xdmf.close()
|
pynibs/expio/neurone.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import pynibs
|
|
4
|
+
import datetime
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_mep_elements(session_path, chan_idx=None, recording_id=None,
|
|
9
|
+
tms_port_id=2, mep_start_ms=16, mep_end_ms=40, plot_folder=None, verbose=False):
|
|
10
|
+
"""
|
|
11
|
+
This function calculates the MEP elements for a given session and channel for Neurone Tesla EEG data.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
session_path : str
|
|
16
|
+
Path to the session folder.
|
|
17
|
+
mep_start_ms : int
|
|
18
|
+
Start time of the MEP in ms.
|
|
19
|
+
mep_end_ms : int
|
|
20
|
+
End time of the MEP in ms.
|
|
21
|
+
chan_idx : int, optional
|
|
22
|
+
Channel index. Default: All channels.
|
|
23
|
+
recording_id : int, optional
|
|
24
|
+
Recording ID. Default: All recordings
|
|
25
|
+
tms_port_id : int
|
|
26
|
+
TMS port ID for the pulse trigger.
|
|
27
|
+
verbose : bool
|
|
28
|
+
Print verbose output.
|
|
29
|
+
plot_folder : str, optional
|
|
30
|
+
Folder to store the plots.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
p2p_array : np.ndarray of float
|
|
35
|
+
(N_stim) Peak to peak values of N sweeps.
|
|
36
|
+
time_mep_lst : list of datetime.timedelta
|
|
37
|
+
MEP-timestamps
|
|
38
|
+
mep_raw_data : np.ndarray of float
|
|
39
|
+
(N_channel, N_stim, N_samples) Raw (unfiltered) MEP data.
|
|
40
|
+
mep_filt_data : np.ndarray of float
|
|
41
|
+
(N_channel, N_stim, N_samples) Filtered MEP data (Butterworth lowpass filter).
|
|
42
|
+
time : np.ndarray of float
|
|
43
|
+
(N_samples) Time axis corresponding to MEP data.
|
|
44
|
+
mep_onset_array : np.ndarray of float
|
|
45
|
+
(S_samples) MEP onset after TMS pulse.
|
|
46
|
+
"""
|
|
47
|
+
from neurone_tools import neurone_tools
|
|
48
|
+
# read all recordings in the session
|
|
49
|
+
if recording_id is None:
|
|
50
|
+
subdirs = glob.glob(f"{session_path}/*")
|
|
51
|
+
n_subdirs = len([os.path.split(subdir)[1] for subdir in subdirs if os.path.isdir(subdir)])
|
|
52
|
+
if verbose:
|
|
53
|
+
print(f"Found {n_subdirs} recordings in session {session_path}")
|
|
54
|
+
recording_ids = list(range(1, n_subdirs+1))
|
|
55
|
+
else:
|
|
56
|
+
recording_ids = [recording_id]
|
|
57
|
+
|
|
58
|
+
# read all channels in the recording
|
|
59
|
+
nt = neurone_tools(session_path, recording_ids[0], channels='all')
|
|
60
|
+
if chan_idx is None:
|
|
61
|
+
chan_ids = range(len(nt.get_channel_info()))
|
|
62
|
+
if verbose:
|
|
63
|
+
print(f"Found {len(chan_ids)} channels in recording {recording_id}")
|
|
64
|
+
else:
|
|
65
|
+
chan_ids = [chan_idx]
|
|
66
|
+
|
|
67
|
+
p2p_arr = []
|
|
68
|
+
mep_raw_arr = []
|
|
69
|
+
mep_filt_arr = []
|
|
70
|
+
mep_onset_arr = []
|
|
71
|
+
time_tms_pulse_list_chan = []
|
|
72
|
+
time_axes_chan = []
|
|
73
|
+
|
|
74
|
+
for chan_idx in chan_ids:
|
|
75
|
+
p2p_arr_chan = []
|
|
76
|
+
time_tms_pulse_list_chan = []
|
|
77
|
+
mep_raw_arr_chan = []
|
|
78
|
+
mep_filt_arr_chan = []
|
|
79
|
+
time_axes_chan = []
|
|
80
|
+
mep_onset_arr_chan = []
|
|
81
|
+
|
|
82
|
+
for recording_id in recording_ids:
|
|
83
|
+
nt = neurone_tools(session_path, recording_id, channels='all')
|
|
84
|
+
fsample = nt.get_sampling_rate()
|
|
85
|
+
|
|
86
|
+
if nt.get_channel_info()[chan_idx]['Unit'] == 'µV':
|
|
87
|
+
fac = 1/1000
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f"Unknown unit {nt.get_channel_info()[chan_idx]['Unit']}")
|
|
90
|
+
|
|
91
|
+
# mep_start_sample = int(fsample * mep_start_ms/1000)
|
|
92
|
+
mep_end_sample = int(fsample * mep_end_ms*2/1000)
|
|
93
|
+
|
|
94
|
+
events = nt.load_events()
|
|
95
|
+
tms_pulse_idx = events['start_sample_index'][events['source_port'] == tms_port_id].values
|
|
96
|
+
data, chan_names = nt.load_data()
|
|
97
|
+
if not len(tms_pulse_idx):
|
|
98
|
+
print(f"No TMS pulses found in session {recording_id}")
|
|
99
|
+
continue
|
|
100
|
+
timedelta = datetime.timedelta(milliseconds=0)
|
|
101
|
+
for pulse_id, pulse in enumerate(tms_pulse_idx):
|
|
102
|
+
sweep = data[pulse:int(mep_end_sample + pulse), chan_idx]
|
|
103
|
+
sweep *= fac
|
|
104
|
+
sweep -= sweep.mean()
|
|
105
|
+
|
|
106
|
+
if plot_folder is not None:
|
|
107
|
+
fn_plot = f"{plot_folder}/ses{recording_id:0>2}_chan{chan_idx}_{pulse_id:0>3}_mep.png"
|
|
108
|
+
else:
|
|
109
|
+
fn_plot = None
|
|
110
|
+
p2p, sweep_filt, onset = pynibs.expio.calc_p2p(sweep, tms_pulse_time=0,
|
|
111
|
+
start_mep=mep_start_ms,
|
|
112
|
+
end_mep=mep_end_ms,
|
|
113
|
+
measurement_start_time=0,
|
|
114
|
+
sampling_rate=fsample,
|
|
115
|
+
cutoff_freq=500,
|
|
116
|
+
fn_plot=fn_plot)
|
|
117
|
+
p2p_arr_chan.append(p2p)
|
|
118
|
+
|
|
119
|
+
time_tms_pulse_list_chan.append(timedelta)
|
|
120
|
+
timedelta += datetime.timedelta(milliseconds=1000 * (pulse / fsample))
|
|
121
|
+
mep_filt_arr_chan.append(sweep_filt)
|
|
122
|
+
mep_onset_arr_chan.append(onset)
|
|
123
|
+
mep_raw_arr_chan.append(sweep)
|
|
124
|
+
time_axes_chan.append(np.arange(sweep.shape[0]) / fsample)
|
|
125
|
+
|
|
126
|
+
print(f"channel {chan_idx} session {recording_id: >2}: {(events['source_port'] == tms_port_id).sum()}")
|
|
127
|
+
mep_raw_arr_chan = np.vstack(mep_raw_arr_chan)
|
|
128
|
+
mep_filt_arr_chan = np.vstack(mep_filt_arr_chan)
|
|
129
|
+
|
|
130
|
+
p2p_arr.append(p2p_arr_chan)
|
|
131
|
+
mep_filt_arr.append(mep_filt_arr_chan)
|
|
132
|
+
mep_onset_arr.append(mep_onset_arr_chan)
|
|
133
|
+
mep_raw_arr.append(mep_raw_arr_chan)
|
|
134
|
+
p2p_arr = np.vstack(p2p_arr)
|
|
135
|
+
mep_raw_arr = np.stack(mep_raw_arr, 0)
|
|
136
|
+
mep_filt_arr = np.stack(mep_filt_arr, 0)
|
|
137
|
+
mep_onset_arr = np.vstack(mep_onset_arr)
|
|
138
|
+
|
|
139
|
+
return p2p_arr, time_tms_pulse_list_chan, mep_raw_arr, mep_filt_arr, time_axes_chan, mep_onset_arr
|