bluecellulab 2.6.61__tar.gz → 2.6.77__tar.gz
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.
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.github/workflows/test.yml +1 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.gitignore +4 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/PKG-INFO +10 -5
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/README.rst +2 -2
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/analysis/analysis.py +118 -65
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/core.py +259 -11
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/injector.py +25 -12
- bluecellulab-2.6.77/bluecellulab/cell/section_tools.py +96 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/circuit_access/bluepy_circuit_access.py +2 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +2 -2
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/config/bluepy_simulation_config.py +15 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/config/definition.py +12 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/config/sonata_simulation_config.py +69 -3
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/node_id.py +1 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/simulation_access.py +29 -7
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit_simulation.py +268 -139
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/connection.py +13 -5
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/plotwindow.py +1 -1
- bluecellulab-2.6.77/bluecellulab/reports/manager.py +91 -0
- bluecellulab-2.6.77/bluecellulab/reports/utils.py +227 -0
- bluecellulab-2.6.77/bluecellulab/reports/writers/__init__.py +25 -0
- bluecellulab-2.6.77/bluecellulab/reports/writers/base_writer.py +30 -0
- bluecellulab-2.6.77/bluecellulab/reports/writers/compartment.py +221 -0
- bluecellulab-2.6.77/bluecellulab/reports/writers/spikes.py +86 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/simulation/neuron_globals.py +12 -0
- bluecellulab-2.6.77/bluecellulab/simulation/report.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/simulation/simulation.py +5 -10
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/stimulus/circuit_stimulus_definitions.py +72 -28
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/tools.py +22 -85
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/utils.py +1 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/validation/validation.py +154 -69
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab.egg-info/PKG-INFO +10 -5
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab.egg-info/SOURCES.txt +8 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab.egg-info/requires.txt +7 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/requirements_docs.txt +1 -1
- bluecellulab-2.6.77/docs/source/_static/.gitkeep +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/conf.py +4 -1
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/pyproject.toml +10 -2
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/tox.ini +5 -3
- bluecellulab-2.6.61/bluecellulab/simulation/report.py +0 -264
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.compile_mod.sh +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.gitattributes +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.github/dependabot.yml +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.github/workflows/release.yml +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.gitlab-ci.yml +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.readthedocs.yml +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/.zenodo.json +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/AUTHORS.txt +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/CHANGELOG.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/CITATION.cff +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/CONTRIBUTING.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/LICENSE +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/MANIFEST.in +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/Makefile +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/analysis/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/analysis/inject_sequence.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/analysis/plotting.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/analysis/utils.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/ballstick/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/ballstick/emodel.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/ballstick/morphology.asc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/cell_dict.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/plotting.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/random.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/recording.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/section_distance.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/serialized_sections.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/sonata_proxy.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/stimuli_generator.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/cell/template.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/circuit_access/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/circuit_access/definition.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/config/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/config/sections.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/format.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/iotools.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/synapse_properties.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/circuit/validate.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/dendrogram.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/exceptions.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/graph.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/hoc/Cell.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/hoc/RNGSettings.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/hoc/TDistFunc.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/hoc/TStim.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/hoc/fileUtils.hoc +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/importer.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/neuron_interpreter.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/psection.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/psegment.py +0 -0
- /bluecellulab-2.6.61/docs/source/_static/.gitkeep → /bluecellulab-2.6.77/bluecellulab/reports/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/rngsettings.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/simulation/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/simulation/parallel.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/stimulus/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/stimulus/factory.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/stimulus/stimulus.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/synapse/__init__.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/synapse/synapse_factory.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/synapse/synapse_types.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/type_aliases.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab/verbosity.py +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab.egg-info/dependency_links.txt +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/bluecellulab.egg-info/top_level.txt +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/Makefile +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/images/voltage-readme.png +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/make.bat +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/api.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/changelog.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/compiling-mechanisms.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/contributing.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/index.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/list_of_stim.rst +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/docs/source/logo/BlueCelluLabBanner.jpg +0 -0
- {bluecellulab-2.6.61 → bluecellulab-2.6.77}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bluecellulab
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.77
|
|
4
4
|
Summary: Biologically detailed neural network simulations and analysis.
|
|
5
5
|
Author: Blue Brain Project, EPFL
|
|
6
6
|
License: Apache2.0
|
|
@@ -14,12 +14,12 @@ Classifier: Operating System :: POSIX
|
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Topic :: Utilities
|
|
17
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
License-File: AUTHORS.txt
|
|
21
21
|
Requires-Dist: NEURON<9.0.0,>=8.0.2
|
|
22
|
-
Requires-Dist: numpy<2.
|
|
22
|
+
Requires-Dist: numpy<2.4,>=2.0.0
|
|
23
23
|
Requires-Dist: matplotlib<4.0.0,>=3.0.0
|
|
24
24
|
Requires-Dist: pandas<3.0.0,>=1.0.0
|
|
25
25
|
Requires-Dist: bluepysnap<4.0.0,>=3.0.0
|
|
@@ -28,6 +28,11 @@ Requires-Dist: typing-extensions>=4.8.0
|
|
|
28
28
|
Requires-Dist: networkx>=3.1
|
|
29
29
|
Requires-Dist: h5py>=3.8.0
|
|
30
30
|
Requires-Dist: seaborn
|
|
31
|
+
Requires-Dist: efel>=5.7.19
|
|
32
|
+
Requires-Dist: neo>=0.14.0
|
|
33
|
+
Requires-Dist: quantities>=0.16.2
|
|
34
|
+
Provides-Extra: examples
|
|
35
|
+
Requires-Dist: ipywidgets; extra == "examples"
|
|
31
36
|
Dynamic: license-file
|
|
32
37
|
|
|
33
38
|
|banner|
|
|
@@ -98,8 +103,8 @@ We are providing support on `Gitter <https://gitter.im/openbraininstitute/BlueCe
|
|
|
98
103
|
Main dependencies
|
|
99
104
|
=================
|
|
100
105
|
|
|
101
|
-
* `Python 3.
|
|
102
|
-
* `
|
|
106
|
+
* `Python 3.10+ <https://www.python.org/downloads/release/python-390/>`_
|
|
107
|
+
* `NEURON <=8.2.7 <https://pypi.org/project/NEURON/>`__
|
|
103
108
|
|
|
104
109
|
Installation
|
|
105
110
|
============
|
|
@@ -66,8 +66,8 @@ We are providing support on `Gitter <https://gitter.im/openbraininstitute/BlueCe
|
|
|
66
66
|
Main dependencies
|
|
67
67
|
=================
|
|
68
68
|
|
|
69
|
-
* `Python 3.
|
|
70
|
-
* `
|
|
69
|
+
* `Python 3.10+ <https://www.python.org/downloads/release/python-390/>`_
|
|
70
|
+
* `NEURON <=8.2.7 <https://pypi.org/project/NEURON/>`__
|
|
71
71
|
|
|
72
72
|
Installation
|
|
73
73
|
============
|
|
@@ -4,9 +4,11 @@ try:
|
|
|
4
4
|
except ImportError:
|
|
5
5
|
efel = None
|
|
6
6
|
from itertools import islice
|
|
7
|
+
from itertools import repeat
|
|
7
8
|
import logging
|
|
8
9
|
from matplotlib.collections import LineCollection
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
11
|
+
from multiprocessing import Pool
|
|
10
12
|
import neuron
|
|
11
13
|
import numpy as np
|
|
12
14
|
import pathlib
|
|
@@ -18,6 +20,7 @@ from bluecellulab.analysis.inject_sequence import run_stimulus
|
|
|
18
20
|
from bluecellulab.analysis.plotting import plot_iv_curve, plot_fi_curve
|
|
19
21
|
from bluecellulab.analysis.utils import exp_decay
|
|
20
22
|
from bluecellulab.simulation import Simulation
|
|
23
|
+
from bluecellulab.simulation.neuron_globals import set_neuron_globals
|
|
21
24
|
from bluecellulab.stimulus import StimulusFactory
|
|
22
25
|
from bluecellulab.stimulus.circuit_stimulus_definitions import Hyperpolarizing
|
|
23
26
|
from bluecellulab.tools import calculate_rheobase
|
|
@@ -40,7 +43,10 @@ def compute_plot_iv_curve(cell,
|
|
|
40
43
|
show_figure=True,
|
|
41
44
|
save_figure=False,
|
|
42
45
|
output_dir="./",
|
|
43
|
-
output_fname="iv_curve.pdf"
|
|
46
|
+
output_fname="iv_curve.pdf",
|
|
47
|
+
n_processes=None,
|
|
48
|
+
celsius=None,
|
|
49
|
+
v_init=None):
|
|
44
50
|
"""Compute and plot the Current-Voltage (I-V) curve for a given cell by
|
|
45
51
|
injecting a range of currents.
|
|
46
52
|
|
|
@@ -72,6 +78,11 @@ def compute_plot_iv_curve(cell,
|
|
|
72
78
|
save_figure (bool): Whether to save the figure. Default is False.
|
|
73
79
|
output_dir (str): The directory to save the figure if save_figure is True. Default is "./".
|
|
74
80
|
output_fname (str): The filename to save the figure as if save_figure is True. Default is "iv_curve.png".
|
|
81
|
+
n_processes (int, optional): The number of processes to use for parallel execution.
|
|
82
|
+
If None or if it is higher than the number of steps,
|
|
83
|
+
it will use the number of steps as the number of processes.
|
|
84
|
+
celsius (float, optional): Temperature in Celsius.
|
|
85
|
+
v_init (float, optional): Initial membrane potential.
|
|
75
86
|
|
|
76
87
|
Returns:
|
|
77
88
|
tuple: A tuple containing:
|
|
@@ -95,15 +106,22 @@ def compute_plot_iv_curve(cell,
|
|
|
95
106
|
for amp in list_amp
|
|
96
107
|
]
|
|
97
108
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
if n_processes is None or n_processes > len(steps):
|
|
110
|
+
n_processes = len(steps)
|
|
111
|
+
with Pool(n_processes, initializer=set_neuron_globals, initargs=(celsius, v_init)) as p:
|
|
112
|
+
recordings = p.starmap(
|
|
113
|
+
run_stimulus,
|
|
114
|
+
zip(
|
|
115
|
+
repeat(cell.template_params),
|
|
116
|
+
steps,
|
|
117
|
+
repeat(injecting_section),
|
|
118
|
+
repeat(injecting_segment),
|
|
119
|
+
repeat(True), # cvode
|
|
120
|
+
repeat(True), # add_hypamp
|
|
121
|
+
repeat(recording_section),
|
|
122
|
+
repeat(recording_segment),
|
|
123
|
+
)
|
|
124
|
+
)
|
|
107
125
|
|
|
108
126
|
steady_states = []
|
|
109
127
|
# compute steady state response
|
|
@@ -148,7 +166,10 @@ def compute_plot_fi_curve(cell,
|
|
|
148
166
|
show_figure=True,
|
|
149
167
|
save_figure=False,
|
|
150
168
|
output_dir="./",
|
|
151
|
-
output_fname="fi_curve.pdf"
|
|
169
|
+
output_fname="fi_curve.pdf",
|
|
170
|
+
n_processes=None,
|
|
171
|
+
celsius=None,
|
|
172
|
+
v_init=None):
|
|
152
173
|
"""Compute and plot the Frequency-Current (F-I) curve for a given cell by
|
|
153
174
|
injecting a range of currents.
|
|
154
175
|
|
|
@@ -182,6 +203,11 @@ def compute_plot_fi_curve(cell,
|
|
|
182
203
|
save_figure (bool): Whether to save the figure. Default is False.
|
|
183
204
|
output_dir (str): The directory to save the figure if save_figure is True. Default is "./".
|
|
184
205
|
output_fname (str): The filename to save the figure as if save_figure is True. Default is "iv_curve.png".
|
|
206
|
+
n_processes (int, optional): The number of processes to use for parallel execution.
|
|
207
|
+
If None or if it is higher than the number of steps,
|
|
208
|
+
it will use the number of steps as the number of processes.
|
|
209
|
+
celsius (float, optional): Temperature in Celsius.
|
|
210
|
+
v_init (float, optional): Initial membrane potential.
|
|
185
211
|
|
|
186
212
|
Returns:
|
|
187
213
|
tuple: A tuple containing:
|
|
@@ -201,19 +227,26 @@ def compute_plot_fi_curve(cell,
|
|
|
201
227
|
for amp in list_amp
|
|
202
228
|
]
|
|
203
229
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
230
|
+
if n_processes is None or n_processes > len(steps):
|
|
231
|
+
n_processes = len(steps)
|
|
232
|
+
with Pool(n_processes, initializer=set_neuron_globals, initargs=(celsius, v_init)) as p:
|
|
233
|
+
recordings = p.starmap(
|
|
234
|
+
run_stimulus,
|
|
235
|
+
zip(
|
|
236
|
+
repeat(cell.template_params),
|
|
237
|
+
steps,
|
|
238
|
+
repeat(injecting_section),
|
|
239
|
+
repeat(injecting_segment),
|
|
240
|
+
repeat(True), # cvode
|
|
241
|
+
repeat(True), # add_hypamp
|
|
242
|
+
repeat(recording_section),
|
|
243
|
+
repeat(recording_segment),
|
|
244
|
+
repeat(True), # enable_spike_detection
|
|
245
|
+
repeat(threshold_voltage), # threshold_spike_detection
|
|
246
|
+
)
|
|
247
|
+
)
|
|
215
248
|
|
|
216
|
-
spike_count = [len(spike) for
|
|
249
|
+
spike_count = [len(recording.spike) for recording in recordings]
|
|
217
250
|
|
|
218
251
|
plot_fi_curve(list_amp,
|
|
219
252
|
spike_count,
|
|
@@ -327,54 +360,69 @@ class BPAP:
|
|
|
327
360
|
return soma_amp, dend_amps, dend_dist, apic_amps, apic_dist
|
|
328
361
|
|
|
329
362
|
@staticmethod
|
|
330
|
-
def fit(soma_amp,
|
|
363
|
+
def fit(soma_amp, branch_amps, branch_dist):
|
|
331
364
|
"""Fit the amplitudes vs distances to an exponential decay function."""
|
|
332
365
|
from scipy.optimize import curve_fit
|
|
333
366
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return popt_dend, popt_apic
|
|
347
|
-
|
|
348
|
-
def validate(self, soma_amp, dend_amps, dend_dist, apic_amps, apic_dist):
|
|
367
|
+
if not branch_amps or not branch_dist or len(branch_amps) != len(branch_dist):
|
|
368
|
+
return None, False
|
|
369
|
+
try:
|
|
370
|
+
dist = [0] + branch_dist
|
|
371
|
+
amps = soma_amp + branch_amps
|
|
372
|
+
popt, _ = curve_fit(exp_decay, dist, amps)
|
|
373
|
+
return popt, False
|
|
374
|
+
except RuntimeError:
|
|
375
|
+
return None, True
|
|
376
|
+
|
|
377
|
+
def validate(self, soma_amp, dend_amps, dend_dist, apic_amps, apic_dist, validate_with_fit=True):
|
|
349
378
|
"""Check that the exponential fit is decaying."""
|
|
350
379
|
validated = True
|
|
351
380
|
notes = ""
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
381
|
+
if validate_with_fit:
|
|
382
|
+
popt_dend, dend_fit_error = self.fit(soma_amp, dend_amps, dend_dist)
|
|
383
|
+
popt_apic, apic_fit_error = self.fit(soma_amp, apic_amps, apic_dist)
|
|
384
|
+
if dend_fit_error or apic_fit_error:
|
|
385
|
+
logger.debug("Fitting error occurred.")
|
|
386
|
+
validated = False
|
|
387
|
+
notes += "Validation failed: Fitting error occurred.\n"
|
|
388
|
+
return validated, notes
|
|
389
|
+
if popt_dend is None:
|
|
390
|
+
logger.debug("No dendritic recordings found.")
|
|
391
|
+
notes += "No dendritic recordings found.\n"
|
|
392
|
+
elif popt_dend[1] <= 0 or popt_dend[0] <= 0:
|
|
393
|
+
logger.debug("Dendritic fit is not decaying.")
|
|
394
|
+
validated = False
|
|
395
|
+
notes += "Dendritic fit is not decaying.\n"
|
|
396
|
+
else:
|
|
397
|
+
notes += "Dendritic validation passed: dendritic amplitude is decaying with distance relative to soma.\n"
|
|
398
|
+
if popt_apic is None:
|
|
399
|
+
logger.debug("No apical recordings found.")
|
|
400
|
+
notes += "No apical recordings found.\n"
|
|
401
|
+
elif popt_apic[1] <= 0 or popt_apic[0] <= 0:
|
|
402
|
+
logger.debug("Apical fit is not decaying.")
|
|
403
|
+
validated = False
|
|
404
|
+
notes += "Apical fit is not decaying.\n"
|
|
405
|
+
else:
|
|
406
|
+
notes += "Apical validation passed: apical amplitude is decaying with distance relative to soma.\n"
|
|
367
407
|
else:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
408
|
+
if dend_amps and dend_dist:
|
|
409
|
+
furthest_dend_idx = np.argmax(dend_dist)
|
|
410
|
+
if dend_amps[furthest_dend_idx] < soma_amp[0]:
|
|
411
|
+
notes += "Dendritic validation passed: dendritic amplitude is decaying with distance relative to soma.\n"
|
|
412
|
+
else:
|
|
413
|
+
validated = False
|
|
414
|
+
notes += "Dendritic validation failed: dendritic amplitude is not decaying with distance relative to soma.\n"
|
|
415
|
+
else:
|
|
416
|
+
notes += "No dendritic recordings found.\n"
|
|
417
|
+
if apic_amps and apic_dist:
|
|
418
|
+
furthest_apic_idx = np.argmax(apic_dist)
|
|
419
|
+
if apic_amps[furthest_apic_idx] < soma_amp[0]:
|
|
420
|
+
notes += "Apical validation passed: apical amplitude is decaying with distance relative to soma.\n"
|
|
421
|
+
else:
|
|
422
|
+
validated = False
|
|
423
|
+
notes += "Apical validation failed: apical amplitude is not decaying with distance relative to soma.\n"
|
|
424
|
+
else:
|
|
425
|
+
notes += "No apical recordings found.\n"
|
|
378
426
|
|
|
379
427
|
return validated, notes
|
|
380
428
|
|
|
@@ -389,9 +437,14 @@ class BPAP:
|
|
|
389
437
|
save_figure=False,
|
|
390
438
|
output_dir="./",
|
|
391
439
|
output_fname="bpap.pdf",
|
|
440
|
+
do_fit=True,
|
|
392
441
|
):
|
|
393
442
|
"""Plot the results of the BPAP analysis."""
|
|
394
|
-
popt_dend
|
|
443
|
+
popt_dend = None
|
|
444
|
+
popt_apic = None
|
|
445
|
+
if do_fit:
|
|
446
|
+
popt_dend, _ = self.fit(soma_amp, dend_amps, dend_dist)
|
|
447
|
+
popt_apic, _ = self.fit(soma_amp, apic_amps, apic_dist)
|
|
395
448
|
|
|
396
449
|
outpath = pathlib.Path(output_dir) / output_fname
|
|
397
450
|
fig, ax1 = plt.subplots(figsize=(10, 6))
|
|
@@ -19,7 +19,7 @@ import logging
|
|
|
19
19
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
import queue
|
|
22
|
-
from typing import Optional
|
|
22
|
+
from typing import List, Optional, Tuple
|
|
23
23
|
from typing_extensions import deprecated
|
|
24
24
|
|
|
25
25
|
import neuron
|
|
@@ -47,6 +47,7 @@ from bluecellulab.stimulus.circuit_stimulus_definitions import SynapseReplay
|
|
|
47
47
|
from bluecellulab.synapse import SynapseFactory, Synapse
|
|
48
48
|
from bluecellulab.synapse.synapse_types import SynapseID
|
|
49
49
|
from bluecellulab.type_aliases import HocObjectType, NeuronSection, SectionMapping
|
|
50
|
+
from bluecellulab.cell.section_tools import currents_vars, section_to_variable_recording_str
|
|
50
51
|
|
|
51
52
|
logger = logging.getLogger(__name__)
|
|
52
53
|
|
|
@@ -250,6 +251,10 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
250
251
|
else:
|
|
251
252
|
self._default_disable_ttx()
|
|
252
253
|
|
|
254
|
+
@property
|
|
255
|
+
def ttx_enabled(self):
|
|
256
|
+
return getattr(self, "_ttx_enabled", False)
|
|
257
|
+
|
|
253
258
|
def _default_enable_ttx(self) -> None:
|
|
254
259
|
"""Default enable_ttx implementation."""
|
|
255
260
|
for section in self.sections.values():
|
|
@@ -760,17 +765,66 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
760
765
|
"""Get a vector of AIS voltage."""
|
|
761
766
|
return self.get_recording('self.axonal[1](0.5)._ref_v')
|
|
762
767
|
|
|
763
|
-
def add_variable_recording(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
+
def add_variable_recording(
|
|
769
|
+
self,
|
|
770
|
+
variable: str,
|
|
771
|
+
section: Optional[NeuronSection] = None,
|
|
772
|
+
segx: float = 0.5,
|
|
773
|
+
dt: Optional[float] = None
|
|
774
|
+
) -> None:
|
|
775
|
+
"""Add a recording of any NEURON RANGE variable (e.g., gna, gk, ina)
|
|
776
|
+
from a given section and segment.
|
|
768
777
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
778
|
+
Args:
|
|
779
|
+
variable: The NEURON variable name to record (e.g., "gna").
|
|
780
|
+
section: The section to record from (defaults to soma).
|
|
781
|
+
segx: Segment position between 0 and 1.
|
|
782
|
+
dt: Optional recording time step.
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
if section is None:
|
|
786
|
+
section = self.soma
|
|
787
|
+
|
|
788
|
+
# validate before constructing the string
|
|
789
|
+
seg = section(segx)
|
|
790
|
+
if "." in variable:
|
|
791
|
+
mech, var = variable.split(".", 1)
|
|
792
|
+
mobj = getattr(seg, mech, None)
|
|
793
|
+
if mobj is None or not hasattr(mobj, f"_ref_{var}"):
|
|
794
|
+
raise AttributeError(
|
|
795
|
+
f"'{variable}' not recordable at {section.name()}({segx}). "
|
|
796
|
+
f"Mechanisms here: {list(section.psection()['density_mechs'].keys())}"
|
|
797
|
+
)
|
|
772
798
|
else:
|
|
773
|
-
|
|
799
|
+
if not hasattr(seg, f"_ref_{variable}"):
|
|
800
|
+
raise AttributeError(
|
|
801
|
+
f"'{variable}' not recordable at {section.name()}({segx}). "
|
|
802
|
+
f"(Top-level vars are typically v/ina/ik/ica)"
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
var_name = section_to_variable_recording_str(section, segx, variable)
|
|
806
|
+
self.add_recording(var_name, dt)
|
|
807
|
+
|
|
808
|
+
def get_variable_recording(
|
|
809
|
+
self, variable: str, section: Optional[NeuronSection], segx: float
|
|
810
|
+
) -> np.ndarray:
|
|
811
|
+
"""Get a recording of any variable recorded from a section and segment.
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
variable: The name of the recorded variable (e.g., 'v', 'gna').
|
|
815
|
+
section: The NEURON section object.
|
|
816
|
+
segx: Segment location from 0 to 1.
|
|
817
|
+
|
|
818
|
+
Returns:
|
|
819
|
+
NumPy array of recorded values.
|
|
820
|
+
|
|
821
|
+
Raises:
|
|
822
|
+
ValueError: If the recording is not found.
|
|
823
|
+
"""
|
|
824
|
+
if section is None:
|
|
825
|
+
section = self.soma
|
|
826
|
+
recording_name = section_to_variable_recording_str(section, segx, variable)
|
|
827
|
+
return self.get_recording(recording_name)
|
|
774
828
|
|
|
775
829
|
@property
|
|
776
830
|
def n_segments(self) -> int:
|
|
@@ -784,7 +838,18 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
784
838
|
connected to that cell."""
|
|
785
839
|
if self.sonata_proxy is None:
|
|
786
840
|
raise BluecellulabError("Cell: add_synapse_replay requires a sonata proxy.")
|
|
787
|
-
|
|
841
|
+
|
|
842
|
+
file_path = Path(stimulus.spike_file).expanduser()
|
|
843
|
+
if not file_path.is_absolute():
|
|
844
|
+
config_dir = stimulus.config_dir
|
|
845
|
+
if config_dir is not None:
|
|
846
|
+
file_path = Path(config_dir) / file_path
|
|
847
|
+
|
|
848
|
+
file_path = file_path.resolve()
|
|
849
|
+
|
|
850
|
+
if not file_path.exists():
|
|
851
|
+
raise FileNotFoundError(f"Spike file not found: {str(file_path)}")
|
|
852
|
+
synapse_spikes: dict = get_synapse_replay_spikes(str(file_path))
|
|
788
853
|
for synapse_id, synapse in self.synapses.items():
|
|
789
854
|
source_population = synapse.syn_description["source_population_name"]
|
|
790
855
|
pre_gid = CellId(
|
|
@@ -843,3 +908,186 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
843
908
|
|
|
844
909
|
def __del__(self):
|
|
845
910
|
self.delete()
|
|
911
|
+
|
|
912
|
+
def get_section(self, section_name: str) -> NeuronSection:
|
|
913
|
+
"""Return a single, fully specified NEURON section (e.g., 'soma[0]',
|
|
914
|
+
'dend[3]').
|
|
915
|
+
|
|
916
|
+
Raises:
|
|
917
|
+
ValueError or TypeError if the section is not found or invalid.
|
|
918
|
+
"""
|
|
919
|
+
if section_name in self.sections:
|
|
920
|
+
section = self.sections[section_name]
|
|
921
|
+
if hasattr(section, "nseg"):
|
|
922
|
+
return section
|
|
923
|
+
raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
|
|
924
|
+
|
|
925
|
+
available = ", ".join(self.sections.keys())
|
|
926
|
+
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
927
|
+
|
|
928
|
+
def get_sections(self, section_name: str) -> List[NeuronSection]:
|
|
929
|
+
"""Return a list of NEURON sections.
|
|
930
|
+
|
|
931
|
+
If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
|
|
932
|
+
If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
|
|
933
|
+
|
|
934
|
+
Raises:
|
|
935
|
+
ValueError if no valid sections are found.
|
|
936
|
+
"""
|
|
937
|
+
# Try to interpret as fully qualified section name
|
|
938
|
+
try:
|
|
939
|
+
return [self.get_section(section_name)]
|
|
940
|
+
except ValueError:
|
|
941
|
+
pass # Not a precise match; try prefix match
|
|
942
|
+
|
|
943
|
+
# Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
|
|
944
|
+
matched = [
|
|
945
|
+
section for name, section in self.sections.items()
|
|
946
|
+
if name.startswith(f"{section_name}[")
|
|
947
|
+
]
|
|
948
|
+
if matched:
|
|
949
|
+
return matched
|
|
950
|
+
|
|
951
|
+
available = ", ".join(self.sections.keys())
|
|
952
|
+
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
953
|
+
|
|
954
|
+
def get_section_by_id(self, section_id: int) -> NeuronSection:
|
|
955
|
+
"""Return NEURON section by global section index (LibSONATA
|
|
956
|
+
ordering)."""
|
|
957
|
+
if not self.psections:
|
|
958
|
+
self._init_psections()
|
|
959
|
+
|
|
960
|
+
try:
|
|
961
|
+
return self.psections[int(section_id)].hsection
|
|
962
|
+
except KeyError:
|
|
963
|
+
raise IndexError(f"Section ID {section_id} is out of range for cell {self.cell_id.id}")
|
|
964
|
+
|
|
965
|
+
def resolve_segments_from_compartment_set(self, node_id, compartment_nodes) -> List[Tuple[NeuronSection, str, float]]:
|
|
966
|
+
"""Resolve segments for a cell using a predefined compartment node
|
|
967
|
+
list.
|
|
968
|
+
|
|
969
|
+
Supports both LibSONATA format ([node_id, section_id, seg]) and
|
|
970
|
+
name-based format ([node_id, section_name, seg]).
|
|
971
|
+
"""
|
|
972
|
+
result = []
|
|
973
|
+
for n_id, sec_ref, seg in compartment_nodes:
|
|
974
|
+
if n_id != node_id:
|
|
975
|
+
continue
|
|
976
|
+
|
|
977
|
+
if isinstance(sec_ref, str):
|
|
978
|
+
# Name-based: e.g., "dend[5]"
|
|
979
|
+
section = self.get_section(sec_ref)
|
|
980
|
+
sec_name = section.name().split(".")[-1]
|
|
981
|
+
elif isinstance(sec_ref, int):
|
|
982
|
+
# ID-based: resolve by section index
|
|
983
|
+
try:
|
|
984
|
+
section = self.get_section_by_id(sec_ref)
|
|
985
|
+
sec_name = section.name().split(".")[-1]
|
|
986
|
+
except AttributeError:
|
|
987
|
+
raise ValueError(f"Cell object does not support section lookup by index: {sec_ref}")
|
|
988
|
+
else:
|
|
989
|
+
raise TypeError(f"Unsupported section reference type: {type(sec_ref)}")
|
|
990
|
+
|
|
991
|
+
result.append((section, sec_name, seg))
|
|
992
|
+
return result
|
|
993
|
+
|
|
994
|
+
def resolve_segments_from_config(self, report_cfg) -> List[Tuple[NeuronSection, str, float]]:
|
|
995
|
+
"""Resolve segments from NEURON sections based on config."""
|
|
996
|
+
compartment = report_cfg.get("compartments", "center")
|
|
997
|
+
if compartment not in {"center", "all"}:
|
|
998
|
+
raise ValueError(
|
|
999
|
+
f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
section_name = report_cfg.get("sections", "soma")
|
|
1003
|
+
sections = self.get_sections(section_name)
|
|
1004
|
+
|
|
1005
|
+
targets = []
|
|
1006
|
+
for sec in sections:
|
|
1007
|
+
sec_name = sec.name().split(".")[-1]
|
|
1008
|
+
if compartment == "center":
|
|
1009
|
+
targets.append((sec, sec_name, 0.5))
|
|
1010
|
+
elif compartment == "all":
|
|
1011
|
+
for seg in sec:
|
|
1012
|
+
targets.append((sec, sec_name, seg.x))
|
|
1013
|
+
return targets
|
|
1014
|
+
|
|
1015
|
+
def configure_recording(self, recording_sites, variable_name, report_name):
|
|
1016
|
+
"""Configure recording of a variable on a single cell.
|
|
1017
|
+
|
|
1018
|
+
This function sets up the recording of the specified variable (e.g., membrane voltage)
|
|
1019
|
+
in the target cell, for each resolved segment.
|
|
1020
|
+
|
|
1021
|
+
Parameters
|
|
1022
|
+
----------
|
|
1023
|
+
cell : Any
|
|
1024
|
+
The cell object on which to configure recordings.
|
|
1025
|
+
|
|
1026
|
+
recording_sites : list of tuples
|
|
1027
|
+
List of tuples (section, section_name, segment) where:
|
|
1028
|
+
- section is the section object in the cell.
|
|
1029
|
+
- section_name is the name of the section.
|
|
1030
|
+
- segment is the Neuron segment index (0-1).
|
|
1031
|
+
|
|
1032
|
+
variable_name : str
|
|
1033
|
+
The name of the variable to record (e.g., "v" for membrane voltage).
|
|
1034
|
+
|
|
1035
|
+
report_name : str
|
|
1036
|
+
The name of the report (used in logging).
|
|
1037
|
+
"""
|
|
1038
|
+
node_id = self.cell_id.id
|
|
1039
|
+
|
|
1040
|
+
for sec, sec_name, seg in recording_sites:
|
|
1041
|
+
try:
|
|
1042
|
+
self.add_variable_recording(variable=variable_name, section=sec, segx=seg)
|
|
1043
|
+
logger.info(
|
|
1044
|
+
f"Recording '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}'"
|
|
1045
|
+
)
|
|
1046
|
+
except AttributeError:
|
|
1047
|
+
logger.warning(
|
|
1048
|
+
f"Recording for variable '{variable_name}' is not implemented in Cell."
|
|
1049
|
+
)
|
|
1050
|
+
return
|
|
1051
|
+
except Exception as e:
|
|
1052
|
+
logger.warning(
|
|
1053
|
+
f"Failed to record '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
def add_currents_recordings(
|
|
1057
|
+
self,
|
|
1058
|
+
section,
|
|
1059
|
+
segx: float = 0.5,
|
|
1060
|
+
*,
|
|
1061
|
+
include_nonspecific: bool = True,
|
|
1062
|
+
include_point_processes: bool = False,
|
|
1063
|
+
dt: float | None = None,
|
|
1064
|
+
) -> list[str]:
|
|
1065
|
+
"""Record all available currents (ionic + optionally nonspecific) at
|
|
1066
|
+
(section, segx)."""
|
|
1067
|
+
|
|
1068
|
+
# discover what’s available at this site
|
|
1069
|
+
available = currents_vars(section)
|
|
1070
|
+
chosen: list[str] = []
|
|
1071
|
+
|
|
1072
|
+
for name, meta in available.items():
|
|
1073
|
+
kind = meta.get("kind")
|
|
1074
|
+
|
|
1075
|
+
if kind == "ionic_current":
|
|
1076
|
+
self.add_variable_recording(name, section=section, segx=segx, dt=dt)
|
|
1077
|
+
chosen.append(name)
|
|
1078
|
+
|
|
1079
|
+
elif kind == "nonspecific_current":
|
|
1080
|
+
if not include_nonspecific:
|
|
1081
|
+
continue
|
|
1082
|
+
# density-mech nonspecific currents
|
|
1083
|
+
self.add_variable_recording(name, section=section, segx=segx, dt=dt)
|
|
1084
|
+
chosen.append(name)
|
|
1085
|
+
|
|
1086
|
+
elif kind == "point_process_current":
|
|
1087
|
+
if not include_point_processes:
|
|
1088
|
+
continue
|
|
1089
|
+
# point process nonspecific currents
|
|
1090
|
+
self.add_variable_recording(name, section=section, segx=segx, dt=dt)
|
|
1091
|
+
chosen.append(name)
|
|
1092
|
+
|
|
1093
|
+
return chosen
|