bmtool 0.7.0.6.4__py3-none-any.whl → 0.7.1.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.
- bmtool/SLURM.py +162 -109
- bmtool/__init__.py +1 -1
- bmtool/__main__.py +8 -7
- bmtool/analysis/entrainment.py +290 -147
- bmtool/analysis/lfp.py +279 -134
- bmtool/analysis/netcon_reports.py +41 -44
- bmtool/analysis/spikes.py +114 -73
- bmtool/bmplot/connections.py +658 -325
- bmtool/bmplot/entrainment.py +17 -18
- bmtool/bmplot/lfp.py +24 -17
- bmtool/bmplot/netcon_reports.py +0 -4
- bmtool/bmplot/spikes.py +97 -48
- bmtool/connectors.py +394 -251
- bmtool/debug/commands.py +13 -7
- bmtool/debug/debug.py +2 -2
- bmtool/graphs.py +26 -19
- bmtool/manage.py +6 -11
- bmtool/plot_commands.py +350 -151
- bmtool/singlecell.py +357 -195
- bmtool/synapses.py +564 -470
- bmtool/util/commands.py +1079 -627
- bmtool/util/neuron/celltuner.py +989 -609
- bmtool/util/util.py +992 -588
- {bmtool-0.7.0.6.4.dist-info → bmtool-0.7.1.1.dist-info}/METADATA +40 -2
- bmtool-0.7.1.1.dist-info/RECORD +34 -0
- {bmtool-0.7.0.6.4.dist-info → bmtool-0.7.1.1.dist-info}/WHEEL +1 -1
- bmtool-0.7.0.6.4.dist-info/RECORD +0 -34
- {bmtool-0.7.0.6.4.dist-info → bmtool-0.7.1.1.dist-info}/entry_points.txt +0 -0
- {bmtool-0.7.0.6.4.dist-info → bmtool-0.7.1.1.dist-info}/licenses/LICENSE +0 -0
- {bmtool-0.7.0.6.4.dist-info → bmtool-0.7.1.1.dist-info}/top_level.txt +0 -0
bmtool/singlecell.py
CHANGED
@@ -1,38 +1,40 @@
|
|
1
1
|
import glob
|
2
|
-
import os
|
3
2
|
import json
|
3
|
+
import os
|
4
4
|
from typing import Tuple
|
5
|
-
|
5
|
+
|
6
6
|
import matplotlib.pyplot as plt
|
7
|
-
from scipy.optimize import curve_fit
|
8
7
|
import neuron
|
9
|
-
|
8
|
+
import numpy as np
|
10
9
|
import pandas as pd
|
10
|
+
from neuron import h
|
11
|
+
from scipy.optimize import curve_fit
|
11
12
|
|
12
13
|
|
13
14
|
def load_biophys1():
|
14
15
|
"""
|
15
16
|
Load the Biophys1 template from BMTK if it hasn't been loaded yet.
|
16
|
-
|
17
|
+
|
17
18
|
This function checks if the Biophys1 object exists in NEURON's h namespace.
|
18
19
|
If not, it loads the necessary HOC files for Allen Cell Types Database models.
|
19
|
-
|
20
|
+
|
20
21
|
Notes:
|
21
22
|
------
|
22
23
|
This is primarily used for working with cell models from the Allen Cell Types Database.
|
23
24
|
"""
|
24
|
-
if not hasattr(h,
|
25
|
+
if not hasattr(h, "Biophys1"):
|
25
26
|
from bmtk import utils
|
27
|
+
|
26
28
|
module_dir = os.path.dirname(os.path.abspath(utils.__file__))
|
27
|
-
hoc_file = os.path.join(module_dir,
|
29
|
+
hoc_file = os.path.join(module_dir, "scripts", "bionet", "templates", "Biophys1.hoc")
|
28
30
|
h.load_file("import3d.hoc")
|
29
31
|
h.load_file(hoc_file)
|
30
32
|
|
31
33
|
|
32
|
-
def load_allen_database_cells(morphology, dynamic_params, model_processing=
|
34
|
+
def load_allen_database_cells(morphology, dynamic_params, model_processing="aibs_perisomatic"):
|
33
35
|
"""
|
34
36
|
Create a cell model from the Allen Cell Types Database.
|
35
|
-
|
37
|
+
|
36
38
|
Parameters:
|
37
39
|
-----------
|
38
40
|
morphology : str
|
@@ -42,34 +44,37 @@ def load_allen_database_cells(morphology, dynamic_params, model_processing='aibs
|
|
42
44
|
model_processing : str, optional
|
43
45
|
Model processing type from the AllenCellType database.
|
44
46
|
Default is 'aibs_perisomatic'.
|
45
|
-
|
47
|
+
|
46
48
|
Returns:
|
47
49
|
--------
|
48
50
|
callable
|
49
51
|
A function that, when called, creates and returns a NEURON cell object
|
50
52
|
with the specified morphology and biophysical properties.
|
51
|
-
|
53
|
+
|
52
54
|
Notes:
|
53
55
|
------
|
54
56
|
This function creates a closure that loads and returns a cell when called.
|
55
57
|
The cell is created using the Allen Institute's modeling framework.
|
56
58
|
"""
|
57
59
|
from bmtk.simulator.bionet.default_setters import cell_models
|
60
|
+
|
58
61
|
load_biophys1()
|
59
62
|
model_processing = getattr(cell_models, model_processing)
|
60
63
|
with open(dynamic_params) as f:
|
61
64
|
dynamics_params = json.load(f)
|
65
|
+
|
62
66
|
def create_cell():
|
63
67
|
hobj = h.Biophys1(morphology)
|
64
68
|
hobj = model_processing(hobj, cell=None, dynamics_params=dynamics_params)
|
65
69
|
return hobj
|
70
|
+
|
66
71
|
return create_cell
|
67
72
|
|
68
73
|
|
69
|
-
def get_target_site(cell, sec=(
|
74
|
+
def get_target_site(cell, sec=("soma", 0), loc=0.5, site=""):
|
70
75
|
"""
|
71
76
|
Get a segment and its section from a cell model using flexible section specification.
|
72
|
-
|
77
|
+
|
73
78
|
Parameters:
|
74
79
|
-----------
|
75
80
|
cell : NEURON cell object
|
@@ -84,12 +89,12 @@ def get_target_site(cell, sec=('soma', 0), loc=0.5, site=''):
|
|
84
89
|
Location along the section (0-1), default is 0.5 (middle of section).
|
85
90
|
site : str, optional
|
86
91
|
Name of the site for error messages (e.g., 'injection', 'recording').
|
87
|
-
|
92
|
+
|
88
93
|
Returns:
|
89
94
|
--------
|
90
95
|
tuple
|
91
96
|
(segment, section) at the specified location
|
92
|
-
|
97
|
+
|
93
98
|
Raises:
|
94
99
|
-------
|
95
100
|
ValueError
|
@@ -98,9 +103,9 @@ def get_target_site(cell, sec=('soma', 0), loc=0.5, site=''):
|
|
98
103
|
if isinstance(sec, str):
|
99
104
|
sec = (sec, 0)
|
100
105
|
elif isinstance(sec, int):
|
101
|
-
if not hasattr(cell,
|
106
|
+
if not hasattr(cell, "all"):
|
102
107
|
raise ValueError("Section list named 'all' does not exist in the template.")
|
103
|
-
sec = (
|
108
|
+
sec = ("all", sec)
|
104
109
|
loc = float(loc)
|
105
110
|
try:
|
106
111
|
section = next(s for i, s in enumerate(getattr(cell, sec[0])) if i == sec[1])
|
@@ -117,11 +122,23 @@ def get_target_site(cell, sec=('soma', 0), loc=0.5, site=''):
|
|
117
122
|
|
118
123
|
|
119
124
|
class CurrentClamp(object):
|
120
|
-
def __init__(
|
121
|
-
|
125
|
+
def __init__(
|
126
|
+
self,
|
127
|
+
template_name,
|
128
|
+
post_init_function=None,
|
129
|
+
record_sec="soma",
|
130
|
+
record_loc=0.5,
|
131
|
+
threshold=None,
|
132
|
+
inj_sec="soma",
|
133
|
+
inj_loc=0.5,
|
134
|
+
inj_amp=100.0,
|
135
|
+
inj_delay=100.0,
|
136
|
+
inj_dur=1000.0,
|
137
|
+
tstop=1000.0,
|
138
|
+
):
|
122
139
|
"""
|
123
140
|
Initialize a current clamp simulation environment.
|
124
|
-
|
141
|
+
|
125
142
|
Parameters:
|
126
143
|
-----------
|
127
144
|
template_name : str or callable
|
@@ -153,7 +170,9 @@ class CurrentClamp(object):
|
|
153
170
|
Total simulation time (ms). Default is 1000.0.
|
154
171
|
Will be extended if necessary to include the full current injection.
|
155
172
|
"""
|
156
|
-
self.create_cell =
|
173
|
+
self.create_cell = (
|
174
|
+
getattr(h, template_name) if isinstance(template_name, str) else template_name
|
175
|
+
)
|
157
176
|
self.record_sec = record_sec
|
158
177
|
self.record_loc = record_loc
|
159
178
|
self.inj_sec = inj_sec
|
@@ -161,14 +180,14 @@ class CurrentClamp(object):
|
|
161
180
|
self.threshold = threshold
|
162
181
|
|
163
182
|
self.tstop = max(tstop, inj_delay + inj_dur)
|
164
|
-
self.inj_delay = inj_delay
|
183
|
+
self.inj_delay = inj_delay # use x ms after start of inj to calculate r_in, etc
|
165
184
|
self.inj_dur = inj_dur
|
166
|
-
self.inj_amp = inj_amp * 1e-3
|
185
|
+
self.inj_amp = inj_amp * 1e-3 # pA to nA
|
167
186
|
|
168
|
-
# sometimes people may put a hoc object in for the template name
|
187
|
+
# sometimes people may put a hoc object in for the template name
|
169
188
|
if callable(template_name):
|
170
189
|
self.cell = template_name()
|
171
|
-
else:
|
190
|
+
else:
|
172
191
|
self.cell = self.create_cell()
|
173
192
|
if post_init_function:
|
174
193
|
eval(f"self.cell.{post_init_function}")
|
@@ -178,23 +197,23 @@ class CurrentClamp(object):
|
|
178
197
|
def setup(self):
|
179
198
|
"""
|
180
199
|
Set up the simulation environment for current clamp experiments.
|
181
|
-
|
200
|
+
|
182
201
|
This method:
|
183
202
|
1. Creates the current clamp stimulus at the specified injection site
|
184
203
|
2. Sets up voltage recording at the specified recording site
|
185
204
|
3. Creates vectors to store time and voltage data
|
186
|
-
|
205
|
+
|
187
206
|
Notes:
|
188
207
|
------
|
189
208
|
Sets self.cell_src as the current clamp object that can be accessed later.
|
190
209
|
"""
|
191
|
-
inj_seg, _ = get_target_site(self.cell, self.inj_sec, self.inj_loc,
|
210
|
+
inj_seg, _ = get_target_site(self.cell, self.inj_sec, self.inj_loc, "injection")
|
192
211
|
self.cell_src = h.IClamp(inj_seg)
|
193
212
|
self.cell_src.delay = self.inj_delay
|
194
213
|
self.cell_src.dur = self.inj_dur
|
195
214
|
self.cell_src.amp = self.inj_amp
|
196
215
|
|
197
|
-
rec_seg, rec_sec = get_target_site(self.cell, self.record_sec, self.record_loc,
|
216
|
+
rec_seg, rec_sec = get_target_site(self.cell, self.record_sec, self.record_loc, "recording")
|
198
217
|
self.v_vec = h.Vector()
|
199
218
|
self.v_vec.record(rec_seg._ref_v)
|
200
219
|
|
@@ -207,18 +226,18 @@ class CurrentClamp(object):
|
|
207
226
|
self.tspk_vec = h.Vector()
|
208
227
|
self.nc.record(self.tspk_vec)
|
209
228
|
|
210
|
-
print(f
|
211
|
-
print(f
|
229
|
+
print(f"Injection location: {inj_seg}")
|
230
|
+
print(f"Recording: {rec_seg}._ref_v")
|
212
231
|
|
213
232
|
def execute(self) -> Tuple[list, list]:
|
214
233
|
"""
|
215
234
|
Run the current clamp simulation and return recorded data.
|
216
|
-
|
235
|
+
|
217
236
|
This method:
|
218
237
|
1. Sets up the simulation duration
|
219
238
|
2. Initializes and runs the NEURON simulation
|
220
239
|
3. Converts recorded vectors to Python lists
|
221
|
-
|
240
|
+
|
222
241
|
Returns:
|
223
242
|
--------
|
224
243
|
tuple
|
@@ -234,17 +253,25 @@ class CurrentClamp(object):
|
|
234
253
|
if self.threshold is not None:
|
235
254
|
self.nspks = len(self.tspk_vec)
|
236
255
|
print()
|
237
|
-
print(f
|
256
|
+
print(f"Number of spikes: {self.nspks:d}")
|
238
257
|
print()
|
239
258
|
return self.t_vec.to_python(), self.v_vec.to_python()
|
240
259
|
|
241
260
|
|
242
261
|
class Passive(CurrentClamp):
|
243
|
-
def __init__(
|
244
|
-
|
262
|
+
def __init__(
|
263
|
+
self,
|
264
|
+
template_name,
|
265
|
+
inj_amp=-100.0,
|
266
|
+
inj_delay=200.0,
|
267
|
+
inj_dur=1000.0,
|
268
|
+
tstop=1200.0,
|
269
|
+
method=None,
|
270
|
+
**kwargs,
|
271
|
+
):
|
245
272
|
"""
|
246
273
|
Initialize a passive membrane property simulation environment.
|
247
|
-
|
274
|
+
|
248
275
|
Parameters:
|
249
276
|
-----------
|
250
277
|
template_name : str or callable
|
@@ -264,39 +291,49 @@ class Passive(CurrentClamp):
|
|
264
291
|
- 'exp': Fit a single exponential curve
|
265
292
|
- 'exp2': Fit a double exponential curve
|
266
293
|
Default is None, which uses 'simple' when calculations are performed.
|
267
|
-
**kwargs :
|
294
|
+
**kwargs :
|
268
295
|
Additional keyword arguments to pass to the parent CurrentClamp constructor.
|
269
|
-
|
296
|
+
|
270
297
|
Notes:
|
271
298
|
------
|
272
299
|
This class is designed for measuring passive membrane properties including
|
273
300
|
input resistance and membrane time constant.
|
274
|
-
|
301
|
+
|
275
302
|
Raises:
|
276
303
|
-------
|
277
304
|
AssertionError
|
278
305
|
If inj_amp is zero (must be non-zero to measure passive properties).
|
279
306
|
"""
|
280
|
-
assert
|
281
|
-
super().__init__(
|
282
|
-
|
307
|
+
assert inj_amp != 0
|
308
|
+
super().__init__(
|
309
|
+
template_name=template_name,
|
310
|
+
tstop=tstop,
|
311
|
+
inj_amp=inj_amp,
|
312
|
+
inj_delay=inj_delay,
|
313
|
+
inj_dur=inj_dur,
|
314
|
+
**kwargs,
|
315
|
+
)
|
283
316
|
self.inj_stop = inj_delay + inj_dur
|
284
317
|
self.method = method
|
285
|
-
self.tau_methods = {
|
318
|
+
self.tau_methods = {
|
319
|
+
"simple": self.tau_simple,
|
320
|
+
"exp2": self.tau_double_exponential,
|
321
|
+
"exp": self.tau_single_exponential,
|
322
|
+
}
|
286
323
|
|
287
324
|
def tau_simple(self):
|
288
325
|
"""
|
289
326
|
Calculate membrane time constant using the simple 0.632 criterion method.
|
290
|
-
|
327
|
+
|
291
328
|
This method calculates the membrane time constant by finding the time it takes
|
292
|
-
for the membrane potential to reach 63.2% (1-1/e) of its final value after
|
329
|
+
for the membrane potential to reach 63.2% (1-1/e) of its final value after
|
293
330
|
a step current injection.
|
294
|
-
|
331
|
+
|
295
332
|
Returns:
|
296
333
|
--------
|
297
334
|
callable
|
298
335
|
A function that prints the calculation details when called.
|
299
|
-
|
336
|
+
|
300
337
|
Notes:
|
301
338
|
------
|
302
339
|
Sets the following attributes:
|
@@ -308,19 +345,22 @@ class Passive(CurrentClamp):
|
|
308
345
|
|
309
346
|
def print_calc():
|
310
347
|
print()
|
311
|
-
print(
|
312
|
-
print(
|
313
|
-
print(
|
314
|
-
|
315
|
-
|
348
|
+
print("Tau Calculation: time until 63.2% of dV")
|
349
|
+
print("v_rest + 0.632*(v_final-v_rest)")
|
350
|
+
print(
|
351
|
+
f"{self.v_rest:.2f} + 0.632*({self.cell_v_final:.2f}-({self.v_rest:.2f})) = {v_t_const:.2f} (mV)"
|
352
|
+
)
|
353
|
+
print(f"Time where V = {v_t_const:.2f} (mV) is {self.v_rest_time + self.tau:.2f} (ms)")
|
354
|
+
print(f"{self.v_rest_time + self.tau:.2f} - {self.v_rest_time:g} = {self.tau:.2f} (ms)")
|
316
355
|
print()
|
356
|
+
|
317
357
|
return print_calc
|
318
358
|
|
319
359
|
@staticmethod
|
320
360
|
def single_exponential(t, a0, a, tau):
|
321
361
|
"""
|
322
362
|
Single exponential function for fitting membrane potential response.
|
323
|
-
|
363
|
+
|
324
364
|
Parameters:
|
325
365
|
-----------
|
326
366
|
t : array-like
|
@@ -331,7 +371,7 @@ class Passive(CurrentClamp):
|
|
331
371
|
Amplitude of the exponential component
|
332
372
|
tau : float
|
333
373
|
Time constant of the exponential decay
|
334
|
-
|
374
|
+
|
335
375
|
Returns:
|
336
376
|
--------
|
337
377
|
array-like
|
@@ -342,18 +382,18 @@ class Passive(CurrentClamp):
|
|
342
382
|
def tau_single_exponential(self):
|
343
383
|
"""
|
344
384
|
Calculate membrane time constant by fitting a single exponential curve.
|
345
|
-
|
385
|
+
|
346
386
|
This method:
|
347
387
|
1. Identifies the peak response (for sag characterization)
|
348
388
|
2. Falls back to simple method for initial estimate
|
349
389
|
3. Fits a single exponential function to the membrane potential response
|
350
390
|
4. Sets tau to the exponential time constant
|
351
|
-
|
391
|
+
|
352
392
|
Returns:
|
353
393
|
--------
|
354
394
|
callable
|
355
395
|
A function that prints the calculation details when called.
|
356
|
-
|
396
|
+
|
357
397
|
Notes:
|
358
398
|
------
|
359
399
|
Sets the following attributes:
|
@@ -376,33 +416,44 @@ class Passive(CurrentClamp):
|
|
376
416
|
|
377
417
|
p0 = (self.v_diff, self.tau) # initial estimate
|
378
418
|
v0 = self.v_rest
|
419
|
+
|
379
420
|
def fit_func(t, a, tau):
|
380
421
|
return self.single_exponential(t, a0=v0 - a, a=a, tau=tau)
|
422
|
+
|
381
423
|
bounds = ((-np.inf, 1e-3), np.inf)
|
382
|
-
popt, self.pcov = curve_fit(
|
424
|
+
popt, self.pcov = curve_fit(
|
425
|
+
fit_func, self.t_vec_inj, self.v_vec_inj, p0=p0, bounds=bounds, maxfev=10000
|
426
|
+
)
|
383
427
|
self.popt = np.insert(popt, 0, v0 - popt[0])
|
384
428
|
self.tau = self.popt[2]
|
385
429
|
|
386
430
|
def print_calc():
|
387
431
|
print()
|
388
|
-
print(
|
389
|
-
|
390
|
-
|
391
|
-
print(f
|
432
|
+
print(
|
433
|
+
"Tau Calculation: Fit a single exponential curve to the membrane potential response"
|
434
|
+
)
|
435
|
+
print("f(t) = a0 + a*exp(-t/tau)")
|
436
|
+
print(
|
437
|
+
f"Fit parameters: (a0, a, tau) = ({self.popt[0]:.2f}, {self.popt[1]:.2f}, {self.popt[2]:.2f})"
|
438
|
+
)
|
439
|
+
print(
|
440
|
+
f"Membrane time constant is determined from the exponential term: {self.tau:.2f} (ms)"
|
441
|
+
)
|
392
442
|
print()
|
393
|
-
print(
|
394
|
-
print(
|
443
|
+
print("Sag potential: v_sag = v_peak - v_final = %.2f (mV)" % self.v_sag)
|
444
|
+
print("Normalized sag potential: v_sag / (v_peak - v_rest) = %.3f" % self.sag_norm)
|
395
445
|
print()
|
446
|
+
|
396
447
|
return print_calc
|
397
448
|
|
398
449
|
@staticmethod
|
399
450
|
def double_exponential(t, a0, a1, a2, tau1, tau2):
|
400
451
|
"""
|
401
452
|
Double exponential function for fitting membrane potential response.
|
402
|
-
|
453
|
+
|
403
454
|
This function is particularly useful for modeling cells with sag responses,
|
404
455
|
where the membrane potential shows two distinct time constants.
|
405
|
-
|
456
|
+
|
406
457
|
Parameters:
|
407
458
|
-----------
|
408
459
|
t : array-like
|
@@ -417,7 +468,7 @@ class Passive(CurrentClamp):
|
|
417
468
|
Time constant of the first exponential component
|
418
469
|
tau2 : float
|
419
470
|
Time constant of the second exponential component
|
420
|
-
|
471
|
+
|
421
472
|
Returns:
|
422
473
|
--------
|
423
474
|
array-like
|
@@ -428,15 +479,15 @@ class Passive(CurrentClamp):
|
|
428
479
|
def tau_double_exponential(self):
|
429
480
|
"""
|
430
481
|
Calculate membrane time constant by fitting a double exponential curve.
|
431
|
-
|
482
|
+
|
432
483
|
This method is useful for cells with sag responses that cannot be
|
433
484
|
fitted well with a single exponential.
|
434
|
-
|
485
|
+
|
435
486
|
Returns:
|
436
487
|
--------
|
437
488
|
callable
|
438
489
|
A function that prints the calculation details when called.
|
439
|
-
|
490
|
+
|
440
491
|
Notes:
|
441
492
|
------
|
442
493
|
Sets the following attributes:
|
@@ -458,30 +509,43 @@ class Passive(CurrentClamp):
|
|
458
509
|
self.tau_simple()
|
459
510
|
p0 = (self.v_sag, -self.v_max_diff, self.t_peak, self.tau) # initial estimate
|
460
511
|
v0 = self.v_rest
|
512
|
+
|
461
513
|
def fit_func(t, a1, a2, tau1, tau2):
|
462
514
|
return self.double_exponential(t, v0 - a1 - a2, a1, a2, tau1, tau2)
|
515
|
+
|
463
516
|
bounds = ((-np.inf, -np.inf, 1e-3, 1e-3), np.inf)
|
464
|
-
popt, self.pcov = curve_fit(
|
517
|
+
popt, self.pcov = curve_fit(
|
518
|
+
fit_func, self.t_vec_inj, self.v_vec_inj, p0=p0, bounds=bounds, maxfev=10000
|
519
|
+
)
|
465
520
|
self.popt = np.insert(popt, 0, v0 - sum(popt[:2]))
|
466
521
|
self.tau = max(self.popt[-2:])
|
467
522
|
|
468
523
|
def print_calc():
|
469
524
|
print()
|
470
|
-
print(
|
471
|
-
|
472
|
-
|
473
|
-
print(
|
474
|
-
print(
|
525
|
+
print(
|
526
|
+
"Tau Calculation: Fit a double exponential curve to the membrane potential response"
|
527
|
+
)
|
528
|
+
print("f(t) = a0 + a1*exp(-t/tau1) + a2*exp(-t/tau2)")
|
529
|
+
print("Constrained by initial value: f(0) = a0 + a1 + a2 = v_rest")
|
530
|
+
print(
|
531
|
+
"Fit parameters: (a0, a1, a2, tau1, tau2) = ("
|
532
|
+
+ ", ".join(f"{x:.2f}" for x in self.popt)
|
533
|
+
+ ")"
|
534
|
+
)
|
535
|
+
print(
|
536
|
+
f"Membrane time constant is determined from the slowest exponential term: {self.tau:.2f} (ms)"
|
537
|
+
)
|
475
538
|
print()
|
476
|
-
print(
|
477
|
-
print(
|
539
|
+
print("Sag potential: v_sag = v_peak - v_final = %.2f (mV)" % self.v_sag)
|
540
|
+
print("Normalized sag potential: v_sag / (v_peak - v_rest) = %.3f" % self.sag_norm)
|
478
541
|
print()
|
542
|
+
|
479
543
|
return print_calc
|
480
544
|
|
481
545
|
def double_exponential_fit(self):
|
482
546
|
"""
|
483
547
|
Get the double exponential fit values for plotting.
|
484
|
-
|
548
|
+
|
485
549
|
Returns:
|
486
550
|
--------
|
487
551
|
tuple
|
@@ -492,11 +556,11 @@ class Passive(CurrentClamp):
|
|
492
556
|
t_vec = self.v_rest_time + self.t_vec_inj
|
493
557
|
v_fit = self.double_exponential(self.t_vec_inj, *self.popt)
|
494
558
|
return t_vec, v_fit
|
495
|
-
|
559
|
+
|
496
560
|
def single_exponential_fit(self):
|
497
561
|
"""
|
498
562
|
Get the single exponential fit values for plotting.
|
499
|
-
|
563
|
+
|
500
564
|
Returns:
|
501
565
|
--------
|
502
566
|
tuple
|
@@ -511,19 +575,19 @@ class Passive(CurrentClamp):
|
|
511
575
|
def execute(self):
|
512
576
|
"""
|
513
577
|
Run the simulation and calculate passive membrane properties.
|
514
|
-
|
578
|
+
|
515
579
|
This method:
|
516
580
|
1. Runs the NEURON simulation
|
517
581
|
2. Extracts membrane potential at rest and steady-state
|
518
582
|
3. Calculates input resistance from the step response
|
519
583
|
4. Calculates membrane time constant using the specified method
|
520
584
|
5. Prints detailed calculations for educational purposes
|
521
|
-
|
585
|
+
|
522
586
|
Returns:
|
523
587
|
--------
|
524
588
|
tuple
|
525
589
|
(time_vector, voltage_vector) from the simulation
|
526
|
-
|
590
|
+
|
527
591
|
Notes:
|
528
592
|
------
|
529
593
|
Sets several attributes including:
|
@@ -553,28 +617,42 @@ class Passive(CurrentClamp):
|
|
553
617
|
print_calc = self.tau_methods.get(self.method, self.tau_simple)()
|
554
618
|
|
555
619
|
print()
|
556
|
-
print(f
|
557
|
-
print(f
|
558
|
-
print(f
|
620
|
+
print(f"V Rest: {self.v_rest:.2f} (mV)")
|
621
|
+
print(f"Resistance: {self.r_in:.2f} (MOhms)")
|
622
|
+
print(f"Membrane time constant: {self.tau:.2f} (ms)")
|
559
623
|
print()
|
560
|
-
print(f
|
561
|
-
print(f
|
624
|
+
print(f"V_rest Calculation: Voltage taken at time {self.v_rest_time:.1f} (ms) is")
|
625
|
+
print(f"{self.v_rest:.2f} (mV)")
|
562
626
|
print()
|
563
|
-
print(
|
564
|
-
print(f
|
565
|
-
print(
|
627
|
+
print("R_in Calculation: dV/dI = (v_final-v_rest)/(i_final-i_start)")
|
628
|
+
print(f"({self.cell_v_final:.2f} - ({self.v_rest:.2f})) / ({self.inj_amp:g} - 0)")
|
629
|
+
print(
|
630
|
+
f"{np.sign(self.inj_amp) * self.v_diff:.2f} (mV) / {np.abs(self.inj_amp)} (nA) = {self.r_in:.2f} (MOhms)"
|
631
|
+
)
|
566
632
|
print_calc()
|
567
633
|
|
568
634
|
return self.t_vec.to_python(), self.v_vec.to_python()
|
569
635
|
|
570
636
|
|
571
637
|
class FI(object):
|
572
|
-
def __init__(
|
573
|
-
|
574
|
-
|
638
|
+
def __init__(
|
639
|
+
self,
|
640
|
+
template_name,
|
641
|
+
post_init_function=None,
|
642
|
+
i_start=0.0,
|
643
|
+
i_stop=1050.0,
|
644
|
+
i_increment=100.0,
|
645
|
+
tstart=50.0,
|
646
|
+
tdur=1000.0,
|
647
|
+
threshold=0.0,
|
648
|
+
record_sec="soma",
|
649
|
+
record_loc=0.5,
|
650
|
+
inj_sec="soma",
|
651
|
+
inj_loc=0.5,
|
652
|
+
):
|
575
653
|
"""
|
576
654
|
Initialize a frequency-current (F-I) curve simulation environment.
|
577
|
-
|
655
|
+
|
578
656
|
Parameters:
|
579
657
|
-----------
|
580
658
|
template_name : str or callable
|
@@ -602,16 +680,18 @@ class FI(object):
|
|
602
680
|
Section for current injection. Same format as record_sec. Default is 'soma'.
|
603
681
|
inj_loc : float, optional
|
604
682
|
Location (0-1) within section for current injection. Default is 0.5.
|
605
|
-
|
683
|
+
|
606
684
|
Notes:
|
607
685
|
------
|
608
686
|
This class creates multiple instances of the cell model, one for each
|
609
687
|
current amplitude to be tested, allowing all simulations to be run
|
610
688
|
in a single call to NEURON's run() function.
|
611
689
|
"""
|
612
|
-
self.create_cell =
|
690
|
+
self.create_cell = (
|
691
|
+
getattr(h, template_name) if isinstance(template_name, str) else template_name
|
692
|
+
)
|
613
693
|
self.post_init_function = post_init_function
|
614
|
-
self.i_start = i_start * 1e-3
|
694
|
+
self.i_start = i_start * 1e-3 # pA to nA
|
615
695
|
self.i_stop = i_stop * 1e-3
|
616
696
|
self.i_increment = i_increment * 1e-3
|
617
697
|
self.tstart = tstart
|
@@ -644,26 +724,26 @@ class FI(object):
|
|
644
724
|
def setup(self):
|
645
725
|
"""
|
646
726
|
Set up the simulation environment for frequency-current (F-I) analysis.
|
647
|
-
|
727
|
+
|
648
728
|
For each current amplitude to be tested, this method:
|
649
729
|
1. Creates a current source at the injection site
|
650
730
|
2. Sets up spike detection at the recording site
|
651
731
|
3. Creates vectors to record spike times
|
652
|
-
|
732
|
+
|
653
733
|
Notes:
|
654
734
|
------
|
655
735
|
This preparation allows multiple simulations to be run with different
|
656
736
|
current amplitudes in a single call to h.run().
|
657
737
|
"""
|
658
738
|
for cell, amp in zip(self.cells, self.amps):
|
659
|
-
inj_seg, _ = get_target_site(cell, self.inj_sec, self.inj_loc,
|
739
|
+
inj_seg, _ = get_target_site(cell, self.inj_sec, self.inj_loc, "injection")
|
660
740
|
src = h.IClamp(inj_seg)
|
661
741
|
src.delay = self.tstart
|
662
742
|
src.dur = self.tdur
|
663
743
|
src.amp = amp
|
664
744
|
self.sources.append(src)
|
665
745
|
|
666
|
-
rec_seg, rec_sec = get_target_site(cell, self.record_sec, self.record_loc,
|
746
|
+
rec_seg, rec_sec = get_target_site(cell, self.record_sec, self.record_loc, "recording")
|
667
747
|
nc = h.NetCon(rec_seg._ref_v, None, sec=rec_sec)
|
668
748
|
nc.threshold = self.threshold
|
669
749
|
spvec = h.Vector()
|
@@ -671,18 +751,18 @@ class FI(object):
|
|
671
751
|
self.ncs.append(nc)
|
672
752
|
self.tspk_vecs.append(spvec)
|
673
753
|
|
674
|
-
print(f
|
675
|
-
print(f
|
754
|
+
print(f"Injection location: {inj_seg}")
|
755
|
+
print(f"Recording: {rec_seg}._ref_v")
|
676
756
|
|
677
757
|
def execute(self):
|
678
758
|
"""
|
679
759
|
Run the simulation and count spikes for each current amplitude.
|
680
|
-
|
760
|
+
|
681
761
|
This method:
|
682
762
|
1. Initializes and runs a single NEURON simulation that evaluates all current amplitudes
|
683
763
|
2. Counts spikes for each current amplitude
|
684
764
|
3. Prints a summary of results in tabular format
|
685
|
-
|
765
|
+
|
686
766
|
Returns:
|
687
767
|
--------
|
688
768
|
tuple
|
@@ -699,22 +779,32 @@ class FI(object):
|
|
699
779
|
print()
|
700
780
|
print("Results")
|
701
781
|
# lets make a df so the results line up nice
|
702
|
-
data = {
|
782
|
+
data = {"Injection (nA):": self.amps, "number of spikes": self.nspks}
|
703
783
|
df = pd.DataFrame(data)
|
704
784
|
print(df)
|
705
|
-
#print(f'Injection (nA): ' + ', '.join(f'{x:g}' for x in self.amps))
|
706
|
-
#print(f'Number of spikes: ' + ', '.join(f'{x:d}' for x in self.nspks))
|
785
|
+
# print(f'Injection (nA): ' + ', '.join(f'{x:g}' for x in self.amps))
|
786
|
+
# print(f'Number of spikes: ' + ', '.join(f'{x:d}' for x in self.nspks))
|
707
787
|
print()
|
708
788
|
|
709
789
|
return self.amps, self.nspks
|
710
790
|
|
711
791
|
|
712
792
|
class ZAP(CurrentClamp):
|
713
|
-
def __init__(
|
714
|
-
|
793
|
+
def __init__(
|
794
|
+
self,
|
795
|
+
template_name,
|
796
|
+
inj_amp=100.0,
|
797
|
+
inj_delay=200.0,
|
798
|
+
inj_dur=15000.0,
|
799
|
+
tstop=15500.0,
|
800
|
+
fstart=0.0,
|
801
|
+
fend=15.0,
|
802
|
+
chirp_type=None,
|
803
|
+
**kwargs,
|
804
|
+
):
|
715
805
|
"""
|
716
806
|
Initialize a ZAP (impedance amplitude profile) simulation environment.
|
717
|
-
|
807
|
+
|
718
808
|
Parameters:
|
719
809
|
-----------
|
720
810
|
template_name : str or callable
|
@@ -736,35 +826,41 @@ class ZAP(CurrentClamp):
|
|
736
826
|
Type of chirp current determining how frequency increases over time:
|
737
827
|
- 'linear': Linear increase in frequency (default if None)
|
738
828
|
- 'exponential': Exponential increase in frequency
|
739
|
-
**kwargs :
|
829
|
+
**kwargs :
|
740
830
|
Additional keyword arguments to pass to the parent CurrentClamp constructor.
|
741
|
-
|
831
|
+
|
742
832
|
Notes:
|
743
833
|
------
|
744
834
|
This class is designed for measuring the frequency-dependent impedance profile
|
745
835
|
of a neuron using a chirp current that sweeps through frequencies.
|
746
|
-
|
836
|
+
|
747
837
|
Raises:
|
748
838
|
-------
|
749
839
|
AssertionError
|
750
840
|
- If inj_amp is zero
|
751
841
|
- If chirp_type is 'exponential' and either fstart or fend is <= 0
|
752
842
|
"""
|
753
|
-
assert
|
754
|
-
super().__init__(
|
755
|
-
|
843
|
+
assert inj_amp != 0
|
844
|
+
super().__init__(
|
845
|
+
template_name=template_name,
|
846
|
+
tstop=tstop,
|
847
|
+
inj_amp=inj_amp,
|
848
|
+
inj_delay=inj_delay,
|
849
|
+
inj_dur=inj_dur,
|
850
|
+
**kwargs,
|
851
|
+
)
|
756
852
|
self.inj_stop = inj_delay + inj_dur
|
757
853
|
self.fstart = fstart
|
758
854
|
self.fend = fend
|
759
855
|
self.chirp_type = chirp_type
|
760
|
-
self.chirp_func = {
|
761
|
-
if chirp_type==
|
762
|
-
assert
|
856
|
+
self.chirp_func = {"linear": self.linear_chirp, "exponential": self.exponential_chirp}
|
857
|
+
if chirp_type == "exponential":
|
858
|
+
assert fstart > 0 and fend > 0
|
763
859
|
|
764
860
|
def linear_chirp(self, t, f0, f1):
|
765
861
|
"""
|
766
862
|
Generate a chirp current with linearly increasing frequency.
|
767
|
-
|
863
|
+
|
768
864
|
Parameters:
|
769
865
|
-----------
|
770
866
|
t : ndarray
|
@@ -773,11 +869,11 @@ class ZAP(CurrentClamp):
|
|
773
869
|
Start frequency (kHz)
|
774
870
|
f1 : float
|
775
871
|
End frequency (kHz)
|
776
|
-
|
872
|
+
|
777
873
|
Returns:
|
778
874
|
--------
|
779
875
|
ndarray
|
780
|
-
Current values with amplitude self.inj_amp and frequency
|
876
|
+
Current values with amplitude self.inj_amp and frequency
|
781
877
|
increasing linearly from f0 to f1 Hz over time t
|
782
878
|
"""
|
783
879
|
return self.inj_amp * np.sin(np.pi * (2 * f0 + (f1 - f0) / t[-1] * t) * t)
|
@@ -785,7 +881,7 @@ class ZAP(CurrentClamp):
|
|
785
881
|
def exponential_chirp(self, t, f0, f1):
|
786
882
|
"""
|
787
883
|
Generate a chirp current with exponentially increasing frequency.
|
788
|
-
|
884
|
+
|
789
885
|
Parameters:
|
790
886
|
-----------
|
791
887
|
t : ndarray
|
@@ -794,13 +890,13 @@ class ZAP(CurrentClamp):
|
|
794
890
|
Start frequency (kHz), must be > 0
|
795
891
|
f1 : float
|
796
892
|
End frequency (kHz), must be > 0
|
797
|
-
|
893
|
+
|
798
894
|
Returns:
|
799
895
|
--------
|
800
896
|
ndarray
|
801
|
-
Current values with amplitude self.inj_amp and frequency
|
897
|
+
Current values with amplitude self.inj_amp and frequency
|
802
898
|
increasing exponentially from f0 to f1 Hz over time t
|
803
|
-
|
899
|
+
|
804
900
|
Notes:
|
805
901
|
------
|
806
902
|
For exponential chirp, both f0 and f1 must be positive.
|
@@ -811,12 +907,12 @@ class ZAP(CurrentClamp):
|
|
811
907
|
def zap_current(self):
|
812
908
|
"""
|
813
909
|
Create a frequency-modulated (chirp) current for probing impedance.
|
814
|
-
|
910
|
+
|
815
911
|
This method:
|
816
912
|
1. Sets up time vectors for the simulation and current injection
|
817
913
|
2. Creates a chirp current based on the specified parameters (linear or exponential)
|
818
914
|
3. Prepares the current vector for NEURON playback
|
819
|
-
|
915
|
+
|
820
916
|
Notes:
|
821
917
|
------
|
822
918
|
The chirp current increases in frequency from fstart to fend Hz over the duration
|
@@ -828,13 +924,13 @@ class ZAP(CurrentClamp):
|
|
828
924
|
self.index_v_final = int(self.inj_stop / dt)
|
829
925
|
|
830
926
|
t = np.arange(int(self.tstop / dt) + 1) * dt
|
831
|
-
t_inj = t[:self.index_v_final - self.index_v_rest + 1]
|
832
|
-
f0 = self.fstart * 1e-3
|
927
|
+
t_inj = t[: self.index_v_final - self.index_v_rest + 1]
|
928
|
+
f0 = self.fstart * 1e-3 # Hz to 1/ms
|
833
929
|
f1 = self.fend * 1e-3
|
834
930
|
chirp_func = self.chirp_func.get(self.chirp_type, self.linear_chirp)
|
835
931
|
self.zap_vec_inj = chirp_func(t_inj, f0, f1)
|
836
932
|
i_inj = np.zeros_like(t)
|
837
|
-
i_inj[self.index_v_rest:self.index_v_final + 1] = self.zap_vec_inj
|
933
|
+
i_inj[self.index_v_rest : self.index_v_final + 1] = self.zap_vec_inj
|
838
934
|
|
839
935
|
self.zap_vec = h.Vector()
|
840
936
|
self.zap_vec.from_python(i_inj)
|
@@ -843,50 +939,52 @@ class ZAP(CurrentClamp):
|
|
843
939
|
def get_impedance(self, smooth=1):
|
844
940
|
"""
|
845
941
|
Calculate and extract the frequency-dependent impedance profile.
|
846
|
-
|
942
|
+
|
847
943
|
This method:
|
848
944
|
1. Filters the impedance to the frequency range of interest
|
849
945
|
2. Optionally applies smoothing to reduce noise
|
850
946
|
3. Identifies the resonant frequency (peak impedance)
|
851
|
-
|
947
|
+
|
852
948
|
Parameters:
|
853
949
|
-----------
|
854
950
|
smooth : int, optional
|
855
951
|
Window size for smoothing the impedance. Default is 1 (no smoothing).
|
856
|
-
|
952
|
+
|
857
953
|
Returns:
|
858
954
|
--------
|
859
955
|
tuple
|
860
956
|
(frequencies, impedance_values) in the range of interest
|
861
|
-
|
957
|
+
|
862
958
|
Notes:
|
863
959
|
------
|
864
960
|
Sets self.peak_freq to the resonant frequency (frequency of maximum impedance).
|
865
961
|
"""
|
866
|
-
f_idx = (self.freq > min(self.fstart, self.fend)) & (
|
962
|
+
f_idx = (self.freq > min(self.fstart, self.fend)) & (
|
963
|
+
self.freq < max(self.fstart, self.fend)
|
964
|
+
)
|
867
965
|
impedance = self.impedance
|
868
966
|
if smooth > 1:
|
869
|
-
impedance = np.convolve(impedance, np.ones(smooth) / smooth, mode=
|
967
|
+
impedance = np.convolve(impedance, np.ones(smooth) / smooth, mode="same")
|
870
968
|
freq, impedance = self.freq[f_idx], impedance[f_idx]
|
871
969
|
self.peak_freq = freq[np.argmax(impedance)]
|
872
|
-
print(f
|
970
|
+
print(f"Resonant Peak Frequency: {self.peak_freq:.3g} (Hz)")
|
873
971
|
return freq, impedance
|
874
972
|
|
875
973
|
def execute(self) -> Tuple[list, list]:
|
876
974
|
"""
|
877
975
|
Run the ZAP simulation and calculate the impedance profile.
|
878
|
-
|
976
|
+
|
879
977
|
This method:
|
880
978
|
1. Sets up the chirp current
|
881
979
|
2. Runs the NEURON simulation
|
882
980
|
3. Calculates the impedance using FFT
|
883
981
|
4. Prints a summary of the frequency range and analysis method
|
884
|
-
|
982
|
+
|
885
983
|
Returns:
|
886
984
|
--------
|
887
985
|
tuple
|
888
986
|
(time_vector, voltage_vector) from the simulation
|
889
|
-
|
987
|
+
|
890
988
|
Notes:
|
891
989
|
------
|
892
990
|
Sets several attributes including:
|
@@ -909,21 +1007,26 @@ class ZAP(CurrentClamp):
|
|
909
1007
|
self.t_vec_inj = np.array(self.t_vec)[t_idx] - self.v_rest_time
|
910
1008
|
|
911
1009
|
self.cell_v_amp_max = np.abs(self.v_vec_inj).max()
|
912
|
-
self.Z = np.fft.rfft(self.v_vec_inj) / np.fft.rfft(self.zap_vec_inj)
|
913
|
-
self.freq = np.fft.rfftfreq(self.zap_vec_inj.size, d=self.dt * 1e-3)
|
1010
|
+
self.Z = np.fft.rfft(self.v_vec_inj) / np.fft.rfft(self.zap_vec_inj) # MOhms
|
1011
|
+
self.freq = np.fft.rfftfreq(self.zap_vec_inj.size, d=self.dt * 1e-3) # ms to sec
|
914
1012
|
self.impedance = np.abs(self.Z)
|
915
1013
|
|
916
1014
|
print()
|
917
|
-
print(
|
918
|
-
|
919
|
-
|
920
|
-
|
1015
|
+
print(
|
1016
|
+
"Chirp current injection with frequency changing from "
|
1017
|
+
f"{self.fstart:g} to {self.fend:g} Hz over {self.inj_dur * 1e-3:g} seconds"
|
1018
|
+
)
|
1019
|
+
print(
|
1020
|
+
"Impedance is calculated as the ratio of FFT amplitude "
|
1021
|
+
"of membrane voltage to FFT amplitude of chirp current"
|
1022
|
+
)
|
921
1023
|
print()
|
922
1024
|
return self.t_vec.to_python(), self.v_vec.to_python()
|
923
1025
|
|
924
1026
|
|
925
|
-
class Profiler
|
1027
|
+
class Profiler:
|
926
1028
|
"""All in one single cell profiler"""
|
1029
|
+
|
927
1030
|
def __init__(self, template_dir: str = None, mechanism_dir: str = None, dt=None):
|
928
1031
|
self.template_dir = None
|
929
1032
|
self.mechanism_dir = None
|
@@ -942,8 +1045,12 @@ class Profiler():
|
|
942
1045
|
h.steps_per_ms = 1 / h.dt
|
943
1046
|
|
944
1047
|
def load_templates(self, hoc_template_file=None):
|
945
|
-
if self.templates is None:
|
946
|
-
if
|
1048
|
+
if self.templates is None: # Can really only do this once
|
1049
|
+
if (
|
1050
|
+
self.mechanism_dir != "./"
|
1051
|
+
and self.mechanism_dir != "."
|
1052
|
+
and self.mechanism_dir != "././"
|
1053
|
+
):
|
947
1054
|
neuron.load_mechanisms(self.mechanism_dir)
|
948
1055
|
h_base = set(dir(h))
|
949
1056
|
|
@@ -966,9 +1073,16 @@ class Profiler():
|
|
966
1073
|
|
967
1074
|
return self.templates
|
968
1075
|
|
969
|
-
def passive_properties(
|
970
|
-
|
971
|
-
|
1076
|
+
def passive_properties(
|
1077
|
+
self,
|
1078
|
+
template_name: str,
|
1079
|
+
post_init_function: str = None,
|
1080
|
+
record_sec: str = "soma",
|
1081
|
+
inj_sec: str = "soma",
|
1082
|
+
plot: bool = True,
|
1083
|
+
method=None,
|
1084
|
+
**kwargs,
|
1085
|
+
) -> Tuple[list, list]:
|
972
1086
|
"""
|
973
1087
|
Calculates passive properties for the specified cell template_name
|
974
1088
|
|
@@ -991,45 +1105,67 @@ class Profiler():
|
|
991
1105
|
extra key word arguments for Passive()
|
992
1106
|
|
993
1107
|
Returns time (ms), membrane voltage (mV)
|
994
|
-
|
995
|
-
passive = Passive(
|
996
|
-
|
1108
|
+
"""
|
1109
|
+
passive = Passive(
|
1110
|
+
template_name,
|
1111
|
+
post_init_function=post_init_function,
|
1112
|
+
record_sec=record_sec,
|
1113
|
+
inj_sec=inj_sec,
|
1114
|
+
method=method,
|
1115
|
+
**kwargs,
|
1116
|
+
)
|
997
1117
|
time, amp = passive.execute()
|
998
1118
|
|
999
1119
|
if plot:
|
1000
1120
|
plt.figure()
|
1001
1121
|
plt.plot(time, amp)
|
1002
|
-
if passive.method ==
|
1003
|
-
plt.plot(*passive.double_exponential_fit(),
|
1122
|
+
if passive.method == "exp2":
|
1123
|
+
plt.plot(*passive.double_exponential_fit(), "r:", label="double exponential fit")
|
1004
1124
|
plt.legend()
|
1005
1125
|
plt.title("Passive Cell Current Injection")
|
1006
|
-
plt.xlabel(
|
1007
|
-
plt.ylabel(
|
1126
|
+
plt.xlabel("Time (ms)")
|
1127
|
+
plt.ylabel("Membrane Potential (mV)")
|
1008
1128
|
plt.show()
|
1009
1129
|
|
1010
1130
|
return time, amp
|
1011
1131
|
|
1012
|
-
def current_injection(
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1132
|
+
def current_injection(
|
1133
|
+
self,
|
1134
|
+
template_name: str,
|
1135
|
+
post_init_function: str = None,
|
1136
|
+
record_sec: str = "soma",
|
1137
|
+
inj_sec: str = "soma",
|
1138
|
+
plot: bool = True,
|
1139
|
+
**kwargs,
|
1140
|
+
) -> Tuple[list, list]:
|
1141
|
+
ccl = CurrentClamp(
|
1142
|
+
template_name,
|
1143
|
+
post_init_function=post_init_function,
|
1144
|
+
record_sec=record_sec,
|
1145
|
+
inj_sec=inj_sec,
|
1146
|
+
**kwargs,
|
1147
|
+
)
|
1018
1148
|
time, amp = ccl.execute()
|
1019
1149
|
|
1020
1150
|
if plot:
|
1021
1151
|
plt.figure()
|
1022
1152
|
plt.plot(time, amp)
|
1023
1153
|
plt.title("Current Injection")
|
1024
|
-
plt.xlabel(
|
1025
|
-
plt.ylabel(
|
1154
|
+
plt.xlabel("Time (ms)")
|
1155
|
+
plt.ylabel("Membrane Potential (mV)")
|
1026
1156
|
plt.show()
|
1027
1157
|
|
1028
1158
|
return time, amp
|
1029
1159
|
|
1030
|
-
def fi_curve(
|
1031
|
-
|
1032
|
-
|
1160
|
+
def fi_curve(
|
1161
|
+
self,
|
1162
|
+
template_name: str,
|
1163
|
+
post_init_function: str = None,
|
1164
|
+
record_sec: str = "soma",
|
1165
|
+
inj_sec: str = "soma",
|
1166
|
+
plot: bool = True,
|
1167
|
+
**kwargs,
|
1168
|
+
) -> Tuple[list, list]:
|
1033
1169
|
"""
|
1034
1170
|
Calculates an FI curve for the specified cell template_name
|
1035
1171
|
|
@@ -1050,23 +1186,36 @@ class Profiler():
|
|
1050
1186
|
Returns the injection amplitudes (nA) used, number of spikes per amplitude supplied
|
1051
1187
|
list(amps), list(# of spikes)
|
1052
1188
|
"""
|
1053
|
-
fi = FI(
|
1054
|
-
|
1189
|
+
fi = FI(
|
1190
|
+
template_name,
|
1191
|
+
post_init_function=post_init_function,
|
1192
|
+
record_sec=record_sec,
|
1193
|
+
inj_sec=inj_sec,
|
1194
|
+
**kwargs,
|
1195
|
+
)
|
1055
1196
|
amp, nspk = fi.execute()
|
1056
1197
|
|
1057
1198
|
if plot:
|
1058
1199
|
plt.figure()
|
1059
1200
|
plt.plot(amp, nspk)
|
1060
1201
|
plt.title("FI Curve")
|
1061
|
-
plt.xlabel(
|
1062
|
-
plt.ylabel(
|
1202
|
+
plt.xlabel("Injection (nA)")
|
1203
|
+
plt.ylabel("# Spikes")
|
1063
1204
|
plt.show()
|
1064
1205
|
|
1065
1206
|
return amp, nspk
|
1066
1207
|
|
1067
|
-
def impedance_amplitude_profile(
|
1068
|
-
|
1069
|
-
|
1208
|
+
def impedance_amplitude_profile(
|
1209
|
+
self,
|
1210
|
+
template_name: str,
|
1211
|
+
post_init_function: str = None,
|
1212
|
+
record_sec: str = "soma",
|
1213
|
+
inj_sec: str = "soma",
|
1214
|
+
plot: bool = True,
|
1215
|
+
chirp_type=None,
|
1216
|
+
smooth: int = 9,
|
1217
|
+
**kwargs,
|
1218
|
+
) -> Tuple[list, list]:
|
1070
1219
|
"""
|
1071
1220
|
chirp_type: str
|
1072
1221
|
Type of chirp current (see ZAP)
|
@@ -1075,32 +1224,39 @@ class Profiler():
|
|
1075
1224
|
**kwargs:
|
1076
1225
|
extra key word arguments for ZAP()
|
1077
1226
|
"""
|
1078
|
-
zap = ZAP(
|
1079
|
-
|
1227
|
+
zap = ZAP(
|
1228
|
+
template_name,
|
1229
|
+
post_init_function=post_init_function,
|
1230
|
+
record_sec=record_sec,
|
1231
|
+
inj_sec=inj_sec,
|
1232
|
+
chirp_type=chirp_type,
|
1233
|
+
**kwargs,
|
1234
|
+
)
|
1080
1235
|
time, amp = zap.execute()
|
1081
1236
|
|
1082
1237
|
if plot:
|
1083
1238
|
plt.figure()
|
1084
1239
|
plt.plot(time, amp)
|
1085
1240
|
plt.title("ZAP Response")
|
1086
|
-
plt.xlabel(
|
1087
|
-
plt.ylabel(
|
1241
|
+
plt.xlabel("Time (ms)")
|
1242
|
+
plt.ylabel("Membrane Potential (mV)")
|
1088
1243
|
|
1089
1244
|
plt.figure()
|
1090
1245
|
plt.plot(time, zap.zap_vec)
|
1091
|
-
plt.title(
|
1092
|
-
plt.xlabel(
|
1093
|
-
plt.ylabel(
|
1246
|
+
plt.title("ZAP Current")
|
1247
|
+
plt.xlabel("Time (ms)")
|
1248
|
+
plt.ylabel("Current Injection (nA)")
|
1094
1249
|
|
1095
1250
|
plt.figure()
|
1096
1251
|
plt.plot(*zap.get_impedance(smooth=smooth))
|
1097
|
-
plt.title(
|
1098
|
-
plt.xlabel(
|
1099
|
-
plt.ylabel(
|
1252
|
+
plt.title("Impedance Amplitude Profile")
|
1253
|
+
plt.xlabel("Frequency (Hz)")
|
1254
|
+
plt.ylabel("Impedance (MOhms)")
|
1100
1255
|
plt.show()
|
1101
1256
|
|
1102
1257
|
return time, amp
|
1103
1258
|
|
1259
|
+
|
1104
1260
|
# Example usage
|
1105
1261
|
# profiler = Profiler('./temp/templates', './temp/mechanisms/modfiles')
|
1106
1262
|
# profiler.passive_properties('Cell_Cf')
|
@@ -1108,8 +1264,14 @@ class Profiler():
|
|
1108
1264
|
# profiler.current_injection('Cell_Cf', post_init_function="insert_mechs(123)", inj_amp=300, inj_delay=100)
|
1109
1265
|
|
1110
1266
|
|
1111
|
-
def run_and_plot(
|
1112
|
-
|
1267
|
+
def run_and_plot(
|
1268
|
+
sim,
|
1269
|
+
title=None,
|
1270
|
+
xlabel="Time (ms)",
|
1271
|
+
ylabel="Membrane Potential (mV)",
|
1272
|
+
plot=True,
|
1273
|
+
plot_injection_only=False,
|
1274
|
+
):
|
1113
1275
|
"""Helper function for running simulation and plot
|
1114
1276
|
sim: instance of the simulation class in this module
|
1115
1277
|
title, xlabel, ylabel: plot labels
|