bmtool 0.5.4__py3-none-any.whl → 0.5.5__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.
- bmtool/SLURM.py +294 -0
- bmtool/bmplot.py +92 -4
- bmtool/connectors.py +142 -33
- bmtool/graphs.py +104 -104
- bmtool/singlecell.py +48 -7
- bmtool/synapses.py +638 -0
- bmtool/util/util.py +27 -10
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/METADATA +3 -2
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/RECORD +13 -11
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/WHEEL +1 -1
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/LICENSE +0 -0
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/entry_points.txt +0 -0
- {bmtool-0.5.4.dist-info → bmtool-0.5.5.dist-info}/top_level.txt +0 -0
bmtool/synapses.py
ADDED
@@ -0,0 +1,638 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import numpy as np
|
4
|
+
import neuron
|
5
|
+
from neuron import h
|
6
|
+
from neuron.units import ms, mV
|
7
|
+
import matplotlib.pyplot as plt
|
8
|
+
from scipy.signal import find_peaks
|
9
|
+
from scipy.optimize import curve_fit
|
10
|
+
|
11
|
+
import ipywidgets as widgets
|
12
|
+
from IPython.display import display, clear_output
|
13
|
+
from ipywidgets import HBox, VBox
|
14
|
+
|
15
|
+
class SynapseTuner:
|
16
|
+
def __init__(self, mechanisms_dir: str, templates_dir: str, conn_type_settings: dict, connection: str,
|
17
|
+
general_settings: dict, json_folder_path: str = None, current_name: str = 'i',
|
18
|
+
other_vars_to_record: list = None, slider_vars: list = None) -> None:
|
19
|
+
"""
|
20
|
+
Initialize the SynapseModule class with connection type settings, mechanisms, and template directories.
|
21
|
+
|
22
|
+
Parameters:
|
23
|
+
-----------
|
24
|
+
mechanisms_dir : str
|
25
|
+
Directory path containing the compiled mod files needed for NEURON mechanisms.
|
26
|
+
templates_dir : str
|
27
|
+
Directory path containing cell template files (.hoc or .py) loaded into NEURON.
|
28
|
+
conn_type_settings : dict
|
29
|
+
A dictionary containing connection-specific settings, such as synaptic properties and details.
|
30
|
+
connection : str
|
31
|
+
Name of the connection type to be used from the conn_type_settings dictionary.
|
32
|
+
general_settings : dict
|
33
|
+
General settings dictionary including parameters like simulation time step, duration, and temperature.
|
34
|
+
json_folder_path : str, optional
|
35
|
+
Path to folder containing JSON files with additional synaptic properties to update settings.
|
36
|
+
current_name : str, optional
|
37
|
+
Name of the synaptic current variable to be recorded (default is 'i').
|
38
|
+
other_vars_to_record : list, optional
|
39
|
+
List of additional synaptic variables to record during the simulation (e.g., 'Pr', 'Use').
|
40
|
+
slider_vars : list, optional
|
41
|
+
List of synaptic variables you would like sliders set up for the STP sliders method by default will use all parameters in spec_syn_param.
|
42
|
+
|
43
|
+
"""
|
44
|
+
neuron.load_mechanisms(mechanisms_dir)
|
45
|
+
h.load_file(templates_dir)
|
46
|
+
self.conn_type_settings = conn_type_settings
|
47
|
+
if json_folder_path:
|
48
|
+
print(f"updating settings from json path {json_folder_path}")
|
49
|
+
self.update_spec_syn_param(json_folder_path)
|
50
|
+
self.general_settings = general_settings
|
51
|
+
self.conn = self.conn_type_settings[connection]
|
52
|
+
self.synaptic_props = self.conn['spec_syn_param']
|
53
|
+
self.vclamp = general_settings['vclamp']
|
54
|
+
self.current_name = current_name
|
55
|
+
self.other_vars_to_record = other_vars_to_record
|
56
|
+
|
57
|
+
if slider_vars:
|
58
|
+
self.slider_vars = {key: value for key, value in self.synaptic_props.items() if key in slider_vars} # filters dict to have only the entries that have a key in the sliders var
|
59
|
+
else:
|
60
|
+
self.slider_vars = self.synaptic_props
|
61
|
+
|
62
|
+
h.tstop = general_settings['tstart'] + general_settings['tdur']
|
63
|
+
h.dt = general_settings['dt'] # Time step (resolution) of the simulation in ms
|
64
|
+
h.steps_per_ms = 1 / h.dt
|
65
|
+
h.celsius = general_settings['celsius']
|
66
|
+
|
67
|
+
def update_spec_syn_param(self, json_folder_path):
|
68
|
+
"""
|
69
|
+
Update specific synaptic parameters using JSON files located in the specified folder.
|
70
|
+
|
71
|
+
Parameters:
|
72
|
+
-----------
|
73
|
+
json_folder_path : str
|
74
|
+
Path to folder containing JSON files, where each JSON file corresponds to a connection type.
|
75
|
+
"""
|
76
|
+
for conn_type, settings in self.conn_type_settings.items():
|
77
|
+
json_file_path = os.path.join(json_folder_path, f"{conn_type}.json")
|
78
|
+
if os.path.exists(json_file_path):
|
79
|
+
with open(json_file_path, 'r') as json_file:
|
80
|
+
json_data = json.load(json_file)
|
81
|
+
settings['spec_syn_param'].update(json_data)
|
82
|
+
else:
|
83
|
+
print(f"JSON file for {conn_type} not found.")
|
84
|
+
|
85
|
+
|
86
|
+
def set_up_cell(self):
|
87
|
+
"""
|
88
|
+
Set up the neuron cell based on the specified connection settings.
|
89
|
+
"""
|
90
|
+
self.cell = getattr(h, self.conn['spec_settings']['post_cell'])()
|
91
|
+
|
92
|
+
|
93
|
+
def set_up_synapse(self):
|
94
|
+
"""
|
95
|
+
Set up the synapse on the target cell according to the synaptic parameters in `conn_type_settings`.
|
96
|
+
|
97
|
+
Notes:
|
98
|
+
------
|
99
|
+
- `set_up_cell()` should be called before setting up the synapse.
|
100
|
+
- Synapse location, type, and properties are specified within `spec_syn_param` and `spec_settings`.
|
101
|
+
"""
|
102
|
+
self.syn = getattr(h, self.conn['spec_syn_param']['level_of_detail'])(list(self.cell.all)[self.conn['spec_settings']['sec_id']](self.conn['spec_settings']['sec_x']))
|
103
|
+
for key, value in self.conn['spec_syn_param'].items():
|
104
|
+
if isinstance(value, (int, float)): # Only create sliders for numeric values
|
105
|
+
if hasattr(self.syn, key):
|
106
|
+
setattr(self.syn, key, value)
|
107
|
+
else:
|
108
|
+
print(f"Warning: {key} cannot be assigned as it does not exist in the synapse. Check your mod file or spec_syn_param.")
|
109
|
+
|
110
|
+
|
111
|
+
def set_up_recorders(self):
|
112
|
+
"""
|
113
|
+
Set up recording vectors to capture simulation data.
|
114
|
+
|
115
|
+
The method sets up recorders for:
|
116
|
+
- Synaptic current specified by `current_name`
|
117
|
+
- Other specified synaptic variables (`other_vars_to_record`)
|
118
|
+
- Time, soma voltage, and voltage clamp current for all simulations.
|
119
|
+
"""
|
120
|
+
self.rec_vectors = {}
|
121
|
+
for var in self.other_vars_to_record:
|
122
|
+
self.rec_vectors[var] = h.Vector()
|
123
|
+
ref_attr = f'_ref_{var}'
|
124
|
+
if hasattr(self.syn, ref_attr):
|
125
|
+
self.rec_vectors[var].record(getattr(self.syn, ref_attr))
|
126
|
+
else:
|
127
|
+
print(f"Warning: {ref_attr} not found in the syn object. Use vars() to inspect available attributes.")
|
128
|
+
|
129
|
+
# Record synaptic current
|
130
|
+
self.rec_vectors[self.current_name] = h.Vector()
|
131
|
+
ref_attr = f'_ref_{self.current_name}'
|
132
|
+
if hasattr(self.syn, ref_attr):
|
133
|
+
self.rec_vectors[self.current_name].record(getattr(self.syn, ref_attr))
|
134
|
+
else:
|
135
|
+
print("Warning: Synaptic current recorder not set up correctly.")
|
136
|
+
|
137
|
+
# Record time, synaptic events, soma voltage, and voltage clamp current
|
138
|
+
self.t = h.Vector()
|
139
|
+
self.tspk = h.Vector()
|
140
|
+
self.soma_v = h.Vector()
|
141
|
+
self.ivcl = h.Vector()
|
142
|
+
|
143
|
+
self.t.record(h._ref_t)
|
144
|
+
self.nc.record(self.tspk)
|
145
|
+
self.nc2.record(self.tspk)
|
146
|
+
self.soma_v.record(self.cell.soma[0](0.5)._ref_v)
|
147
|
+
self.ivcl.record(self.vcl._ref_i)
|
148
|
+
|
149
|
+
|
150
|
+
def SingleEvent(self):
|
151
|
+
"""
|
152
|
+
Simulate a single synaptic event by delivering an input stimulus to the synapse.
|
153
|
+
|
154
|
+
The method sets up the neuron cell, synapse, stimulus, and voltage clamp,
|
155
|
+
and then runs the NEURON simulation for a single event. The single synaptic event will occur at general_settings['tstart']
|
156
|
+
Will display graphs and synaptic properies works best with a jupyter notebook
|
157
|
+
"""
|
158
|
+
self.set_up_cell()
|
159
|
+
self.set_up_synapse()
|
160
|
+
|
161
|
+
# Set up the stimulus
|
162
|
+
self.nstim = h.NetStim()
|
163
|
+
self.nstim.start = self.general_settings['tstart']
|
164
|
+
self.nstim.noise = 0
|
165
|
+
self.nstim2 = h.NetStim()
|
166
|
+
self.nstim2.start = h.tstop
|
167
|
+
self.nstim2.noise = 0
|
168
|
+
self.nc = h.NetCon(self.nstim, self.syn, self.general_settings['threshold'], self.general_settings['delay'], self.general_settings['weight'])
|
169
|
+
self.nc2 = h.NetCon(self.nstim2, self.syn, self.general_settings['threshold'], self.general_settings['delay'], self.general_settings['weight'])
|
170
|
+
|
171
|
+
# Set up voltage clamp
|
172
|
+
self.vcl = h.VClamp(self.cell.soma[0](0.5))
|
173
|
+
vcldur = [[0, 0, 0], [self.general_settings['tstart'], h.tstop, 1e9]]
|
174
|
+
for i in range(3):
|
175
|
+
self.vcl.amp[i] = self.conn['spec_settings']['vclamp_amp']
|
176
|
+
self.vcl.dur[i] = vcldur[1][i]
|
177
|
+
|
178
|
+
self.set_up_recorders()
|
179
|
+
|
180
|
+
# Run simulation
|
181
|
+
h.tstop = self.general_settings['tstart'] + self.general_settings['tdur']
|
182
|
+
self.nstim.interval = self.general_settings['tdur']
|
183
|
+
self.nstim.number = 1
|
184
|
+
self.nstim2.start = h.tstop
|
185
|
+
h.run()
|
186
|
+
self.plot_model([self.general_settings['tstart'] - 5, self.general_settings['tstart'] + self.general_settings['tdur']])
|
187
|
+
syn_props = self.get_syn_prop(rise_interval=self.general_settings['rise_interval'])
|
188
|
+
for prop in syn_props.items():
|
189
|
+
print(prop)
|
190
|
+
|
191
|
+
|
192
|
+
def find_first(self, x):
|
193
|
+
"""
|
194
|
+
Find the index of the first non-zero element in a given array.
|
195
|
+
|
196
|
+
Parameters:
|
197
|
+
-----------
|
198
|
+
x : np.array
|
199
|
+
The input array to search.
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
--------
|
203
|
+
int
|
204
|
+
Index of the first non-zero element, or None if none exist.
|
205
|
+
"""
|
206
|
+
x = np.asarray(x)
|
207
|
+
idx = np.nonzero(x)[0]
|
208
|
+
return idx[0] if idx.size else None
|
209
|
+
|
210
|
+
|
211
|
+
def get_syn_prop(self, rise_interval=(0.2, 0.8), dt=h.dt, short=False):
|
212
|
+
"""
|
213
|
+
Calculate synaptic properties such as peak amplitude, latency, rise time, decay time, and half-width.
|
214
|
+
|
215
|
+
Parameters:
|
216
|
+
-----------
|
217
|
+
rise_interval : tuple of floats, optional
|
218
|
+
Fractional rise time interval to calculate (default is (0.2, 0.8)).
|
219
|
+
dt : float, optional
|
220
|
+
Time step of the simulation (default is NEURON's `h.dt`).
|
221
|
+
short : bool, optional
|
222
|
+
If True, only return baseline and sign without calculating full properties.
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
--------
|
226
|
+
dict
|
227
|
+
A dictionary containing the synaptic properties: baseline, sign, peak amplitude, latency, rise time,
|
228
|
+
decay time, and half-width.
|
229
|
+
"""
|
230
|
+
if self.vclamp:
|
231
|
+
isyn = self.ivcl
|
232
|
+
else:
|
233
|
+
isyn = self.rec_vectors['i']
|
234
|
+
isyn = np.asarray(isyn)
|
235
|
+
tspk = np.asarray(self.tspk)
|
236
|
+
if tspk.size:
|
237
|
+
tspk = tspk[0]
|
238
|
+
|
239
|
+
ispk = int(np.floor(tspk / dt))
|
240
|
+
baseline = isyn[ispk]
|
241
|
+
isyn = isyn[ispk:] - baseline
|
242
|
+
# print(np.abs(isyn))
|
243
|
+
# print(np.argmax(np.abs(isyn)))
|
244
|
+
# print(isyn[np.argmax(np.abs(isyn))])
|
245
|
+
# print(np.sign(isyn[np.argmax(np.abs(isyn))]))
|
246
|
+
sign = np.sign(isyn[np.argmax(np.abs(isyn))])
|
247
|
+
if short:
|
248
|
+
return {'baseline': baseline, 'sign': sign}
|
249
|
+
isyn *= sign
|
250
|
+
# print(isyn)
|
251
|
+
# peak amplitude
|
252
|
+
ipk, _ = find_peaks(isyn)
|
253
|
+
ipk = ipk[0]
|
254
|
+
peak = isyn[ipk]
|
255
|
+
# latency
|
256
|
+
istart = self.find_first(np.diff(isyn[:ipk + 1]) > 0)
|
257
|
+
latency = dt * (istart + 1)
|
258
|
+
# rise time
|
259
|
+
rt1 = self.find_first(isyn[istart:ipk + 1] > rise_interval[0] * peak)
|
260
|
+
rt2 = self.find_first(isyn[istart:ipk + 1] > rise_interval[1] * peak)
|
261
|
+
rise_time = (rt2 - rt1) * dt
|
262
|
+
# decay time
|
263
|
+
iend = self.find_first(np.diff(isyn[ipk:]) > 0)
|
264
|
+
iend = isyn.size - 1 if iend is None else iend + ipk
|
265
|
+
decay_len = iend - ipk + 1
|
266
|
+
popt, _ = curve_fit(lambda t, a, tau: a * np.exp(-t / tau), dt * np.arange(decay_len),
|
267
|
+
isyn[ipk:iend + 1], p0=(peak, dt * decay_len / 2))
|
268
|
+
decay_time = popt[1]
|
269
|
+
# half-width
|
270
|
+
hw1 = self.find_first(isyn[istart:ipk + 1] > 0.5 * peak)
|
271
|
+
hw2 = self.find_first(isyn[ipk:] < 0.5 * peak)
|
272
|
+
hw2 = isyn.size if hw2 is None else hw2 + ipk
|
273
|
+
half_width = dt * (hw2 - hw1)
|
274
|
+
output = {'baseline': baseline, 'sign': sign, 'latency': latency,
|
275
|
+
'amp': peak, 'rise_time': rise_time, 'decay_time': decay_time, 'half_width': half_width}
|
276
|
+
return output
|
277
|
+
|
278
|
+
|
279
|
+
def plot_model(self, xlim):
|
280
|
+
"""
|
281
|
+
Plots the results of the simulation, including synaptic current, soma voltage,
|
282
|
+
and any additional recorded variables.
|
283
|
+
|
284
|
+
Parameters:
|
285
|
+
-----------
|
286
|
+
xlim : tuple
|
287
|
+
A tuple specifying the limits of the x-axis for the plot (start_time, end_time).
|
288
|
+
|
289
|
+
Notes:
|
290
|
+
------
|
291
|
+
- The function determines how many plots to generate based on the number of variables recorded.
|
292
|
+
- Synaptic current and either voltage clamp or soma voltage will always be plotted.
|
293
|
+
- If other variables are provided in `other_vars_to_record`, they are also plotted.
|
294
|
+
- The function adjusts the plot layout and removes any extra subplots that are not needed.
|
295
|
+
"""
|
296
|
+
# Determine the number of plots to generate (at least 2: current and voltage)
|
297
|
+
num_vars_to_plot = 2 + (len(self.other_vars_to_record) if self.other_vars_to_record else 0)
|
298
|
+
|
299
|
+
# Set up figure based on number of plots (2x2 grid max)
|
300
|
+
num_rows = (num_vars_to_plot + 1) // 2 # This ensures we have enough rows
|
301
|
+
fig, axs = plt.subplots(num_rows, 2, figsize=(12, 7))
|
302
|
+
axs = axs.ravel()
|
303
|
+
|
304
|
+
# Plot synaptic current (always included)
|
305
|
+
axs[0].plot(self.t, 1000 * self.rec_vectors[self.current_name])
|
306
|
+
axs[0].set_ylabel('Synaptic Current (pA)')
|
307
|
+
|
308
|
+
# Plot voltage clamp or soma voltage (always included)
|
309
|
+
ispk = int(np.round(self.tspk[0] / h.dt))
|
310
|
+
if self.vclamp:
|
311
|
+
baseline = self.ivcl[ispk]
|
312
|
+
ivcl_plt = np.array(self.ivcl) - baseline
|
313
|
+
ivcl_plt[:ispk] = 0
|
314
|
+
axs[1].plot(self.t, 1000 * ivcl_plt)
|
315
|
+
axs[1].set_ylabel('VClamp Current (pA)')
|
316
|
+
else:
|
317
|
+
soma_v_plt = np.array(self.soma_v)
|
318
|
+
soma_v_plt[:ispk] = soma_v_plt[ispk]
|
319
|
+
axs[1].plot(self.t, soma_v_plt)
|
320
|
+
axs[1].set_ylabel('Soma Voltage (mV)')
|
321
|
+
|
322
|
+
# Plot any other variables from other_vars_to_record, if provided
|
323
|
+
if self.other_vars_to_record:
|
324
|
+
for i, var in enumerate(self.other_vars_to_record, start=2):
|
325
|
+
if var in self.rec_vectors:
|
326
|
+
axs[i].plot(self.t, self.rec_vectors[var])
|
327
|
+
axs[i].set_ylabel(f'{var.capitalize()}')
|
328
|
+
|
329
|
+
# Adjust the layout
|
330
|
+
for i, ax in enumerate(axs[:num_vars_to_plot]):
|
331
|
+
ax.set_xlim(*xlim)
|
332
|
+
if i >= num_vars_to_plot - 2: # Add x-label to the last row
|
333
|
+
ax.set_xlabel('Time (ms)')
|
334
|
+
|
335
|
+
# Remove extra subplots if less than 4 plots are present
|
336
|
+
if num_vars_to_plot < len(axs):
|
337
|
+
for j in range(num_vars_to_plot, len(axs)):
|
338
|
+
fig.delaxes(axs[j])
|
339
|
+
|
340
|
+
plt.tight_layout()
|
341
|
+
plt.show()
|
342
|
+
|
343
|
+
|
344
|
+
def set_drive_train(self,freq=50., delay=250.):
|
345
|
+
"""
|
346
|
+
Configures trains of 12 action potentials at a specified frequency and delay period
|
347
|
+
between pulses 8 and 9.
|
348
|
+
|
349
|
+
Parameters:
|
350
|
+
-----------
|
351
|
+
freq : float, optional
|
352
|
+
Frequency of the pulse train in Hz. Default is 50 Hz.
|
353
|
+
delay : float, optional
|
354
|
+
Delay period in milliseconds between the 8th and 9th pulses. Default is 250 ms.
|
355
|
+
|
356
|
+
Returns:
|
357
|
+
--------
|
358
|
+
tstop : float
|
359
|
+
The time at which the last pulse stops.
|
360
|
+
|
361
|
+
Notes:
|
362
|
+
------
|
363
|
+
- This function is based on experiments from the Allen Database.
|
364
|
+
"""
|
365
|
+
n_init_pulse = 8
|
366
|
+
n_ending_pulse = 4
|
367
|
+
self.nstim.start = self.general_settings['tstart']
|
368
|
+
self.nstim.interval = 1000 / freq
|
369
|
+
self.nstim2.interval = 1000 / freq
|
370
|
+
self.nstim.number = n_init_pulse
|
371
|
+
self.nstim2.number = n_ending_pulse
|
372
|
+
self.nstim2.start = self.nstim.start + (n_init_pulse - 1) * self.nstim.interval + delay
|
373
|
+
tstop = self.nstim2.start + n_ending_pulse * self.nstim2.interval
|
374
|
+
return tstop
|
375
|
+
|
376
|
+
|
377
|
+
def response_amplitude(self):
|
378
|
+
"""
|
379
|
+
Calculates the amplitude of the synaptic response by analyzing the recorded synaptic current.
|
380
|
+
|
381
|
+
Returns:
|
382
|
+
--------
|
383
|
+
amp : list
|
384
|
+
A list containing the peak amplitudes for each segment of the recorded synaptic current.
|
385
|
+
|
386
|
+
"""
|
387
|
+
isyn = np.asarray(self.rec_vectors['i'])
|
388
|
+
tspk = np.append(np.asarray(self.tspk), h.tstop)
|
389
|
+
syn_prop = self.get_syn_prop(short=True)
|
390
|
+
# print("syn_prp[sign] = " + str(syn_prop['sign']))
|
391
|
+
isyn = (isyn - syn_prop['baseline'])
|
392
|
+
isyn *= syn_prop['sign']
|
393
|
+
# print(isyn)
|
394
|
+
ispk = np.floor((tspk + self.general_settings['delay']) / h.dt).astype(int)
|
395
|
+
amp = [isyn[ispk[i]:ispk[i + 1]].max() for i in range(ispk.size - 1)]
|
396
|
+
return amp
|
397
|
+
|
398
|
+
|
399
|
+
def find_max_amp(self, amp, normalize_by_trial=True):
|
400
|
+
"""
|
401
|
+
Determines the maximum amplitude from the response data.
|
402
|
+
|
403
|
+
Parameters:
|
404
|
+
-----------
|
405
|
+
amp : array-like
|
406
|
+
Array containing the amplitudes of synaptic responses.
|
407
|
+
normalize_by_trial : bool, optional
|
408
|
+
If True, normalize the maximum amplitude within each trial. Default is True.
|
409
|
+
|
410
|
+
Returns:
|
411
|
+
--------
|
412
|
+
max_amp : float
|
413
|
+
The maximum or minimum amplitude based on the sign of the response.
|
414
|
+
"""
|
415
|
+
max_amp = amp.max(axis=1 if normalize_by_trial else None)
|
416
|
+
min_amp = amp.min(axis=1 if normalize_by_trial else None)
|
417
|
+
if(abs(min_amp) > max_amp):
|
418
|
+
return min_amp
|
419
|
+
return max_amp
|
420
|
+
|
421
|
+
|
422
|
+
def induction_recovery(self,amp, normalize_by_trial=True):
|
423
|
+
"""
|
424
|
+
Calculates induction and recovery metrics from the synaptic response amplitudes.
|
425
|
+
|
426
|
+
Parameters:
|
427
|
+
-----------
|
428
|
+
amp : array-like
|
429
|
+
Array containing the amplitudes of synaptic responses.
|
430
|
+
normalize_by_trial : bool, optional
|
431
|
+
If True, normalize the amplitudes within each trial. Default is True.
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
--------
|
435
|
+
induction : float
|
436
|
+
The calculated induction value (difference between pulses 6-8 and 1st pulse).
|
437
|
+
recovery : float
|
438
|
+
The calculated recovery value (difference between pulses 9-12 and pulses 1-4).
|
439
|
+
maxamp : float
|
440
|
+
The maximum amplitude in the response.
|
441
|
+
"""
|
442
|
+
amp = np.array(amp)
|
443
|
+
amp = amp.reshape(-1, amp.shape[-1])
|
444
|
+
|
445
|
+
|
446
|
+
maxamp = amp.max(axis=1 if normalize_by_trial else None)
|
447
|
+
induction = np.mean((amp[:, 5:8].mean(axis=1) - amp[:, :1].mean(axis=1)) / maxamp)
|
448
|
+
recovery = np.mean((amp[:, 8:12].mean(axis=1) - amp[:, :4].mean(axis=1)) / maxamp)
|
449
|
+
|
450
|
+
# maxamp = max(amp, key=lambda x: abs(x[0]))
|
451
|
+
maxamp = maxamp.max()
|
452
|
+
return induction, recovery, maxamp
|
453
|
+
|
454
|
+
|
455
|
+
def paired_pulse_ratio(self, dt=h.dt):
|
456
|
+
"""
|
457
|
+
Computes the paired-pulse ratio (PPR) based on the recorded synaptic current or voltage.
|
458
|
+
|
459
|
+
Parameters:
|
460
|
+
-----------
|
461
|
+
dt : float, optional
|
462
|
+
Time step in milliseconds. Default is the NEURON simulation time step.
|
463
|
+
|
464
|
+
Returns:
|
465
|
+
--------
|
466
|
+
ppr : float
|
467
|
+
The ratio between the second and first pulse amplitudes.
|
468
|
+
|
469
|
+
Notes:
|
470
|
+
------
|
471
|
+
- The function handles both voltage-clamp and current-clamp conditions.
|
472
|
+
- A minimum of two spikes is required to calculate PPR.
|
473
|
+
"""
|
474
|
+
if self.vclamp:
|
475
|
+
isyn = self.ivcl
|
476
|
+
else:
|
477
|
+
isyn = self.rec_vectors['i']
|
478
|
+
isyn = np.asarray(isyn)
|
479
|
+
tspk = np.asarray(self.tspk)
|
480
|
+
if tspk.size < 2:
|
481
|
+
raise ValueError("Need at least two spikes.")
|
482
|
+
syn_prop = self.get_syn_prop()
|
483
|
+
isyn = (isyn - syn_prop['baseline']) * syn_prop['sign']
|
484
|
+
ispk2 = int(np.floor(tspk[1] / dt))
|
485
|
+
ipk, _ = find_peaks(isyn[ispk2:])
|
486
|
+
ipk2 = ipk[0] + ispk2
|
487
|
+
peak2 = isyn[ipk2]
|
488
|
+
return peak2 / syn_prop['amp']
|
489
|
+
|
490
|
+
|
491
|
+
def set_syn_prop(self, **kwargs):
|
492
|
+
"""
|
493
|
+
Sets the synaptic parameters based on user inputs from sliders.
|
494
|
+
|
495
|
+
Parameters:
|
496
|
+
-----------
|
497
|
+
**kwargs : dict
|
498
|
+
Synaptic properties (such as weight, Use, tau_f, tau_d) as keyword arguments.
|
499
|
+
"""
|
500
|
+
for key, value in kwargs.items():
|
501
|
+
setattr(self.syn, key, value)
|
502
|
+
|
503
|
+
|
504
|
+
def simulate_model(self,input_frequency, delay, vclamp=None):
|
505
|
+
"""
|
506
|
+
Runs the simulation with the specified input frequency, delay, and voltage clamp settings.
|
507
|
+
|
508
|
+
Parameters:
|
509
|
+
-----------
|
510
|
+
input_frequency : float
|
511
|
+
Frequency of the input drive train in Hz.
|
512
|
+
delay : float
|
513
|
+
Delay period in milliseconds between the 8th and 9th pulses.
|
514
|
+
vclamp : bool or None, optional
|
515
|
+
Whether to use voltage clamp. If None, the current setting is used. Default is None.
|
516
|
+
|
517
|
+
"""
|
518
|
+
if self.input_mode == False:
|
519
|
+
self.tstop = self.set_drive_train(input_frequency, delay)
|
520
|
+
h.tstop = self.tstop
|
521
|
+
|
522
|
+
vcldur = [[0, 0, 0], [self.general_settings['tstart'], self.tstop, 1e9]]
|
523
|
+
for i in range(3):
|
524
|
+
self.vcl.amp[i] = self.conn['spec_settings']['vclamp_amp']
|
525
|
+
self.vcl.dur[i] = vcldur[1][i]
|
526
|
+
h.finitialize(self.cell.Vinit * mV)
|
527
|
+
h.continuerun(self.tstop * ms)
|
528
|
+
else:
|
529
|
+
self.tstop = self.general_settings['tstart'] + self.general_settings['tdur']
|
530
|
+
self.nstim.interval = 1000 / input_frequency
|
531
|
+
self.nstim.number = np.ceil(self.w_duration.value / 1000 * input_frequency + 1)
|
532
|
+
self.nstim2.number = 0
|
533
|
+
self.tstop = self.w_duration.value + self.general_settings['tstart']
|
534
|
+
|
535
|
+
h.finitialize(self.cell.Vinit * mV)
|
536
|
+
h.continuerun(self.tstop * ms)
|
537
|
+
|
538
|
+
|
539
|
+
def InteractiveTuner(self):
|
540
|
+
"""
|
541
|
+
Sets up interactive sliders for short-term plasticity (STP) experiments in a Jupyter Notebook.
|
542
|
+
|
543
|
+
Notes:
|
544
|
+
------
|
545
|
+
- The sliders allow control over synaptic properties dynamically based on slider_vars.
|
546
|
+
- Additional buttons allow running the simulation and configuring voltage clamp settings.
|
547
|
+
"""
|
548
|
+
# Widgets setup (Sliders)
|
549
|
+
freqs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 50, 100, 200]
|
550
|
+
delays = [125, 250, 500, 1000, 2000, 4000]
|
551
|
+
durations = [300, 500, 1000, 2000, 5000, 10000]
|
552
|
+
freq0 = 50
|
553
|
+
delay0 = 250
|
554
|
+
duration0 = 300
|
555
|
+
vlamp_status = self.vclamp
|
556
|
+
|
557
|
+
w_run = widgets.Button(description='Run', icon='history', button_style='primary')
|
558
|
+
w_vclamp = widgets.ToggleButton(value=vlamp_status, description='Voltage Clamp', icon='fast-backward', button_style='warning')
|
559
|
+
w_input_mode = widgets.ToggleButton(value=False, description='Continuous input', icon='eject', button_style='info')
|
560
|
+
w_input_freq = widgets.SelectionSlider(options=freqs, value=freq0, description='Input Freq')
|
561
|
+
|
562
|
+
# Sliders for delay and duration
|
563
|
+
self.w_delay = widgets.SelectionSlider(options=delays, value=delay0, description='Delay')
|
564
|
+
self.w_duration = widgets.SelectionSlider(options=durations, value=duration0, description='Duration')
|
565
|
+
|
566
|
+
# Generate sliders dynamically based on valid numeric entries in self.slider_vars
|
567
|
+
dynamic_sliders = {}
|
568
|
+
print("Setting up slider! The sliders ranges are set by their init value so try changing that if you dont like the slider range!")
|
569
|
+
for key, value in self.slider_vars.items():
|
570
|
+
if isinstance(value, (int, float)): # Only create sliders for numeric values
|
571
|
+
if hasattr(self.syn, key):
|
572
|
+
if value == 0:
|
573
|
+
print(f'{key} was set to zero, going to try to set a range of values, try settings the {key} to a nonzero value if you dont like the range!')
|
574
|
+
slider = widgets.FloatSlider(value=value, min=0, max=1000, step=1, description=key)
|
575
|
+
else:
|
576
|
+
slider = widgets.FloatSlider(value=value, min=0, max=value*20, step=value/5, description=key)
|
577
|
+
dynamic_sliders[key] = slider
|
578
|
+
else:
|
579
|
+
print(f"skipping slider for {key} due to not being a synaptic variable")
|
580
|
+
|
581
|
+
# Function to update UI based on input mode
|
582
|
+
def update_ui(*args):
|
583
|
+
clear_output()
|
584
|
+
display(ui)
|
585
|
+
self.vclamp = w_vclamp.value
|
586
|
+
self.input_mode = w_input_mode.value
|
587
|
+
# Update synaptic properties based on slider values
|
588
|
+
syn_props = {var: slider.value for var, slider in dynamic_sliders.items()}
|
589
|
+
self.set_syn_prop(**syn_props)
|
590
|
+
if self.input_mode == False:
|
591
|
+
self.simulate_model(w_input_freq.value, self.w_delay.value, w_vclamp.value)
|
592
|
+
else:
|
593
|
+
self.simulate_model(w_input_freq.value, self.w_duration.value, w_vclamp.value)
|
594
|
+
self.plot_model([self.general_settings['tstart'] - self.nstim.interval / 3, self.tstop])
|
595
|
+
amp = self.response_amplitude()
|
596
|
+
induction_single, recovery, maxamp = self.induction_recovery(amp)
|
597
|
+
ppr = self.paired_pulse_ratio()
|
598
|
+
print('Paired Pulse Ratio using ' + ('PSC' if self.vclamp else 'PSP') + f': {ppr:.3f}')
|
599
|
+
print('Single trial ' + ('PSC' if self.vclamp else 'PSP'))
|
600
|
+
print(f'Induction: {induction_single:.2f}; Recovery: {recovery:.2f}')
|
601
|
+
print(f'Rest Amp: {amp[0]:.2f}; Maximum Amp: {maxamp:.2f}')
|
602
|
+
|
603
|
+
# Function to switch between delay and duration sliders
|
604
|
+
def switch_slider(*args):
|
605
|
+
if w_input_mode.value:
|
606
|
+
self.w_delay.layout.display = 'none' # Hide delay slider
|
607
|
+
self.w_duration.layout.display = '' # Show duration slider
|
608
|
+
else:
|
609
|
+
self.w_delay.layout.display = '' # Show delay slider
|
610
|
+
self.w_duration.layout.display = 'none' # Hide duration slider
|
611
|
+
|
612
|
+
# Link input mode to slider switch
|
613
|
+
w_input_mode.observe(switch_slider, names='value')
|
614
|
+
|
615
|
+
# Hide the duration slider initially
|
616
|
+
self.w_duration.layout.display = 'none' # Hide duration slider
|
617
|
+
|
618
|
+
w_run.on_click(update_ui)
|
619
|
+
|
620
|
+
# Add the dynamic sliders to the UI
|
621
|
+
slider_widgets = [slider for slider in dynamic_sliders.values()]
|
622
|
+
|
623
|
+
# Divide sliders into two columns
|
624
|
+
half = len(slider_widgets) // 2
|
625
|
+
col1 = VBox(slider_widgets[:half]) # First half of sliders
|
626
|
+
col2 = VBox(slider_widgets[half:]) # Second half of sliders
|
627
|
+
|
628
|
+
# Create a two-column layout with HBox
|
629
|
+
slider_columns = HBox([col1, col2])
|
630
|
+
|
631
|
+
ui = VBox([HBox([w_run, w_vclamp, w_input_mode]), HBox([w_input_freq, self.w_delay, self.w_duration]), slider_columns])
|
632
|
+
|
633
|
+
display(ui)
|
634
|
+
|
635
|
+
|
636
|
+
|
637
|
+
|
638
|
+
|