bluecellulab 2.6.49__py3-none-any.whl → 2.6.51__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.

Potentially problematic release.


This version of bluecellulab might be problematic. Click here for more details.

@@ -3,14 +3,31 @@ try:
3
3
  import efel
4
4
  except ImportError:
5
5
  efel = None
6
+ from itertools import islice
7
+ from itertools import repeat
8
+ import logging
9
+ from matplotlib.collections import LineCollection
10
+ import matplotlib.pyplot as plt
11
+ from multiprocessing import Pool
12
+ import neuron
6
13
  import numpy as np
14
+ import pathlib
15
+ import seaborn as sns
7
16
 
17
+
18
+ from bluecellulab import Cell
8
19
  from bluecellulab.analysis.inject_sequence import run_stimulus
9
20
  from bluecellulab.analysis.plotting import plot_iv_curve, plot_fi_curve
21
+ from bluecellulab.analysis.utils import exp_decay
22
+ from bluecellulab.simulation import Simulation
10
23
  from bluecellulab.stimulus import StimulusFactory
24
+ from bluecellulab.stimulus.circuit_stimulus_definitions import Hyperpolarizing
11
25
  from bluecellulab.tools import calculate_rheobase
12
26
 
13
27
 
28
+ logger = logging.getLogger(__name__)
29
+
30
+
14
31
  def compute_plot_iv_curve(cell,
15
32
  injecting_section="soma[0]",
16
33
  injecting_segment=0.5,
@@ -48,7 +65,7 @@ def compute_plot_iv_curve(cell,
48
65
  post_delay (float, optional): The delay after the stimulation ends before the simulation stops
49
66
  (in ms). Default is 100.0 ms.
50
67
  threshold_voltage (float, optional): The voltage threshold (in mV) for detecting a steady-state
51
- response. Default is -30 mV.
68
+ response. Default is -20 mV.
52
69
  nb_bins (int, optional): The number of discrete current levels between 0 and the maximum current.
53
70
  Default is 11.
54
71
  rheobase (float, optional): The rheobase current (in nA) for the cell. If not provided, it will
@@ -73,30 +90,35 @@ def compute_plot_iv_curve(cell,
73
90
 
74
91
  list_amp = np.linspace(rheobase - 2, rheobase - 0.1, nb_bins) # [nA]
75
92
 
76
- steps = []
77
- times = []
78
- voltages = []
79
93
  # inject step current and record voltage response
80
94
  stim_factory = StimulusFactory(dt=0.1)
81
- for amp in list_amp:
82
- step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
83
- recording = run_stimulus(cell.template_params,
84
- step_stimulus,
85
- section=injecting_section,
86
- segment=injecting_segment,
87
- recording_section=recording_section,
88
- recording_segment=recording_segment)
89
- steps.append(step_stimulus)
90
- times.append(recording.time)
91
- voltages.append(recording.voltage)
95
+ steps = [
96
+ stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
97
+ for amp in list_amp
98
+ ]
99
+
100
+ with Pool(len(steps)) as p:
101
+ recordings = p.starmap(
102
+ run_stimulus,
103
+ zip(
104
+ repeat(cell.template_params),
105
+ steps,
106
+ repeat(injecting_section),
107
+ repeat(injecting_segment),
108
+ repeat(True), # cvode
109
+ repeat(True), # add_hypamp
110
+ repeat(recording_section),
111
+ repeat(recording_segment),
112
+ )
113
+ )
92
114
 
93
115
  steady_states = []
94
116
  # compute steady state response
95
117
  efel.set_setting('Threshold', threshold_voltage)
96
- for voltage, t in zip(voltages, times):
118
+ for recording in recordings:
97
119
  trace = {
98
- 'T': t,
99
- 'V': voltage,
120
+ 'T': recording.time,
121
+ 'V': recording.voltage,
100
122
  'stim_start': [stim_start],
101
123
  'stim_end': [stim_start + duration]
102
124
  }
@@ -158,7 +180,7 @@ def compute_plot_fi_curve(cell,
158
180
  max_current (float, optional): The maximum amplitude of the injected current (in nA).
159
181
  Default is 0.8 nA.
160
182
  threshold_voltage (float, optional): The voltage threshold (in mV) for detecting a steady-state
161
- response. Default is -30 mV.
183
+ response. Default is -20 mV.
162
184
  nb_bins (int, optional): The number of discrete current levels between 0 and `max_current`.
163
185
  Default is 11.
164
186
  rheobase (float, optional): The rheobase current (in nA) for the cell. If not provided, it will
@@ -180,24 +202,30 @@ def compute_plot_fi_curve(cell,
180
202
  rheobase = calculate_rheobase(cell=cell, section=injecting_section, segx=injecting_segment)
181
203
 
182
204
  list_amp = np.linspace(rheobase, max_current, nb_bins) # [nA]
183
- steps = []
184
- spikes = []
185
- # inject step current and record spike response
186
205
  stim_factory = StimulusFactory(dt=0.1)
187
- for amp in list_amp:
188
- step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
189
- recording = run_stimulus(cell.template_params,
190
- step_stimulus,
191
- section=injecting_section,
192
- segment=injecting_segment,
193
- recording_section=recording_section,
194
- recording_segment=recording_segment,
195
- enable_spike_detection=True,
196
- threshold_spike_detection=threshold_voltage)
197
- steps.append(step_stimulus)
198
- spikes.append(recording.spike)
199
-
200
- spike_count = [len(spike) for spike in spikes]
206
+ steps = [
207
+ stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
208
+ for amp in list_amp
209
+ ]
210
+
211
+ with Pool(len(steps)) as p:
212
+ recordings = p.starmap(
213
+ run_stimulus,
214
+ zip(
215
+ repeat(cell.template_params),
216
+ steps,
217
+ repeat(injecting_section),
218
+ repeat(injecting_segment),
219
+ repeat(True), # cvode
220
+ repeat(True), # add_hypamp
221
+ repeat(recording_section),
222
+ repeat(recording_segment),
223
+ repeat(True), # enable_spike_detection
224
+ repeat(threshold_voltage), # threshold_spike_detection
225
+ )
226
+ )
227
+
228
+ spike_count = [len(recording.spike) for recording in recordings]
201
229
 
202
230
  plot_fi_curve(list_amp,
203
231
  spike_count,
@@ -211,3 +239,264 @@ def compute_plot_fi_curve(cell,
211
239
  output_fname=output_fname)
212
240
 
213
241
  return np.array(list_amp), np.array(spike_count)
242
+
243
+
244
+ class BPAP:
245
+ # taken from the examples
246
+
247
+ def __init__(self, cell: Cell) -> None:
248
+ self.cell = cell
249
+ self.dt = 0.025
250
+ self.stim_start = 1000
251
+ self.stim_duration = 3
252
+ self.basal_cmap = sns.color_palette("crest", as_cmap=True)
253
+ self.apical_cmap = sns.color_palette("YlOrBr_r", as_cmap=True)
254
+
255
+ @property
256
+ def start_index(self) -> int:
257
+ """Get the index of the start of the stimulus."""
258
+ return int(self.stim_start / self.dt)
259
+
260
+ @property
261
+ def end_index(self) -> int:
262
+ """Get the index of the end of the stimulus."""
263
+ return int((self.stim_start + self.stim_duration) / self.dt)
264
+
265
+ def get_recordings(self):
266
+ """Get the soma, basal and apical recordings."""
267
+ all_recordings = self.cell.get_allsections_voltagerecordings()
268
+ soma_rec = None
269
+ dend_rec = {}
270
+ apic_rec = {}
271
+ for key, value in all_recordings.items():
272
+ if "soma" in key:
273
+ soma_rec = value
274
+ elif "dend" in key:
275
+ dend_rec[key] = value
276
+ elif "apic" in key:
277
+ apic_rec[key] = value
278
+
279
+ return soma_rec, dend_rec, apic_rec
280
+
281
+ def run(self, duration: float, amplitude: float) -> None:
282
+ """Apply depolarization and hyperpolarization at the same time."""
283
+ sim = Simulation()
284
+ sim.add_cell(self.cell)
285
+ self.cell.add_allsections_voltagerecordings()
286
+ self.cell.add_step(start_time=self.stim_start, stop_time=self.stim_start + self.stim_duration, level=amplitude)
287
+ hyperpolarizing = Hyperpolarizing("single-cell", delay=0, duration=duration)
288
+ self.cell.add_replay_hypamp(hyperpolarizing)
289
+ sim.run(duration, dt=self.dt, cvode=False)
290
+
291
+ def amplitudes(self, recs) -> list[float]:
292
+ """Return amplitude across given sections."""
293
+ efel_feature_name = "maximum_voltage_from_voltagebase"
294
+ traces = [
295
+ {
296
+ 'T': self.cell.get_time(),
297
+ 'V': rec,
298
+ 'stim_start': [self.stim_start],
299
+ 'stim_end': [self.stim_start + self.stim_duration]
300
+ }
301
+ for rec in recs.values()
302
+ ]
303
+ features_results = efel.get_feature_values(traces, [efel_feature_name])
304
+ amps = [feat_res[efel_feature_name][0] for feat_res in features_results]
305
+
306
+ return amps
307
+
308
+ def distances_to_soma(self, recs) -> list[float]:
309
+ """Return the distance to the soma for each section."""
310
+ res = []
311
+ soma = self.cell.soma
312
+ for key in recs.keys():
313
+ section_name = key.rsplit(".")[-1].split("[")[0] # e.g. "dend"
314
+ section_idx = int(key.rsplit(".")[-1].split("[")[1].split("]")[0]) # e.g. 0
315
+ attribute_value = getattr(self.cell.cell.getCell(), section_name)
316
+ section = next(islice(attribute_value, section_idx, None))
317
+ # section e.g. cADpyr_L2TPC_bluecellulab_x[0].dend[0]
318
+ res.append(neuron.h.distance(soma(0.5), section(0.5)))
319
+ return res
320
+
321
+ def get_amplitudes_and_distances(self):
322
+ soma_rec, dend_rec, apic_rec = self.get_recordings()
323
+ soma_amp = self.amplitudes({"soma": soma_rec})[0]
324
+ dend_amps = None
325
+ dend_dist = None
326
+ apic_amps = None
327
+ apic_dist = None
328
+ if dend_rec:
329
+ dend_amps = self.amplitudes(dend_rec)
330
+ dend_dist = self.distances_to_soma(dend_rec)
331
+ if apic_rec:
332
+ apic_amps = self.amplitudes(apic_rec)
333
+ apic_dist = self.distances_to_soma(apic_rec)
334
+
335
+ return soma_amp, dend_amps, dend_dist, apic_amps, apic_dist
336
+
337
+ def fit(self, soma_amp, dend_amps, dend_dist, apic_amps, apic_dist):
338
+ """Fit the amplitudes vs distances to an exponential decay function."""
339
+ from scipy.optimize import curve_fit
340
+
341
+ popt_dend = None
342
+ if dend_amps and dend_dist:
343
+ dist = [0] + dend_dist # add soma distance
344
+ amps = [soma_amp] + dend_amps # add soma amplitude
345
+ popt_dend, _ = curve_fit(exp_decay, dist, amps)
346
+
347
+ popt_apic = None
348
+ if apic_amps and apic_dist:
349
+ dist = [0] + apic_dist # add soma distance
350
+ amps = [soma_amp] + apic_amps # add soma amplitude
351
+ popt_apic, _ = curve_fit(exp_decay, dist, amps)
352
+
353
+ return popt_dend, popt_apic
354
+
355
+ def validate(self, soma_amp, dend_amps, dend_dist, apic_amps, apic_dist):
356
+ """Check that the exponential fit is decaying."""
357
+ validated = True
358
+ notes = ""
359
+ popt_dend, popt_apic = self.fit(soma_amp, dend_amps, dend_dist, apic_amps, apic_dist)
360
+ if popt_dend is None:
361
+ logger.debug("No dendritic recordings found.")
362
+ notes += "No dendritic recordings found.\n"
363
+ elif popt_dend[1] <= 0:
364
+ logger.debug("Dendritic fit is not decaying.")
365
+ validated = False
366
+ notes += "Dendritic fit is not decaying.\n"
367
+ else:
368
+ notes += "Dendritic validation passed: dendritic amplitude is decaying with distance relative to soma.\n"
369
+ if popt_apic is None:
370
+ logger.debug("No apical recordings found.")
371
+ notes += "No apical recordings found.\n"
372
+ elif popt_apic[1] <= 0:
373
+ logger.debug("Apical fit is not decaying.")
374
+ validated = False
375
+ notes += "Apical fit is not decaying.\n"
376
+ else:
377
+ notes += "Apical validation passed: apical amplitude is decaying with distance relative to soma.\n"
378
+
379
+ return validated, notes
380
+
381
+ def plot_amp_vs_dist(
382
+ self,
383
+ soma_amp,
384
+ dend_amps,
385
+ dend_dist,
386
+ apic_amps,
387
+ apic_dist,
388
+ show_figure=True,
389
+ save_figure=False,
390
+ output_dir="./",
391
+ output_fname="bpap.pdf",
392
+ ):
393
+ """Plot the results of the BPAP analysis."""
394
+ popt_dend, popt_apic = self.fit(soma_amp, dend_amps, dend_dist, apic_amps, apic_dist)
395
+
396
+ outpath = pathlib.Path(output_dir) / output_fname
397
+ fig, ax1 = plt.subplots(figsize=(10, 6))
398
+ ax1.scatter([0], [soma_amp], marker="^", color='black', label='Soma')
399
+ if dend_amps and dend_dist:
400
+ ax1.scatter(
401
+ dend_dist,
402
+ dend_amps,
403
+ c=dend_dist,
404
+ cmap=self.basal_cmap,
405
+ label='Basal Dendrites',
406
+ )
407
+ if popt_dend is not None:
408
+ x = np.linspace(0, max(dend_dist), 100)
409
+ y = exp_decay(x, *popt_dend)
410
+ ax1.plot(x, y, color='darkgreen', linestyle='--', label='Basal Dendritic Fit')
411
+ if apic_amps and apic_dist:
412
+ ax1.scatter(
413
+ apic_dist,
414
+ apic_amps,
415
+ c=apic_dist,
416
+ cmap=self.apical_cmap,
417
+ label='Apical Dendrites'
418
+ )
419
+ if popt_apic is not None:
420
+ x = np.linspace(0, max(apic_dist), 100)
421
+ y = exp_decay(x, *popt_apic)
422
+ ax1.plot(x, y, color='goldenrod', linestyle='--', label='Apical Fit')
423
+ ax1.set_xlabel('Distance to Soma (um)')
424
+ ax1.set_ylabel('Amplitude (mV)')
425
+ ax1.legend()
426
+ fig.suptitle('Back-propagating Action Potential Analysis')
427
+ fig.tight_layout()
428
+ if save_figure:
429
+ fig.savefig(outpath)
430
+ if show_figure:
431
+ plt.show()
432
+
433
+ return outpath
434
+
435
+ def plot_one_axis_recordings(self, fig, ax, rec_list, dist, cmap):
436
+ """Plot the soma and dendritic recordings on one axis.
437
+
438
+ Args:
439
+ fig (matplotlib.figure.Figure): The figure to plot on.
440
+ ax (matplotlib.axes.Axes): The axis to plot on.
441
+ rec_list (list): List of recordings to plot.
442
+ dist (list): List of distances from the soma for each recording.
443
+ cmap (matplotlib.colors.Colormap): Colormap to use for the recordings.
444
+ """
445
+ time = self.cell.get_time()
446
+ line_collection = LineCollection(
447
+ [np.column_stack([time, rec]) for rec in rec_list],
448
+ array=dist,
449
+ cmap=cmap,
450
+ )
451
+ ax.set_xlim(
452
+ self.stim_start - 0.1,
453
+ self.stim_start + 30
454
+ )
455
+ ax.set_ylim(
456
+ min([min(rec[self.start_index:]) for rec in rec_list]) - 2,
457
+ max([max(rec[self.start_index:]) for rec in rec_list]) + 2
458
+ )
459
+ ax.add_collection(line_collection)
460
+ fig.colorbar(line_collection, label="soma distance (um)", ax=ax)
461
+
462
+ def plot_recordings(
463
+ self,
464
+ show_figure=True,
465
+ save_figure=False,
466
+ output_dir="./",
467
+ output_fname="bpap_recordings.pdf",
468
+ ):
469
+ """Plot the recordings from all dendrites."""
470
+ soma_rec, dend_rec, apic_rec = self.get_recordings()
471
+ dend_dist = []
472
+ apic_dist = []
473
+ if dend_rec:
474
+ dend_dist = self.distances_to_soma(dend_rec)
475
+ if apic_rec:
476
+ apic_dist = self.distances_to_soma(apic_rec)
477
+ # add soma_rec to the lists
478
+ dend_rec_list = [soma_rec] + list(dend_rec.values())
479
+ dend_dist = [0] + dend_dist
480
+ apic_rec_list = [soma_rec] + list(apic_rec.values())
481
+ apic_dist = [0] + apic_dist
482
+
483
+ outpath = pathlib.Path(output_dir) / output_fname
484
+ fig, (ax1, ax2) = plt.subplots(figsize=(10, 12), nrows=2, sharex=True)
485
+
486
+ self.plot_one_axis_recordings(fig, ax1, dend_rec_list, dend_dist, self.basal_cmap)
487
+ self.plot_one_axis_recordings(fig, ax2, apic_rec_list, apic_dist, self.apical_cmap)
488
+
489
+ # plt.setp(ax1.get_xticklabels(), visible=False)
490
+ ax1.set_title('Basal Dendritic Recordings')
491
+ ax2.set_title('Apical Dendritic Recordings')
492
+ ax1.set_ylabel('Voltage (mV)')
493
+ ax2.set_ylabel('Voltage (mV)')
494
+ ax2.set_xlabel('Time (ms)')
495
+ fig.suptitle('Back-propagating Action Potential Recordings')
496
+ fig.tight_layout()
497
+ if save_figure:
498
+ fig.savefig(outpath)
499
+ if show_figure:
500
+ plt.show()
501
+
502
+ return outpath
@@ -0,0 +1,7 @@
1
+ """Utility functions for analysis."""
2
+
3
+ import numpy as np
4
+
5
+
6
+ def exp_decay(x, a, b, c):
7
+ return a * np.exp(-b * x) + c
@@ -233,7 +233,7 @@ class BluepyCircuitAccess:
233
233
  source_popid, target_popid = zip(*pop_ids)
234
234
 
235
235
  result = result.assign(
236
- source_popid=source_popid, target_popid=target_popid
236
+ source_popid=pd.Series(source_popid), target_popid=pd.Series(target_popid)
237
237
  )
238
238
 
239
239
  if result.empty:
bluecellulab/utils.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
  import contextlib
5
5
  import io
6
6
  import json
7
+ import multiprocessing
8
+ from multiprocessing import pool
7
9
 
8
10
  import numpy as np
9
11
 
@@ -56,3 +58,27 @@ class NumpyEncoder(json.JSONEncoder):
56
58
  elif isinstance(obj, np.ndarray):
57
59
  return obj.tolist()
58
60
  return json.JSONEncoder.default(self, obj)
61
+
62
+
63
+ class NoDaemonProcess(multiprocessing.Process):
64
+ """Class that represents a non-daemon process."""
65
+
66
+ # pylint: disable=dangerous-default-value
67
+
68
+ def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
69
+ """Ensures group=None, for macosx."""
70
+ super().__init__(group=None, target=target, name=name, args=args, kwargs=kwargs)
71
+
72
+ @property
73
+ def daemon(self):
74
+ return False
75
+
76
+ @daemon.setter
77
+ def daemon(self, val):
78
+ pass
79
+
80
+
81
+ class NestedPool(pool.Pool): # pylint: disable=abstract-method
82
+ """Class that represents a MultiProcessing nested pool."""
83
+
84
+ Process = NoDaemonProcess
@@ -19,10 +19,12 @@ import pathlib
19
19
 
20
20
  import efel
21
21
 
22
+ from bluecellulab.analysis.analysis import BPAP
22
23
  from bluecellulab.analysis.analysis import compute_plot_fi_curve
23
24
  from bluecellulab.analysis.analysis import compute_plot_iv_curve
24
25
  from bluecellulab.analysis.inject_sequence import run_multirecordings_stimulus
25
26
  from bluecellulab.analysis.inject_sequence import run_stimulus
27
+ from bluecellulab.cell.core import Cell
26
28
  from bluecellulab.stimulus.factory import IDRestTimings
27
29
  from bluecellulab.stimulus.factory import StimulusFactory
28
30
  from bluecellulab.tools import calculate_input_resistance
@@ -78,12 +80,12 @@ def plot_traces(recordings, out_dir, fname, title, labels=None, xlim=None):
78
80
  return outpath
79
81
 
80
82
 
81
- def spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
83
+ def spiking_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
82
84
  """Spiking test: cell should spike."""
83
85
  stim_factory = StimulusFactory(dt=1.0)
84
86
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
85
87
  recording = run_stimulus(
86
- cell.template_params,
88
+ template_params,
87
89
  step_stimulus,
88
90
  "soma[0]",
89
91
  0.5,
@@ -101,20 +103,22 @@ def spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
101
103
  title="Spiking Test - Step at 200% of Rheobase",
102
104
  )
103
105
 
106
+ notes = "Validation passed: Spikes detected." if passed else "Validation failed: No spikes detected."
104
107
  return {
105
- "skipped": False,
108
+ "name": "Simulatable Neuron Spiking Validation",
106
109
  "passed": passed,
110
+ "validation_details": notes,
107
111
  "figures": [outpath],
108
112
  }
109
113
 
110
114
 
111
- def depolarization_block_test(cell, rheobase, out_dir):
115
+ def depolarization_block_test(template_params, rheobase, out_dir):
112
116
  """Depolarization block test: no depolarization block should be detected."""
113
117
  # Run the stimulus
114
118
  stim_factory = StimulusFactory(dt=1.0)
115
119
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
116
120
  recording = run_stimulus(
117
- cell.template_params,
121
+ template_params,
118
122
  step_stimulus,
119
123
  "soma[0]",
120
124
  0.5,
@@ -139,20 +143,63 @@ def depolarization_block_test(cell, rheobase, out_dir):
139
143
  title="Depolarization Block Test - Step at 200% of Rheobase",
140
144
  )
141
145
 
146
+ notes = "Validation passed: No depolarization block detected." if not depol_block else "Validation failed: Depolarization block detected."
142
147
  return {
143
- "skipped": False,
148
+ "name": "Simulatable Neuron Depolarization Block Validation",
144
149
  "passed": not depol_block,
150
+ "validation_details": notes,
145
151
  "figures": [outpath],
146
152
  }
147
153
 
148
154
 
149
- def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
155
+ def bpap_test(template_params, rheobase, out_dir="./"):
156
+ """Back-propagating action potential test: exponential fit should decay.
157
+
158
+ Args:
159
+ template_params (dict): The template parameters for creating the cell.
160
+ rheobase (float): The rheobase current to use for the test.
161
+ out_dir (str): Directory to save the figure.
162
+ """
163
+ amplitude = 10. * rheobase # Use 1000% of the rheobase current
164
+ bpap = BPAP(Cell.from_template_parameters(template_params))
165
+ bpap.run(duration=1500, amplitude=amplitude)
166
+ soma_amp, dend_amps, dend_dist, apic_amps, apic_dist = bpap.get_amplitudes_and_distances()
167
+ validated, notes = bpap.validate(soma_amp, dend_amps, dend_dist, apic_amps, apic_dist)
168
+ outpath_amp_dist = bpap.plot_amp_vs_dist(
169
+ soma_amp,
170
+ dend_amps,
171
+ dend_dist,
172
+ apic_amps,
173
+ apic_dist,
174
+ show_figure=False,
175
+ save_figure=True,
176
+ output_dir=out_dir,
177
+ output_fname="bpap.pdf"
178
+ )
179
+ outpath_recordings = bpap.plot_recordings(
180
+ show_figure=False,
181
+ save_figure=True,
182
+ output_dir=out_dir,
183
+ output_fname="bpap_recordings.pdf",
184
+ )
185
+
186
+ return {
187
+ "name": "Simulatable Neuron Back-propagating Action Potential Validation",
188
+ "validation_details": notes,
189
+ "passed": validated,
190
+ "figures": [outpath_amp_dist, outpath_recordings],
191
+ }
192
+
193
+
194
+ def ais_spiking_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
150
195
  """AIS spiking test: axon should spike before soma."""
196
+ name = "Simulatable Neuron AIS Spiking Validation"
151
197
  # Check that the cell has an axon
152
- if len(cell.axonal) == 0:
198
+ if len(Cell.from_template_parameters(template_params).axonal) == 0:
153
199
  return {
154
- "skipped": True,
155
- "passed": False,
200
+ "name": name,
201
+ "passed": True,
202
+ "validation_details": "Validation skipped: Cell does not have an axon section.",
156
203
  "figures": [],
157
204
  }
158
205
 
@@ -160,7 +207,7 @@ def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
160
207
  stim_factory = StimulusFactory(dt=1.0)
161
208
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
162
209
  recordings = run_multirecordings_stimulus(
163
- cell.template_params,
210
+ template_params,
164
211
  step_stimulus,
165
212
  "soma[0]",
166
213
  0.5,
@@ -192,27 +239,35 @@ def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
192
239
  for recording in recordings:
193
240
  if recording.spike is None or len(recording.spike) == 0:
194
241
  return {
195
- "skipped": False,
242
+ "name": name,
196
243
  "passed": False,
244
+ "validation_details": "Validation failed: No spikes detected in one or both recordings.",
197
245
  "figures": [outpath1, outpath2],
198
246
  }
199
247
 
200
248
  # Check if axon spike happens before soma spike
201
249
  passed = bool(axon_recording.spike[0] < soma_recording.spike[0])
250
+ notes = (
251
+ "Validation passed: Axon spikes before soma."
252
+ if passed
253
+ else "Validation failed: Axon does not spike before soma."
254
+ )
202
255
  return {
203
- "skipped": False,
256
+ "name": name,
204
257
  "passed": passed,
258
+ "validation_details": notes,
205
259
  "figures": [outpath1, outpath2],
206
260
  }
207
261
 
208
262
 
209
- def hyperpolarization_test(cell, rheobase, out_dir):
263
+ def hyperpolarization_test(template_params, rheobase, out_dir):
210
264
  """Hyperpolarization test: hyperpolarized voltage should be lower than RMP."""
265
+ name = "Simulatable Neuron Hyperpolarization Validation"
211
266
  # Run the stimulus
212
267
  stim_factory = StimulusFactory(dt=1.0)
213
268
  step_stimulus = stim_factory.iv(threshold_current=rheobase, threshold_percentage=-40)
214
269
  recording = run_stimulus(
215
- cell.template_params,
270
+ template_params,
216
271
  step_stimulus,
217
272
  "soma[0]",
218
273
  0.5,
@@ -240,15 +295,22 @@ def hyperpolarization_test(cell, rheobase, out_dir):
240
295
  ss_voltage = features_results[0]["steady_state_voltage_stimend"][0]
241
296
  if rmp is None or ss_voltage is None:
242
297
  return {
243
- "skipped": False,
298
+ "name": name,
244
299
  "passed": False,
300
+ "validation_details": "Validation failed: Could not determine RMP or steady state voltage.",
245
301
  "figures": [outpath],
246
302
  }
247
303
  hyperpol_bool = bool(ss_voltage < rmp)
248
304
 
305
+ notes = (
306
+ f"Validation passed: Hyperpolarized voltage ({ss_voltage:.2f} mV) is lower than RMP ({rmp:.2f} mV)."
307
+ if hyperpol_bool
308
+ else f"Validation failed: Hyperpolarized voltage ({ss_voltage:.2f} mV) is not lower than RMP ({rmp:.2f} mV)."
309
+ )
249
310
  return {
250
- "skipped": False,
311
+ "name": name,
251
312
  "passed": hyperpol_bool,
313
+ "validation_details": notes,
252
314
  "figures": [outpath],
253
315
  }
254
316
 
@@ -257,17 +319,24 @@ def rin_test(rin):
257
319
  """Rin should have an acceptable biological range (< 1000 MOhm)"""
258
320
  passed = bool(rin < 1000)
259
321
 
322
+ notes = (
323
+ f"Validation passed: Input resistance (Rin) = {rin:.2f} MOhm is smaller than 1000 MOhm."
324
+ if passed
325
+ else f"Validation failed: Input resistance (Rin) = {rin:.2f} MOhm is higher than 1000 MOhm, which is not realistic."
326
+ )
260
327
  return {
261
- "skipped": False,
328
+ "name": "Simulatable Neuron Input Resistance Validation",
262
329
  "passed": passed,
330
+ "validation_details": notes,
263
331
  "figures": [],
264
332
  }
265
333
 
266
334
 
267
- def iv_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
335
+ def iv_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
268
336
  """IV curve should have a positive slope."""
337
+ name = "Simulatable Neuron IV Curve Validation"
269
338
  amps, steady_states = compute_plot_iv_curve(
270
- cell,
339
+ Cell.from_template_parameters(template_params),
271
340
  rheobase=rheobase,
272
341
  threshold_voltage=spike_threshold_voltage,
273
342
  nb_bins=5,
@@ -281,23 +350,31 @@ def iv_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
281
350
  # Check for positive slope
282
351
  if len(amps) < 2 or len(steady_states) < 2:
283
352
  return {
284
- "skipped": False,
353
+ "name": name,
285
354
  "passed": False,
355
+ "validation_details": "Validation failed: Not enough data points to determine slope.",
286
356
  "figures": [outpath],
287
357
  }
288
358
  slope = numpy.polyfit(amps, steady_states, 1)[0]
289
359
  passed = bool(slope > 0)
360
+ notes = (
361
+ f"Validation passed: Slope of IV curve = {slope:.2f} is positive."
362
+ if passed
363
+ else f"Validation failed: Slope of IV curve = {slope:.2f} is not positive."
364
+ )
290
365
  return {
291
- "skipped": False,
366
+ "name": name,
367
+ "validation_details": notes,
292
368
  "passed": passed,
293
369
  "figures": [outpath],
294
370
  }
295
371
 
296
372
 
297
- def fi_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
373
+ def fi_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
298
374
  """FI curve should have a positive slope."""
375
+ name = "Simulatable Neuron FI Curve Validation"
299
376
  amps, spike_counts = compute_plot_fi_curve(
300
- cell,
377
+ Cell.from_template_parameters(template_params),
301
378
  rheobase=rheobase,
302
379
  threshold_voltage=spike_threshold_voltage,
303
380
  nb_bins=5,
@@ -311,14 +388,21 @@ def fi_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
311
388
  # Check for positive slope
312
389
  if len(amps) < 2 or len(spike_counts) < 2:
313
390
  return {
314
- "skipped": False,
391
+ "name": name,
315
392
  "passed": False,
393
+ "validation_details": "Validation failed: Not enough data points to determine slope.",
316
394
  "figures": [outpath],
317
395
  }
318
396
  slope = numpy.polyfit(amps, spike_counts, 1)[0]
319
397
  passed = bool(slope > 0)
398
+ notes = (
399
+ f"Validation passed: Slope of FI curve = {slope:.2f} is positive."
400
+ if passed
401
+ else f"Validation failed: Slope of FI curve = {slope:.2f} is not positive."
402
+ )
320
403
  return {
321
- "skipped": False,
404
+ "name": name,
405
+ "validation_details": notes,
322
406
  "passed": passed,
323
407
  "figures": [outpath],
324
408
  }
@@ -338,7 +422,6 @@ def run_validations(
338
422
  out_dir = pathlib.Path(output_dir) / cell_name
339
423
  out_dir.mkdir(parents=True, exist_ok=True)
340
424
 
341
- # cell = Cell.from_template_parameters(template_params)
342
425
  # get me-model properties
343
426
  holding_current = cell.hypamp if cell.hypamp else 0.0
344
427
  if cell.threshold:
@@ -354,48 +437,79 @@ def run_validations(
354
437
  emodel_properties=cell.template_params.emodel_properties,
355
438
  )
356
439
 
357
- # Validation 1: Spiking Test
358
- logger.debug("Running spiking test")
359
- spiking_test_result = spiking_test(cell, rheobase, out_dir, spike_threshold_voltage)
440
+ logger.debug("Running validations...")
441
+ from bluecellulab.utils import NestedPool
442
+ with NestedPool(processes=8) as pool:
443
+ # Validation 1: Spiking Test
444
+ spiking_test_result_future = pool.apply_async(
445
+ spiking_test,
446
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
447
+ )
360
448
 
361
- # Validation 2: Depolarization Block Test
362
- logger.debug("Running depolarization block test")
363
- depolarization_block_result = depolarization_block_test(cell, rheobase, out_dir)
449
+ # Validation 2: Depolarization Block Test
450
+ depolarization_block_result_future = pool.apply_async(
451
+ depolarization_block_test,
452
+ (cell.template_params, rheobase, out_dir)
453
+ )
364
454
 
365
- # Validation 3: Backpropagating AP Test
366
- # logger.debug("Running backpropagating AP test")
455
+ # Validation 3: Backpropagating AP Test
456
+ bpap_result_future = pool.apply_async(
457
+ bpap_test,
458
+ (cell.template_params, rheobase, out_dir)
459
+ )
367
460
 
368
- # Validation 4: Postsynaptic Potential Test
369
- # logger.debug("Running postsynaptic potential test")
461
+ # Validation 4: Postsynaptic Potential Test
462
+ # We have to wait for ProbAMPANMDA_EMS to be present in entitycore to implement this test
370
463
 
371
- # Validation 5: AIS Spiking Test
372
- logger.debug("Running AIS spiking test")
373
- ais_spiking_test_result = ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage)
464
+ # Validation 5: AIS Spiking Test
465
+ ais_spiking_test_result_future = pool.apply_async(
466
+ ais_spiking_test,
467
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
468
+ )
374
469
 
375
- # Validation 6: Hyperpolarization Test
376
- logger.debug("Running hyperpolarization test")
377
- hyperpolarization_result = hyperpolarization_test(cell, rheobase, out_dir)
470
+ # Validation 6: Hyperpolarization Test
471
+ hyperpolarization_result_future = pool.apply_async(
472
+ hyperpolarization_test,
473
+ (cell.template_params, rheobase, out_dir)
474
+ )
378
475
 
379
- # Validation 7: Rin Test
380
- logger.debug("Running Rin test")
381
- rin_result = rin_test(rin)
476
+ # Validation 7: Rin Test
477
+ rin_result_future = pool.apply_async(
478
+ rin_test,
479
+ (rin,)
480
+ )
382
481
 
383
- # Validation 8: IV Test
384
- logger.debug("Running IV test")
385
- iv_test_result = iv_test(cell, rheobase, out_dir, spike_threshold_voltage)
482
+ # # Validation 8: IV Test
483
+ iv_test_result_future = pool.apply_async(
484
+ iv_test,
485
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
486
+ )
487
+
488
+ # # Validation 9: FI Test
489
+ fi_test_result_future = pool.apply_async(
490
+ fi_test,
491
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
492
+ )
386
493
 
387
- # Validation 9: FI Test
388
- logger.debug("Running FI test")
389
- fi_test_result = fi_test(cell, rheobase, out_dir, spike_threshold_voltage)
494
+ # Wait for all validations to complete
495
+ spiking_test_result = spiking_test_result_future.get()
496
+ depolarization_block_result = depolarization_block_result_future.get()
497
+ bpap_result = bpap_result_future.get()
498
+ ais_spiking_test_result = ais_spiking_test_result_future.get()
499
+ hyperpolarization_result = hyperpolarization_result_future.get()
500
+ rin_result = rin_result_future.get()
501
+ iv_test_result = iv_test_result_future.get()
502
+ fi_test_result = fi_test_result_future.get()
390
503
 
391
504
  return {
392
505
  "memodel_properties": {
393
- "holding_current": float(holding_current),
394
- "rheobase": float(rheobase),
395
- "rin": float(rin),
506
+ "holding_current": holding_current,
507
+ "rheobase": rheobase,
508
+ "rin": rin,
396
509
  },
397
510
  "spiking_test": spiking_test_result,
398
511
  "depolarization_block_test": depolarization_block_result,
512
+ "bpap_test": bpap_result,
399
513
  "ais_spiking_test": ais_spiking_test_result,
400
514
  "hyperpolarization_test": hyperpolarization_result,
401
515
  "rin_test": rin_result,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bluecellulab
3
- Version: 2.6.49
3
+ Version: 2.6.51
4
4
  Summary: Biologically detailed neural network simulations and analysis.
5
5
  Author: Blue Brain Project, EPFL
6
6
  License: Apache2.0
@@ -27,6 +27,7 @@ Requires-Dist: pydantic<3.0.0,>=2.5.2
27
27
  Requires-Dist: typing-extensions>=4.8.0
28
28
  Requires-Dist: networkx>=3.1
29
29
  Requires-Dist: h5py>=3.8.0
30
+ Requires-Dist: seaborn
30
31
  Dynamic: license-file
31
32
 
32
33
  |banner|
@@ -12,12 +12,13 @@ bluecellulab/psegment.py,sha256=PTgoGLqM4oFIdF_8QHFQCU59j-8TQmtq6PakiGUQhIo,3138
12
12
  bluecellulab/rngsettings.py,sha256=2Ykb4Ylk3XTs58x1UIxjg8XJqjSpnCgKRZ8avXCDpxk,4237
13
13
  bluecellulab/tools.py,sha256=hF1HJcera0oggetsfN5PNTRYCpFS0sQiZlVxw4jRd1g,17968
14
14
  bluecellulab/type_aliases.py,sha256=DvgjERv2Ztdw_sW63JrZTQGpJ0x5uMTFB5hcBHDb0WA,441
15
- bluecellulab/utils.py,sha256=SbOOkzw1YGjCKV3qOw0zpabNEy7V9BRtgMLsQJiFRq4,1526
15
+ bluecellulab/utils.py,sha256=0NhwlzyLnSi8kziSfDsQf7pokO4qDkMJVAO33kSX4O0,2227
16
16
  bluecellulab/verbosity.py,sha256=T0IgX7DrRo19faxrT4Xzb27gqxzoILQ8FzYKxvUeaPM,1342
17
17
  bluecellulab/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- bluecellulab/analysis/analysis.py,sha256=3gDJRau5SL6wOhAUJ16vqP7aYLtoAZ7f1qZbSUVoUU8,10818
18
+ bluecellulab/analysis/analysis.py,sha256=53g6SW_9ZbG3fbhuXJqurAPRR4VG1jsZYK6efq-EwfA,21229
19
19
  bluecellulab/analysis/inject_sequence.py,sha256=uU6Q6y0ErMDDEgdsTZBXCJ4LgwkATY7G8Fa6mmjpL98,12523
20
20
  bluecellulab/analysis/plotting.py,sha256=PqRoaZz33ULMw8A9YnZXXrxcUd84M_dwlYMTFhG7YT4,3999
21
+ bluecellulab/analysis/utils.py,sha256=eMirP557D11BuedgSqjripDxOq1haIldNbnYNetV1bg,121
21
22
  bluecellulab/cell/__init__.py,sha256=Sbc0QOsJ8E7tSwf3q7fsXuE_SevBN6ZmoCVyyU5zfII,208
22
23
  bluecellulab/cell/cell_dict.py,sha256=VE7pi-NsMVRSmo-PSdbiLYmolDOu0Gc6JxFBkuQpFdk,1346
23
24
  bluecellulab/cell/core.py,sha256=BualY0WOtC0arKmvKzQ3sQ0fhc2jrtd6wqSd9M-e25E,33797
@@ -41,7 +42,7 @@ bluecellulab/circuit/simulation_access.py,sha256=keME58gzLVAPEg2nnWD_bwEm9V2Kjeq
41
42
  bluecellulab/circuit/synapse_properties.py,sha256=TvUMiXZAAeYo1zKkus3z1EUvrE9QCIQ3Ze-jSnPSJWY,6374
42
43
  bluecellulab/circuit/validate.py,sha256=wntnr7oIDyasqD1nM-kqz1NpfWDxBGhx0Ep3e5hHXIw,3593
43
44
  bluecellulab/circuit/circuit_access/__init__.py,sha256=sgp6m5kP-pq60V1IFGUiSUR1OW01zdxXNNUJmPA8anI,201
44
- bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=aXvtZR8EwpVEkB4KAupjC4DX_1xYC4OrHwMUQcQIWrQ,14273
45
+ bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=m5PWSYrrkORb55HN1ZgJpQFLeSHxsNBtzyJ1n0zuVro,14295
45
46
  bluecellulab/circuit/circuit_access/definition.py,sha256=_sUU0DkesGOFW82kS1G9vki0o0aQP76z3Ltk7Vo9KK4,4435
46
47
  bluecellulab/circuit/circuit_access/sonata_circuit_access.py,sha256=tADHxVZw4VgZAz2z4NKMUwc0rd_EO40EZggw5fDhnF4,10411
47
48
  bluecellulab/circuit/config/__init__.py,sha256=aaoJXRKBJzpxxREo9NxKc-_CCPmVeuR1mcViRXcLrC4,215
@@ -65,10 +66,10 @@ bluecellulab/stimulus/stimulus.py,sha256=a_hKJUtZmIgjiFjbJf6RzUPokELqn0IHCgIWGw5
65
66
  bluecellulab/synapse/__init__.py,sha256=RW8XoAMXOvK7OG1nHl_q8jSEKLj9ZN4oWf2nY9HAwuk,192
66
67
  bluecellulab/synapse/synapse_factory.py,sha256=NHwRMYMrnRVm_sHmyKTJ1bdoNmWZNU4UPOGu7FCi-PE,6987
67
68
  bluecellulab/synapse/synapse_types.py,sha256=zs_yBvGTH4QrbQF3nEViidyq1WM_ZcTSFdjUxB3khW0,16871
68
- bluecellulab/validation/validation.py,sha256=irP617pC2fdT_wJafaedkSTDHJ-5I2Badml5jj_b-OI,13141
69
- bluecellulab-2.6.49.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
70
- bluecellulab-2.6.49.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
71
- bluecellulab-2.6.49.dist-info/METADATA,sha256=btGOUwph6uJAQSrUhhatlF7JHau3kt4UnyMSjyTyuCk,8236
72
- bluecellulab-2.6.49.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
73
- bluecellulab-2.6.49.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
74
- bluecellulab-2.6.49.dist-info/RECORD,,
69
+ bluecellulab/validation/validation.py,sha256=EN9HMty70VPwnEPhWUmN5F-MGWbMN3uU0DmNnjL0ucw,18209
70
+ bluecellulab-2.6.51.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
71
+ bluecellulab-2.6.51.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
72
+ bluecellulab-2.6.51.dist-info/METADATA,sha256=uWxsjFCkKomqMrGyVsZuhcUdDHi32Swj5sASJ4faKJE,8259
73
+ bluecellulab-2.6.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
+ bluecellulab-2.6.51.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
75
+ bluecellulab-2.6.51.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5