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
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import pynibs
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.special import logsumexp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Workhorse(object):
|
|
7
|
+
|
|
8
|
+
def __init__(self, t, aMN, renshaw):
|
|
9
|
+
self.t = t
|
|
10
|
+
self.dt = t[1] - t[0]
|
|
11
|
+
self.aMN = aMN
|
|
12
|
+
self.renshaw = renshaw
|
|
13
|
+
|
|
14
|
+
def run(self, input):
|
|
15
|
+
"""
|
|
16
|
+
Workhorse to run alpha motor neurons in parallel
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
input: ndarray of float [n_t]
|
|
21
|
+
Input signal to alpha motor neurons
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
alpha_motor_neurons: list of AlphaMotorNeuron instances
|
|
26
|
+
Alpha motor neurons with calculated membrane potential (Vm) and spike times (spike_times)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# iterate over each time step
|
|
30
|
+
feedback = np.zeros(len(self.t))
|
|
31
|
+
delay = round(self.renshaw.delay / self.dt)
|
|
32
|
+
|
|
33
|
+
for i, t_ in enumerate(self.t):
|
|
34
|
+
feedback[i] = self.aMN.step(input, feedback, t_, i)
|
|
35
|
+
|
|
36
|
+
if i + delay < len(self.t) - 1:
|
|
37
|
+
feedback[i + 1] = self.renshaw.step(feedback, t_, i) # TODO consider delay and length of signal
|
|
38
|
+
|
|
39
|
+
return self.aMN
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Model(object):
|
|
43
|
+
"""
|
|
44
|
+
Spinal cord and Muscle model containing alpha motor neurons and Renshaw cells.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, T, dt, N_MU=100, fn_muaps=None, **kwargs):
|
|
48
|
+
"""
|
|
49
|
+
Initialize spinal cord and Muscle model
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
T : Total Time of obesrvation
|
|
54
|
+
dt : time step
|
|
55
|
+
N_MU: Number of Motor units
|
|
56
|
+
fn_muaps : Path to muap shapes
|
|
57
|
+
**kwars: constants of neurons
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# setup parameters and state variables
|
|
61
|
+
self.N_MU = N_MU
|
|
62
|
+
self.T = T
|
|
63
|
+
self.dt = dt
|
|
64
|
+
self.t = np.arange(0, T + dt, dt)
|
|
65
|
+
|
|
66
|
+
# Neuron
|
|
67
|
+
self.Vm_thr_array = kwargs['n_thr'] + kwargs['m_thr'] * np.arange(self.N_MU) # thresholds of alpha MNs
|
|
68
|
+
self.t_refrac = kwargs['t_refrac']
|
|
69
|
+
self.tau_mem = kwargs['tau_mem']
|
|
70
|
+
self.Vm_rest = kwargs['Vm_rest']
|
|
71
|
+
self.Rm = kwargs['Rm']
|
|
72
|
+
|
|
73
|
+
# Renshaw cell (inhibitory)
|
|
74
|
+
self.slope_renshaw = kwargs.get('slope_renshaw')
|
|
75
|
+
self.Vm_thr_renshaw = kwargs.get('Vm_thr_renshaw')
|
|
76
|
+
self.Vm_rest_renshaw = kwargs.get('Vm_rest_renshaw')
|
|
77
|
+
|
|
78
|
+
# Acetyl synapse (excitatory)
|
|
79
|
+
self.tau_syn_decay_ac = kwargs.get('tau_syn_decay_ac')
|
|
80
|
+
self.tau_syn_rise_ac = kwargs.get('tau_syn_rise_ac')
|
|
81
|
+
self.tau_syn_decay_fast_ac = kwargs.get('tau_syn_decay_fast_ac')
|
|
82
|
+
self.tau_syn_decay_slow_ac = kwargs.get('tau_syn_decay_slow_ac')
|
|
83
|
+
self.a_syn_decay_fast_ac = kwargs.get('a_syn_decay_fast_ac')
|
|
84
|
+
self.a_syn_decay_slow_ac = kwargs.get('a_syn_decay_slow_ac')
|
|
85
|
+
|
|
86
|
+
# Glycin Synapse (inhibitory)
|
|
87
|
+
self.tau_syn_decay_gl = kwargs.get('tau_syn_decay_gl')
|
|
88
|
+
self.tau_syn_rise_gl = kwargs.get('tau_syn_rise_gl')
|
|
89
|
+
self.tau_syn_decay_fast_gl = kwargs.get('tau_syn_decay_fast_gl')
|
|
90
|
+
self.tau_syn_decay_slow_gl = kwargs.get('tau_syn_decay_slow_gl')
|
|
91
|
+
self.a_syn_decay_fast_gl = kwargs.get('a_syn_decay_fast_gl')
|
|
92
|
+
self.a_syn_decay_slow_gl = kwargs.get('a_syn_decay_slow_gl')
|
|
93
|
+
|
|
94
|
+
# Connectivity
|
|
95
|
+
self.connectivity_cortex_alpha = kwargs.get('connectivity_cortex_alpha')
|
|
96
|
+
self.connectivity_renshaw_alpha = kwargs.get('connectivity_renshaw_alpha')
|
|
97
|
+
self.connectivity_alpha_renshaw = kwargs.get('connectivity_alpha_renshaw')
|
|
98
|
+
|
|
99
|
+
# Load MUAPS
|
|
100
|
+
self.fn_muaps = fn_muaps
|
|
101
|
+
self.muaps = []
|
|
102
|
+
self.t_muap = []
|
|
103
|
+
self.muaps, t_muap = pynibs.load_muaps(self.fn_muaps)
|
|
104
|
+
|
|
105
|
+
# Workhorse Arrays
|
|
106
|
+
self.alpha_motor_neurons = []
|
|
107
|
+
self.renshaw_cells = []
|
|
108
|
+
self.motor_units = []
|
|
109
|
+
|
|
110
|
+
# Output Values
|
|
111
|
+
self.mep = None
|
|
112
|
+
self.mep_p2p = None
|
|
113
|
+
|
|
114
|
+
# Synapse (Cortex -> Alpha Motor Neurons)
|
|
115
|
+
acetyl_synapse_1 = Synapse(dt=self.dt, T=self.T,
|
|
116
|
+
tau_syn_decay_fast=self.tau_syn_decay_fast_ac,
|
|
117
|
+
tau_syn_decay_slow=self.tau_syn_decay_slow_ac,
|
|
118
|
+
tau_syn_rise=self.tau_syn_rise_ac,
|
|
119
|
+
a_syn_decay_slow=self.a_syn_decay_slow_ac,
|
|
120
|
+
a_syn_decay_fast=self.a_syn_decay_fast_ac,
|
|
121
|
+
connectivity=kwargs.get('connectivity_cortex_alpha'),
|
|
122
|
+
N=self.N_MU)
|
|
123
|
+
|
|
124
|
+
# Synapse (Alpha Motor Neurons -> Renshaw Cells)
|
|
125
|
+
acetyl_synapse_2 = Synapse(dt=self.dt, T=self.T,
|
|
126
|
+
tau_syn_decay_fast=self.tau_syn_decay_fast_ac,
|
|
127
|
+
tau_syn_decay_slow=self.tau_syn_decay_slow_ac,
|
|
128
|
+
tau_syn_rise=self.tau_syn_rise_ac,
|
|
129
|
+
a_syn_decay_slow=self.a_syn_decay_slow_ac,
|
|
130
|
+
a_syn_decay_fast=self.a_syn_decay_fast_ac,
|
|
131
|
+
connectivity=self.connectivity_alpha_renshaw,
|
|
132
|
+
N=self.N_MU)
|
|
133
|
+
|
|
134
|
+
# Synapse (Renshaw Cells -> Alpha Motor Neurons)
|
|
135
|
+
glycine_synapse = Synapse(dt=self.dt, T=self.T,
|
|
136
|
+
tau_syn_decay_fast=self.tau_syn_decay_fast_gl,
|
|
137
|
+
tau_syn_decay_slow=self.tau_syn_decay_slow_gl,
|
|
138
|
+
tau_syn_rise=self.tau_syn_rise_gl,
|
|
139
|
+
a_syn_decay_slow=self.a_syn_decay_slow_gl,
|
|
140
|
+
a_syn_decay_fast=self.a_syn_decay_fast_gl,
|
|
141
|
+
connectivity=self.connectivity_renshaw_alpha,
|
|
142
|
+
N=self.N_MU)
|
|
143
|
+
|
|
144
|
+
self.aMNs = AlphaMotorNeuron(tau_mem=self.tau_mem, Vm_thr=self.Vm_thr_array, Vm_rest=self.Vm_rest, Rm=self.Rm,
|
|
145
|
+
t_refrac=self.t_refrac, T=self.T, dt=self.dt,
|
|
146
|
+
synapses=[acetyl_synapse_1, glycine_synapse], N=self.N_MU)
|
|
147
|
+
|
|
148
|
+
self.renshaw = RenshawCell(tau_mem=self.tau_mem, Vm_thr=self.Vm_thr_renshaw, Vm_rest=self.Vm_rest, Rm=self.Rm,
|
|
149
|
+
T=self.T, dt=self.dt, delay=0,
|
|
150
|
+
synapses=[acetyl_synapse_2],
|
|
151
|
+
slope=self.slope_renshaw) # TODO own Vm_thr for optimisation
|
|
152
|
+
|
|
153
|
+
for i in range(self.N_MU):
|
|
154
|
+
self.motor_units.append(MotorUnit(muap=self.muaps[:, i], t=self.t))
|
|
155
|
+
|
|
156
|
+
def run(self, inp):
|
|
157
|
+
"""
|
|
158
|
+
Starts the parallel processing.
|
|
159
|
+
Creates Model can all workhorse process
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
inp: ndarray of float [n_t]
|
|
164
|
+
Input signal to alpha motor neurons
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
workhorse = Workhorse(self.t, self.aMNs, self.renshaw)
|
|
168
|
+
self.alpha_motor_neurons = workhorse.run(inp)
|
|
169
|
+
|
|
170
|
+
muscle = Muscle(self.alpha_motor_neurons, self.motor_units)
|
|
171
|
+
self.mep = muscle.get_mep()
|
|
172
|
+
self.mep_p2p = np.max(self.mep) - np.min(self.mep)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Synapse(object):
|
|
176
|
+
"""
|
|
177
|
+
Synapse (conductance-based)
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self, T, dt, N, tau_syn_decay=None, tau_syn_rise=None,
|
|
181
|
+
tau_syn_decay_fast=None, tau_syn_decay_slow=None, a_syn_decay_fast=None, a_syn_decay_slow=None,
|
|
182
|
+
connectivity=1):
|
|
183
|
+
"""
|
|
184
|
+
Initializes Synapse instance
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
T : float
|
|
189
|
+
Total simulation time in ms
|
|
190
|
+
dt : float
|
|
191
|
+
Time-step to solve DEQ
|
|
192
|
+
tau_syn_decay : float
|
|
193
|
+
Decay time constant of synapse in ms (set this parameter, if only one decay time constant is present and set
|
|
194
|
+
tau_syn_decay_fast=None, tau_syn_decay_slow=None)
|
|
195
|
+
tau_syn_rise : float
|
|
196
|
+
Rise time of synapse in ms
|
|
197
|
+
tau_syn_decay_fast : float
|
|
198
|
+
Decay time constant of synapse of fast component in ms
|
|
199
|
+
tau_syn_decay_slow : float
|
|
200
|
+
Decay time constant of synapse of slow component in ms
|
|
201
|
+
a_syn_decay_fast : float
|
|
202
|
+
Proportion of fast decay time constant
|
|
203
|
+
a_syn_decay_slow : float
|
|
204
|
+
Proportion of slow decay time constant
|
|
205
|
+
connectivity : float
|
|
206
|
+
Scaling factor for synaptic strength
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
self.tau_syn_decay = tau_syn_decay
|
|
210
|
+
self.T = T
|
|
211
|
+
self.dt = dt
|
|
212
|
+
self.connectivity = connectivity
|
|
213
|
+
self.tau_syn_rise = tau_syn_rise
|
|
214
|
+
self.tau_syn_decay_fast = tau_syn_decay_fast
|
|
215
|
+
self.tau_syn_decay_slow = tau_syn_decay_slow
|
|
216
|
+
self.a_syn_decay_fast = a_syn_decay_fast
|
|
217
|
+
self.a_syn_decay_slow = a_syn_decay_slow
|
|
218
|
+
self.synapse_single_decay = False
|
|
219
|
+
self.synapse_double_decay = False
|
|
220
|
+
self.synapse_single_decay_rise = False
|
|
221
|
+
self.synapse_double_decay_rise = False
|
|
222
|
+
|
|
223
|
+
if tau_syn_decay is not None and (
|
|
224
|
+
tau_syn_decay_slow is None and tau_syn_decay_fast is None and tau_syn_rise is None):
|
|
225
|
+
self.synapse_single_decay = True
|
|
226
|
+
|
|
227
|
+
if (tau_syn_decay_slow is not None and tau_syn_decay_fast is not None) and (
|
|
228
|
+
tau_syn_rise is None and tau_syn_decay is None):
|
|
229
|
+
self.synapse_double_decay = True
|
|
230
|
+
|
|
231
|
+
if (tau_syn_decay_slow is not None and tau_syn_decay_fast is not None and tau_syn_rise is not None) and (
|
|
232
|
+
tau_syn_decay is None):
|
|
233
|
+
self.synapse_double_decay_rise = True
|
|
234
|
+
|
|
235
|
+
if (tau_syn_rise is not None and tau_syn_decay is not None) and (
|
|
236
|
+
tau_syn_decay_slow is None and tau_syn_decay_fast is None):
|
|
237
|
+
self.synapse_single_decay_rise = True
|
|
238
|
+
|
|
239
|
+
if self.synapse_single_decay + self.synapse_double_decay + self.synapse_single_decay_rise + \
|
|
240
|
+
self.synapse_double_decay_rise != 1:
|
|
241
|
+
raise AssertionError("Please check assignment of time constants...")
|
|
242
|
+
|
|
243
|
+
# initialize arrays
|
|
244
|
+
self.t = np.linspace(0, self.T, int(self.T / self.dt + 1))
|
|
245
|
+
self.g_f = np.zeros((N, len(self.t)))
|
|
246
|
+
self.x_f = np.zeros((N, len(self.t)))
|
|
247
|
+
self.g_s = np.zeros((N, len(self.t)))
|
|
248
|
+
self.x_s = np.zeros((N, len(self.t)))
|
|
249
|
+
self.x = np.zeros((N, len(self.t)))
|
|
250
|
+
self.g_tot = np.zeros((N, len(self.t)))
|
|
251
|
+
|
|
252
|
+
def step(self, inp, i):
|
|
253
|
+
"""
|
|
254
|
+
Solves the DEQ and computes synapse conductivity
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
inp: ndarray of float [n_t]
|
|
259
|
+
Input signal
|
|
260
|
+
i: int
|
|
261
|
+
Current index
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
# double exponential kernel with rise time and slow + fast decay times
|
|
265
|
+
if self.synapse_double_decay_rise:
|
|
266
|
+
self.g_f[:, i] = self.g_f[:, i - 1] + self.x_f[:, i - 1] * self.dt
|
|
267
|
+
self.x_f[:, i] = self.x_f[:, i - 1] + (
|
|
268
|
+
(inp[i] - self.x_f[:, i - 1]) * (self.tau_syn_rise + self.tau_syn_decay_fast) -
|
|
269
|
+
self.g_f[:, i - 1]) / (self.tau_syn_rise * self.tau_syn_decay_fast) * self.dt
|
|
270
|
+
self.g_s[:, i] = self.g_s[:, i - 1] + self.x_s[:, i - 1] * self.dt
|
|
271
|
+
self.x_s[:, i] = self.x_s[:, i - 1] + (
|
|
272
|
+
(inp[i] - self.x_s[:, i - 1]) * (self.tau_syn_rise + self.tau_syn_decay_slow) -
|
|
273
|
+
self.g_s[:, i - 1]) / (self.tau_syn_rise * self.tau_syn_decay_slow) * self.dt
|
|
274
|
+
self.g_tot[:, i] = self.a_syn_decay_fast * self.g_f[:, i] + self.a_syn_decay_slow * self.g_s[:, i]
|
|
275
|
+
|
|
276
|
+
# single exponential kernel with slow + fast decay times
|
|
277
|
+
if self.synapse_double_decay:
|
|
278
|
+
self.g_s[:, i] = self.g_s[:, i - 1] + ((0 - self.g_s[:, i - 1]) / self.tau_syn_decay_slow) * self.dt + inp[
|
|
279
|
+
i]
|
|
280
|
+
self.g_f[:, i] = self.g_f[:, i - 1] + ((0 - self.g_f[:, i - 1]) / self.tau_syn_decay_fast) * self.dt + inp[
|
|
281
|
+
i]
|
|
282
|
+
self.g_tot[:, i] = self.a_syn_decay_fast * self.g_f[:, i] + self.a_syn_decay_slow * self.g_s[:, i]
|
|
283
|
+
|
|
284
|
+
# single exponential kernel with one decay time
|
|
285
|
+
if self.synapse_single_decay:
|
|
286
|
+
self.g_tot[:, i] = self.g_tot[:, i - 1] + ((0 - self.g_tot[:, i - 1]) / self.tau_syn_decay) * self.dt + inp[
|
|
287
|
+
i]
|
|
288
|
+
|
|
289
|
+
# double exponential kernel with rise time and one decay time
|
|
290
|
+
if self.synapse_single_decay_rise:
|
|
291
|
+
self.g_tot[:, i] = self.g_tot[:, i - 1] + self.x[:, i - 1] * self.dt
|
|
292
|
+
self.x[:, i] = self.x[:, i - 1] + ((inp[i] - self.x[:, i - 1]) * (self.tau_syn_rise + self.tau_syn_decay) -
|
|
293
|
+
self.g_tot[:, i - 1]) / (
|
|
294
|
+
self.tau_syn_rise * self.tau_syn_decay) * self.dt
|
|
295
|
+
|
|
296
|
+
return self.g_tot[:, i] * self.connectivity # Apply weight to output
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class RenshawCell(object):
|
|
300
|
+
"""
|
|
301
|
+
Renshaw Cell (Leaky integrate and fire)
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, tau_mem, Vm_thr, Vm_rest, Rm, T, dt, delay, synapses, slope):
|
|
305
|
+
"""
|
|
306
|
+
Initializes Renshaw Cell instance
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
tau_mem : float
|
|
311
|
+
Time constant of cell membrane in ms
|
|
312
|
+
Vm_thr : float
|
|
313
|
+
Firing threshold in mV
|
|
314
|
+
Vm_rest : float
|
|
315
|
+
Resting potential
|
|
316
|
+
Rm : float
|
|
317
|
+
Cell membrane resistance
|
|
318
|
+
T : float
|
|
319
|
+
Total simulation time in ms
|
|
320
|
+
dt : float
|
|
321
|
+
Time-step to solve DEQ
|
|
322
|
+
delay: float
|
|
323
|
+
Delay of feedback in ms
|
|
324
|
+
synapses: list of Synapses
|
|
325
|
+
List of all synapses
|
|
326
|
+
slope : float
|
|
327
|
+
Slope of Threshold
|
|
328
|
+
"""
|
|
329
|
+
self.tau_mem = tau_mem
|
|
330
|
+
self.Vm_thr = Vm_thr # TODO proper Threshold
|
|
331
|
+
self.Vm_rest = Vm_rest
|
|
332
|
+
self.Rm = Rm
|
|
333
|
+
|
|
334
|
+
self.T = T
|
|
335
|
+
self.dt = dt
|
|
336
|
+
self.t_rest = 0
|
|
337
|
+
|
|
338
|
+
self.max_firerate = 23.12 # TODO Proper value
|
|
339
|
+
self.slope = slope
|
|
340
|
+
self.delay = delay
|
|
341
|
+
|
|
342
|
+
# Synapses
|
|
343
|
+
self.acetyl_synapse = synapses[0] # excitatory (Alpha Motor Neuron)
|
|
344
|
+
|
|
345
|
+
# initialize arrays
|
|
346
|
+
self.t = np.linspace(0, self.T, int(self.T / self.dt + 1))
|
|
347
|
+
self.g_tot = np.zeros(len(self.t))
|
|
348
|
+
self.Vm = Vm_rest * np.ones(len(self.t), dtype=np.float128)
|
|
349
|
+
self.spike_times = np.zeros(len(self.t))
|
|
350
|
+
self.output = np.zeros(len(self.t), dtype=np.float128)
|
|
351
|
+
|
|
352
|
+
def step(self, inp, t_, i):
|
|
353
|
+
"""
|
|
354
|
+
Solves the DEQ and computes membrane potential.
|
|
355
|
+
Saves membrane potential in self.Vm and spike times in self.spike_times
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
inp: ndarray of float [n_t]
|
|
360
|
+
Input signal
|
|
361
|
+
t_ : floatf
|
|
362
|
+
Current time step
|
|
363
|
+
i : int
|
|
364
|
+
Current index
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
self.g_tot[i] = np.sum(self.acetyl_synapse.step(inp, i))
|
|
368
|
+
|
|
369
|
+
self.Vm[i] = self.Vm[i - 1] + (-(self.Vm[i - 1] - self.Vm_rest) - self.Vm[i - 1] * self.g_tot[
|
|
370
|
+
i] * self.Rm) / self.tau_mem * self.dt
|
|
371
|
+
|
|
372
|
+
# A. Spiegler potential to Rate
|
|
373
|
+
# np.seterr('raise')
|
|
374
|
+
self.output[i] = 2 * self.max_firerate / (1 + (logsumexp(self.slope * (self.Vm_thr - self.Vm[i]))))
|
|
375
|
+
|
|
376
|
+
return self.output[i]
|
|
377
|
+
|
|
378
|
+
# if(self.Vm[i] >= self.Vm_thr):
|
|
379
|
+
# self.spike_times[i] = t_
|
|
380
|
+
# self.Vm[i] = self.Vm_rest
|
|
381
|
+
# self.t_rest = t_ + self.t_refrac
|
|
382
|
+
# return 1 / self.dt #TODO in Feuerate umwandeln
|
|
383
|
+
#
|
|
384
|
+
# return 0 # return no spike
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class AlphaMotorNeuron(object):
|
|
388
|
+
"""
|
|
389
|
+
Alpha Motor Neuron (Leaky integrate and fire)
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
def __init__(self, tau_mem, Vm_thr, Vm_rest, Rm, t_refrac, T, dt, synapses, N):
|
|
393
|
+
"""
|
|
394
|
+
Initializes AlphaMotorNeuron instance
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
tau_mem : float
|
|
399
|
+
Time constant of cell membrane in ms
|
|
400
|
+
Vm_thr : float
|
|
401
|
+
Firing threshold in mV
|
|
402
|
+
Vm_rest : float
|
|
403
|
+
Resting potential
|
|
404
|
+
Rm : float
|
|
405
|
+
Cell membrane resistance
|
|
406
|
+
t_refrac : float
|
|
407
|
+
Refractory time
|
|
408
|
+
T : float
|
|
409
|
+
Total simulation time in ms
|
|
410
|
+
dt : float
|
|
411
|
+
Time-step to solve DEQ
|
|
412
|
+
synapses: list of Synapses
|
|
413
|
+
List of all synapses
|
|
414
|
+
N: int
|
|
415
|
+
Number of alpha motor neurons
|
|
416
|
+
"""
|
|
417
|
+
self.tau_mem = tau_mem
|
|
418
|
+
self.Vm_thr = Vm_thr
|
|
419
|
+
self.Vm_rest = Vm_rest
|
|
420
|
+
self.Rm = Rm
|
|
421
|
+
self.t_refrac = t_refrac
|
|
422
|
+
self.T = T
|
|
423
|
+
self.N = N
|
|
424
|
+
self.dt = dt
|
|
425
|
+
self.t_rest = np.zeros(N)
|
|
426
|
+
self.is_in_refrac = np.ones(N)
|
|
427
|
+
|
|
428
|
+
# Synapses
|
|
429
|
+
self.acetyl_synapse = synapses[0] # excitatory (from motor cortex)
|
|
430
|
+
self.glycin_synapse = synapses[1] # inhibitory (from Renshaw cell)
|
|
431
|
+
|
|
432
|
+
# initialize arrays
|
|
433
|
+
self.t = np.linspace(0, self.T, int(self.T / self.dt + 1))
|
|
434
|
+
self.g_tot = np.zeros((N, len(self.t)))
|
|
435
|
+
self.Vm = Vm_rest * np.ones((N, len(self.t)), dtype=np.float128)
|
|
436
|
+
self.spike_times = np.zeros((N, len(self.t)))
|
|
437
|
+
|
|
438
|
+
self.anySpike = 0
|
|
439
|
+
|
|
440
|
+
def step(self, inp, renshaw_feedback, t_, i):
|
|
441
|
+
"""
|
|
442
|
+
Solves the DEQ and computes membrane potential.
|
|
443
|
+
Saves membrane potential in self.Vm and spike times in self.spike_times
|
|
444
|
+
|
|
445
|
+
Parameters
|
|
446
|
+
----------
|
|
447
|
+
inp: ndarray of float [n_t]
|
|
448
|
+
Input signal
|
|
449
|
+
renshaw_feedback: ndarray of float
|
|
450
|
+
Feedback from Renshaw Cell
|
|
451
|
+
t_ : float
|
|
452
|
+
Current time
|
|
453
|
+
i : int
|
|
454
|
+
Current index
|
|
455
|
+
"""
|
|
456
|
+
x = 0 # self.glycin_synapse.step(renshaw_feedback, i)
|
|
457
|
+
y = self.acetyl_synapse.step(inp, i)
|
|
458
|
+
|
|
459
|
+
self.g_tot[:, i] = np.where(x + y < 0, 0, x + y)
|
|
460
|
+
|
|
461
|
+
# if t_ > self.t_rest:
|
|
462
|
+
self.Vm[:, i] = self.Vm[:, i - 1] + self.is_in_refrac * \
|
|
463
|
+
((-(self.Vm[:, i - 1] - self.Vm_rest) - self.Vm[:, i - 1] * self.g_tot[:, i] * self.Rm) /
|
|
464
|
+
self.tau_mem * self.dt)
|
|
465
|
+
firing_neurons = zip(*np.where(self.Vm[:, i] >= self.Vm_thr))
|
|
466
|
+
|
|
467
|
+
for f in firing_neurons:
|
|
468
|
+
if self.is_in_refrac[f] == 1:
|
|
469
|
+
self.spike_times[f, i] = t_
|
|
470
|
+
self.Vm[f, i] = self.Vm_rest
|
|
471
|
+
self.t_rest[f] = t_ + self.t_refrac
|
|
472
|
+
self.is_in_refrac[f] = 0
|
|
473
|
+
self.anySpike = 1
|
|
474
|
+
|
|
475
|
+
for k, t in enumerate(self.t_rest):
|
|
476
|
+
if t_ > self.t_rest[k]:
|
|
477
|
+
self.is_in_refrac[k] = 1
|
|
478
|
+
|
|
479
|
+
# spike event (reset to Vm_rest and pause integrating during refractory period)
|
|
480
|
+
if self.anySpike == 1:
|
|
481
|
+
self.anySpike = 0
|
|
482
|
+
return 1 / self.dt
|
|
483
|
+
|
|
484
|
+
return 0 # return no spike
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class MotorUnit(object):
|
|
488
|
+
"""
|
|
489
|
+
Motor unit
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
def __init__(self, muap, t):
|
|
493
|
+
"""
|
|
494
|
+
Initializes motor unit instance
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
muap: ndarray of float [n_t]
|
|
499
|
+
Motor unit action potential
|
|
500
|
+
t: ndarray of float [n_t]
|
|
501
|
+
Time axis corresponding to motor unit action potential
|
|
502
|
+
t_total: ndarray of float [n_t]
|
|
503
|
+
Total time axis of observation (has to fit to t)
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
self.muap = muap
|
|
507
|
+
self.t = t
|
|
508
|
+
|
|
509
|
+
def get_muap(self, spike_times, k):
|
|
510
|
+
"""
|
|
511
|
+
Adds MUAPs of the motorunit at given spiketimes
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
spike_times: ndarray of float [n_spikes]
|
|
516
|
+
Spike times
|
|
517
|
+
|
|
518
|
+
Returns
|
|
519
|
+
-------
|
|
520
|
+
muap_total: ndarray of float [n_t]
|
|
521
|
+
Total MUAP signal (MUAPs are summed at spiketimes)
|
|
522
|
+
"""
|
|
523
|
+
muap_total = np.zeros(len(self.t))
|
|
524
|
+
|
|
525
|
+
for tau in spike_times[k, :]:
|
|
526
|
+
if not tau == 0:
|
|
527
|
+
try:
|
|
528
|
+
idx_tau = np.argmin(np.abs(self.t - tau))
|
|
529
|
+
if len(muap_total) - idx_tau < len(self.muap):
|
|
530
|
+
muap_total[idx_tau:idx_tau + len(self.muap)] += self.muap[:len(muap_total) - idx_tau]
|
|
531
|
+
else:
|
|
532
|
+
muap_total[idx_tau:idx_tau + len(self.muap)] += self.muap
|
|
533
|
+
|
|
534
|
+
except Exception as error:
|
|
535
|
+
print(error)
|
|
536
|
+
return muap_total
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class Muscle(object):
|
|
540
|
+
"""
|
|
541
|
+
Muscle containing multiple motor unit instances
|
|
542
|
+
"""
|
|
543
|
+
|
|
544
|
+
def __init__(self, alpha_motor_neurons, motor_units):
|
|
545
|
+
"""
|
|
546
|
+
Initializes muscle instance
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
alpha_motor_neurons: list of AlphaMotorNeuron instances
|
|
551
|
+
AlphaMotorNeuron instances of the muscle
|
|
552
|
+
motor_units: list of MotorUnit instances
|
|
553
|
+
MotorUnit instances corresponding to the AlphaMotorNeuron instances
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
self.alpha_motor_neurons = alpha_motor_neurons
|
|
557
|
+
self.motor_units = motor_units
|
|
558
|
+
self.t = self.alpha_motor_neurons.t
|
|
559
|
+
self.N = self.alpha_motor_neurons.N
|
|
560
|
+
self.mep = np.zeros(len(self.t))
|
|
561
|
+
|
|
562
|
+
def get_mep(self):
|
|
563
|
+
for k in range(self.N):
|
|
564
|
+
self.mep += self.motor_units[k].get_muap(self.alpha_motor_neurons.spike_times, k)
|
|
565
|
+
|
|
566
|
+
return self.mep
|