bluecellulab 2.6.71__tar.gz → 2.6.73__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.
Potentially problematic release.
This version of bluecellulab might be problematic. Click here for more details.
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.github/workflows/test.yml +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/PKG-INFO +3 -3
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/README.rst +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/core.py +147 -3
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/circuit_access/bluepy_circuit_access.py +2 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +2 -2
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/bluepy_simulation_config.py +10 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/sonata_simulation_config.py +2 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/node_id.py +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit_simulation.py +2 -2
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/plotwindow.py +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/reports/manager.py +23 -10
- bluecellulab-2.6.73/bluecellulab/reports/utils.py +227 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/reports/writers/compartment.py +45 -28
- bluecellulab-2.6.73/bluecellulab/reports/writers/spikes.py +86 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/tools.py +9 -79
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/utils.py +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab.egg-info/PKG-INFO +3 -3
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab.egg-info/requires.txt +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/pyproject.toml +1 -1
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/tox.ini +2 -1
- bluecellulab-2.6.71/bluecellulab/reports/utils.py +0 -156
- bluecellulab-2.6.71/bluecellulab/reports/writers/spikes.py +0 -61
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.compile_mod.sh +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.gitattributes +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.github/dependabot.yml +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.github/workflows/release.yml +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.gitignore +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.gitlab-ci.yml +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.readthedocs.yml +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/.zenodo.json +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/AUTHORS.txt +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/CHANGELOG.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/CITATION.cff +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/CONTRIBUTING.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/LICENSE +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/MANIFEST.in +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/Makefile +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/analysis/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/analysis/analysis.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/analysis/inject_sequence.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/analysis/plotting.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/analysis/utils.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/ballstick/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/ballstick/emodel.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/ballstick/morphology.asc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/cell_dict.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/injector.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/plotting.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/random.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/recording.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/section_distance.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/section_tools.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/serialized_sections.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/sonata_proxy.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/stimuli_generator.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/cell/template.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/circuit_access/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/circuit_access/definition.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/definition.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/sections.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/format.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/iotools.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/simulation_access.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/synapse_properties.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/validate.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/connection.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/dendrogram.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/exceptions.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/graph.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/hoc/Cell.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/hoc/RNGSettings.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/hoc/TDistFunc.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/hoc/TStim.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/hoc/fileUtils.hoc +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/importer.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/neuron_interpreter.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/psection.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/psegment.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/reports/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/reports/writers/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/reports/writers/base_writer.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/rngsettings.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/simulation/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/simulation/neuron_globals.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/simulation/parallel.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/simulation/report.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/simulation/simulation.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/stimulus/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/stimulus/circuit_stimulus_definitions.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/stimulus/factory.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/stimulus/stimulus.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/synapse/__init__.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/synapse/synapse_factory.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/synapse/synapse_types.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/type_aliases.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/validation/validation.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/verbosity.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab.egg-info/SOURCES.txt +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab.egg-info/dependency_links.txt +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab.egg-info/top_level.txt +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/Makefile +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/images/voltage-readme.png +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/make.bat +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/requirements_docs.txt +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/_static/.gitkeep +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/api.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/changelog.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/compiling-mechanisms.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/conf.py +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/contributing.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/index.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/list_of_stim.rst +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/docs/source/logo/BlueCelluLabBanner.jpg +0 -0
- {bluecellulab-2.6.71 → bluecellulab-2.6.73}/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.73
|
|
4
4
|
Summary: Biologically detailed neural network simulations and analysis.
|
|
5
5
|
Author: Blue Brain Project, EPFL
|
|
6
6
|
License: Apache2.0
|
|
@@ -19,7 +19,7 @@ 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
|
|
22
|
+
Requires-Dist: numpy>=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
|
|
@@ -102,7 +102,7 @@ Main dependencies
|
|
|
102
102
|
=================
|
|
103
103
|
|
|
104
104
|
* `Python 3.9+ <https://www.python.org/downloads/release/python-390/>`_
|
|
105
|
-
* `
|
|
105
|
+
* `NEURON <=8.2.7 <https://pypi.org/project/NEURON/>`__
|
|
106
106
|
|
|
107
107
|
Installation
|
|
108
108
|
============
|
|
@@ -67,7 +67,7 @@ Main dependencies
|
|
|
67
67
|
=================
|
|
68
68
|
|
|
69
69
|
* `Python 3.9+ <https://www.python.org/downloads/release/python-390/>`_
|
|
70
|
-
* `
|
|
70
|
+
* `NEURON <=8.2.7 <https://pypi.org/project/NEURON/>`__
|
|
71
71
|
|
|
72
72
|
Installation
|
|
73
73
|
============
|
|
@@ -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
|
|
@@ -793,13 +793,13 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
793
793
|
mech, var = variable.split(".", 1)
|
|
794
794
|
mobj = getattr(seg, mech, None)
|
|
795
795
|
if mobj is None or not hasattr(mobj, f"_ref_{var}"):
|
|
796
|
-
raise
|
|
796
|
+
raise AttributeError(
|
|
797
797
|
f"'{variable}' not recordable at {section.name()}({segx}). "
|
|
798
798
|
f"Mechanisms here: {list(section.psection()['density_mechs'].keys())}"
|
|
799
799
|
)
|
|
800
800
|
else:
|
|
801
801
|
if not hasattr(seg, f"_ref_{variable}"):
|
|
802
|
-
raise
|
|
802
|
+
raise AttributeError(
|
|
803
803
|
f"'{variable}' not recordable at {section.name()}({segx}). "
|
|
804
804
|
f"(Top-level vars are typically v/ina/ik/ica)"
|
|
805
805
|
)
|
|
@@ -911,6 +911,150 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
911
911
|
def __del__(self):
|
|
912
912
|
self.delete()
|
|
913
913
|
|
|
914
|
+
def get_section(self, section_name: str) -> NeuronSection:
|
|
915
|
+
"""Return a single, fully specified NEURON section (e.g., 'soma[0]',
|
|
916
|
+
'dend[3]').
|
|
917
|
+
|
|
918
|
+
Raises:
|
|
919
|
+
ValueError or TypeError if the section is not found or invalid.
|
|
920
|
+
"""
|
|
921
|
+
if section_name in self.sections:
|
|
922
|
+
section = self.sections[section_name]
|
|
923
|
+
if hasattr(section, "nseg"):
|
|
924
|
+
return section
|
|
925
|
+
raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
|
|
926
|
+
|
|
927
|
+
available = ", ".join(self.sections.keys())
|
|
928
|
+
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
929
|
+
|
|
930
|
+
def get_sections(self, section_name: str) -> List[NeuronSection]:
|
|
931
|
+
"""Return a list of NEURON sections.
|
|
932
|
+
|
|
933
|
+
If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
|
|
934
|
+
If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
|
|
935
|
+
|
|
936
|
+
Raises:
|
|
937
|
+
ValueError if no valid sections are found.
|
|
938
|
+
"""
|
|
939
|
+
# Try to interpret as fully qualified section name
|
|
940
|
+
try:
|
|
941
|
+
return [self.get_section(section_name)]
|
|
942
|
+
except ValueError:
|
|
943
|
+
pass # Not a precise match; try prefix match
|
|
944
|
+
|
|
945
|
+
# Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
|
|
946
|
+
matched = [
|
|
947
|
+
section for name, section in self.sections.items()
|
|
948
|
+
if name.startswith(f"{section_name}[")
|
|
949
|
+
]
|
|
950
|
+
if matched:
|
|
951
|
+
return matched
|
|
952
|
+
|
|
953
|
+
available = ", ".join(self.sections.keys())
|
|
954
|
+
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
955
|
+
|
|
956
|
+
def get_section_by_id(self, section_id: int) -> NeuronSection:
|
|
957
|
+
"""Return NEURON section by global section index (LibSONATA
|
|
958
|
+
ordering)."""
|
|
959
|
+
if not self.psections:
|
|
960
|
+
self._init_psections()
|
|
961
|
+
|
|
962
|
+
try:
|
|
963
|
+
return self.psections[int(section_id)].hsection
|
|
964
|
+
except KeyError:
|
|
965
|
+
raise IndexError(f"Section ID {section_id} is out of range for cell {self.cell_id.id}")
|
|
966
|
+
|
|
967
|
+
def resolve_segments_from_compartment_set(self, node_id, compartment_nodes) -> List[Tuple[NeuronSection, str, float]]:
|
|
968
|
+
"""Resolve segments for a cell using a predefined compartment node
|
|
969
|
+
list.
|
|
970
|
+
|
|
971
|
+
Supports both LibSONATA format ([node_id, section_id, seg]) and
|
|
972
|
+
name-based format ([node_id, section_name, seg]).
|
|
973
|
+
"""
|
|
974
|
+
result = []
|
|
975
|
+
for n_id, sec_ref, seg in compartment_nodes:
|
|
976
|
+
if n_id != node_id:
|
|
977
|
+
continue
|
|
978
|
+
|
|
979
|
+
if isinstance(sec_ref, str):
|
|
980
|
+
# Name-based: e.g., "dend[5]"
|
|
981
|
+
section = self.get_section(sec_ref)
|
|
982
|
+
sec_name = section.name().split(".")[-1]
|
|
983
|
+
elif isinstance(sec_ref, int):
|
|
984
|
+
# ID-based: resolve by section index
|
|
985
|
+
try:
|
|
986
|
+
section = self.get_section_by_id(sec_ref)
|
|
987
|
+
sec_name = section.name().split(".")[-1]
|
|
988
|
+
except AttributeError:
|
|
989
|
+
raise ValueError(f"Cell object does not support section lookup by index: {sec_ref}")
|
|
990
|
+
else:
|
|
991
|
+
raise TypeError(f"Unsupported section reference type: {type(sec_ref)}")
|
|
992
|
+
|
|
993
|
+
result.append((section, sec_name, seg))
|
|
994
|
+
return result
|
|
995
|
+
|
|
996
|
+
def resolve_segments_from_config(self, report_cfg) -> List[Tuple[NeuronSection, str, float]]:
|
|
997
|
+
"""Resolve segments from NEURON sections based on config."""
|
|
998
|
+
compartment = report_cfg.get("compartments", "center")
|
|
999
|
+
if compartment not in {"center", "all"}:
|
|
1000
|
+
raise ValueError(
|
|
1001
|
+
f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
section_name = report_cfg.get("sections", "soma")
|
|
1005
|
+
sections = self.get_sections(section_name)
|
|
1006
|
+
|
|
1007
|
+
targets = []
|
|
1008
|
+
for sec in sections:
|
|
1009
|
+
sec_name = sec.name().split(".")[-1]
|
|
1010
|
+
if compartment == "center":
|
|
1011
|
+
targets.append((sec, sec_name, 0.5))
|
|
1012
|
+
elif compartment == "all":
|
|
1013
|
+
for seg in sec:
|
|
1014
|
+
targets.append((sec, sec_name, seg.x))
|
|
1015
|
+
return targets
|
|
1016
|
+
|
|
1017
|
+
def configure_recording(self, recording_sites, variable_name, report_name):
|
|
1018
|
+
"""Configure recording of a variable on a single cell.
|
|
1019
|
+
|
|
1020
|
+
This function sets up the recording of the specified variable (e.g., membrane voltage)
|
|
1021
|
+
in the target cell, for each resolved segment.
|
|
1022
|
+
|
|
1023
|
+
Parameters
|
|
1024
|
+
----------
|
|
1025
|
+
cell : Any
|
|
1026
|
+
The cell object on which to configure recordings.
|
|
1027
|
+
|
|
1028
|
+
recording_sites : list of tuples
|
|
1029
|
+
List of tuples (section, section_name, segment) where:
|
|
1030
|
+
- section is the section object in the cell.
|
|
1031
|
+
- section_name is the name of the section.
|
|
1032
|
+
- segment is the Neuron segment index (0-1).
|
|
1033
|
+
|
|
1034
|
+
variable_name : str
|
|
1035
|
+
The name of the variable to record (e.g., "v" for membrane voltage).
|
|
1036
|
+
|
|
1037
|
+
report_name : str
|
|
1038
|
+
The name of the report (used in logging).
|
|
1039
|
+
"""
|
|
1040
|
+
node_id = self.cell_id.id
|
|
1041
|
+
|
|
1042
|
+
for sec, sec_name, seg in recording_sites:
|
|
1043
|
+
try:
|
|
1044
|
+
self.add_variable_recording(variable=variable_name, section=sec, segx=seg)
|
|
1045
|
+
logger.info(
|
|
1046
|
+
f"Recording '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}'"
|
|
1047
|
+
)
|
|
1048
|
+
except AttributeError:
|
|
1049
|
+
logger.warning(
|
|
1050
|
+
f"Recording for variable '{variable_name}' is not implemented in Cell."
|
|
1051
|
+
)
|
|
1052
|
+
return
|
|
1053
|
+
except Exception as e:
|
|
1054
|
+
logger.warning(
|
|
1055
|
+
f"Failed to record '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
|
|
1056
|
+
)
|
|
1057
|
+
|
|
914
1058
|
def add_currents_recordings(
|
|
915
1059
|
self,
|
|
916
1060
|
section,
|
|
@@ -19,6 +19,7 @@ import os
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Optional
|
|
21
21
|
|
|
22
|
+
from bluecellulab.circuit.circuit_access.definition import CircuitAccess
|
|
22
23
|
import neuron
|
|
23
24
|
import pandas as pd
|
|
24
25
|
from bluecellulab import circuit
|
|
@@ -42,7 +43,7 @@ if BLUEPY_AVAILABLE:
|
|
|
42
43
|
logger = logging.getLogger(__name__)
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
class BluepyCircuitAccess:
|
|
46
|
+
class BluepyCircuitAccess(CircuitAccess):
|
|
46
47
|
"""Bluepy implementation of CircuitAccess protocol."""
|
|
47
48
|
|
|
48
49
|
def __init__(self, simulation_config: str | Path | SimulationConfig) -> None:
|
|
@@ -25,7 +25,7 @@ from bluepysnap import Circuit as SnapCircuit
|
|
|
25
25
|
import neuron
|
|
26
26
|
import pandas as pd
|
|
27
27
|
from bluecellulab import circuit
|
|
28
|
-
from bluecellulab.circuit.circuit_access.definition import EmodelProperties
|
|
28
|
+
from bluecellulab.circuit.circuit_access.definition import CircuitAccess, EmodelProperties
|
|
29
29
|
from bluecellulab.circuit import CellId, SynapseProperty
|
|
30
30
|
from bluecellulab.circuit.config import SimulationConfig
|
|
31
31
|
from bluecellulab.circuit.synapse_properties import SynapseProperties
|
|
@@ -38,7 +38,7 @@ from bluecellulab.circuit.synapse_properties import (
|
|
|
38
38
|
logger = logging.getLogger(__name__)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class SonataCircuitAccess:
|
|
41
|
+
class SonataCircuitAccess(CircuitAccess):
|
|
42
42
|
"""Sonata implementation of CircuitAccess protocol."""
|
|
43
43
|
|
|
44
44
|
def __init__(self, simulation_config: str | Path | SimulationConfig) -> None:
|
{bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/bluepy_simulation_config.py
RENAMED
|
@@ -188,6 +188,16 @@ class BluepySimulationConfig:
|
|
|
188
188
|
else:
|
|
189
189
|
return None
|
|
190
190
|
|
|
191
|
+
@property
|
|
192
|
+
def tstart(self) -> Optional[float]:
|
|
193
|
+
return 0.0
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def tstop(self) -> Optional[float]:
|
|
197
|
+
if 'Duration' in self.impl.Run:
|
|
198
|
+
return float(self.impl.Run['Duration'])
|
|
199
|
+
return None
|
|
200
|
+
|
|
191
201
|
def add_connection_override(
|
|
192
202
|
self,
|
|
193
203
|
connection_override: ConnectionOverrides
|
{bluecellulab-2.6.71 → bluecellulab-2.6.73}/bluecellulab/circuit/config/sonata_simulation_config.py
RENAMED
|
@@ -130,6 +130,8 @@ class SonataSimulationConfig:
|
|
|
130
130
|
"""Resolve the full path for the report output file."""
|
|
131
131
|
output_dir = Path(self.output_root_path)
|
|
132
132
|
file_name = report_cfg.get("file_name", f"{report_key}.h5")
|
|
133
|
+
if not file_name.endswith(".h5"):
|
|
134
|
+
file_name += ".h5"
|
|
133
135
|
return output_dir / file_name
|
|
134
136
|
|
|
135
137
|
@property
|
|
@@ -31,6 +31,6 @@ def create_cell_id(cell_id: int | tuple[str, int] | CellId) -> CellId:
|
|
|
31
31
|
return CellId("", cell_id)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def create_cell_ids(cell_ids: list[int
|
|
34
|
+
def create_cell_ids(cell_ids: list[int | tuple[str, int] | CellId]) -> list[CellId]:
|
|
35
35
|
"""Make a list of CellId from a list of tuple or int."""
|
|
36
36
|
return [create_cell_id(cell_id) for cell_id in cell_ids]
|
|
@@ -125,7 +125,7 @@ class CircuitSimulation:
|
|
|
125
125
|
|
|
126
126
|
def instantiate_gids(
|
|
127
127
|
self,
|
|
128
|
-
cells: int | tuple[str, int] | list[int
|
|
128
|
+
cells: int | tuple[str, int] | list[int | tuple[str, int]],
|
|
129
129
|
add_replay: bool = False,
|
|
130
130
|
add_stimuli: bool = False,
|
|
131
131
|
add_synapses: bool = False,
|
|
@@ -223,7 +223,7 @@ class CircuitSimulation:
|
|
|
223
223
|
will automatically set this option to
|
|
224
224
|
True.
|
|
225
225
|
"""
|
|
226
|
-
if not isinstance(cells,
|
|
226
|
+
if not isinstance(cells, list):
|
|
227
227
|
cells = [cells]
|
|
228
228
|
|
|
229
229
|
# convert to CellId objects
|
|
@@ -42,7 +42,7 @@ class PlotWindow:
|
|
|
42
42
|
linenumber = 0
|
|
43
43
|
for var_name in self.var_list:
|
|
44
44
|
recording = self.cell.get_recording(var_name)
|
|
45
|
-
if recording:
|
|
45
|
+
if recording is not None and recording.size > 0:
|
|
46
46
|
time = self.cell.get_time()
|
|
47
47
|
else:
|
|
48
48
|
time = self.cell.get_time()[1:]
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
from typing import Optional, Dict
|
|
16
16
|
from bluecellulab.reports.writers import get_writer
|
|
17
|
-
from bluecellulab.reports.utils import extract_spikes_from_cells # helper you already have / write
|
|
17
|
+
from bluecellulab.reports.utils import SUPPORTED_REPORT_TYPES, extract_spikes_from_cells # helper you already have / write
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class ReportManager:
|
|
@@ -52,21 +56,30 @@ class ReportManager:
|
|
|
52
56
|
|
|
53
57
|
def _write_voltage_reports(self, cells_or_traces):
|
|
54
58
|
for name, rcfg in self.cfg.get_report_entries().items():
|
|
55
|
-
if rcfg.get("type")
|
|
59
|
+
if rcfg.get("type") not in SUPPORTED_REPORT_TYPES:
|
|
56
60
|
continue
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
if
|
|
62
|
+
report_type = rcfg.get("type")
|
|
63
|
+
if report_type == "compartment_set":
|
|
60
64
|
if rcfg.get("cells") is not None:
|
|
61
|
-
raise ValueError("'cells' may not be set
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if rcfg.get("compartments") not
|
|
65
|
+
raise ValueError("'cells' may not be set when using 'compartment_set' report type.")
|
|
66
|
+
if rcfg.get("sections") is not None:
|
|
67
|
+
raise ValueError("'sections' may not be set when using 'compartment_set' report type.")
|
|
68
|
+
if rcfg.get("compartments") is not None:
|
|
69
|
+
raise ValueError("'compartments' may not be set when using 'compartment_set' report type.")
|
|
70
|
+
src_sets = self.cfg.get_compartment_sets()
|
|
71
|
+
elif report_type == "compartment":
|
|
72
|
+
compartments = rcfg.get("compartments") or "center"
|
|
73
|
+
if compartments not in ("center", "all"):
|
|
65
74
|
raise ValueError("invalid 'compartments' value")
|
|
66
|
-
|
|
75
|
+
if rcfg.get("cells") is None:
|
|
76
|
+
raise ValueError("'cells' must be specified when using compartment reports")
|
|
77
|
+
src_sets = self.cfg.get_node_sets()
|
|
78
|
+
else:
|
|
79
|
+
logger.error(f"Unsupported report type '{report_type}' in configuration for report '{name}'")
|
|
67
80
|
|
|
68
81
|
rcfg["_source_sets"] = src_sets
|
|
69
|
-
rcfg["
|
|
82
|
+
rcfg["name"] = name
|
|
70
83
|
|
|
71
84
|
out_path = self.cfg.report_file_path(rcfg, name)
|
|
72
85
|
writer = get_writer("compartment")(rcfg, out_path, self.dt)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Copyright 2025 Open Brain Institute
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Report class of bluecellulab."""
|
|
15
|
+
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Dict, Any, List
|
|
19
|
+
|
|
20
|
+
from bluecellulab.tools import (
|
|
21
|
+
resolve_source_nodes,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
SUPPORTED_REPORT_TYPES = {"compartment", "compartment_set"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def configure_all_reports(cells, simulation_config):
|
|
30
|
+
"""Configure recordings for all reports defined in the simulation
|
|
31
|
+
configuration.
|
|
32
|
+
|
|
33
|
+
This iterates through all report entries, resolves source nodes or compartments,
|
|
34
|
+
and configures the corresponding recordings on each cell.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
cells : dict
|
|
39
|
+
Mapping from (population, gid) → Cell object.
|
|
40
|
+
|
|
41
|
+
simulation_config : Any
|
|
42
|
+
Simulation configuration object providing report entries,
|
|
43
|
+
node sets, and compartment sets.
|
|
44
|
+
"""
|
|
45
|
+
report_entries = simulation_config.get_report_entries()
|
|
46
|
+
|
|
47
|
+
for report_name, report_cfg in report_entries.items():
|
|
48
|
+
report_type = report_cfg.get("type", "compartment")
|
|
49
|
+
if report_type == "compartment_set":
|
|
50
|
+
source_sets = simulation_config.get_compartment_sets()
|
|
51
|
+
source_name = report_cfg.get("compartment_set")
|
|
52
|
+
if not source_name:
|
|
53
|
+
logger.warning(
|
|
54
|
+
f"Report '{report_name}' does not specify a node set in 'compartment_set' for {report_type}."
|
|
55
|
+
)
|
|
56
|
+
continue
|
|
57
|
+
elif report_type == "compartment":
|
|
58
|
+
source_sets = simulation_config.get_node_sets()
|
|
59
|
+
source_name = report_cfg.get("cells")
|
|
60
|
+
if not source_name:
|
|
61
|
+
logger.warning(
|
|
62
|
+
f"Report '{report_name}' does not specify a node set in 'cells' for {report_type}."
|
|
63
|
+
)
|
|
64
|
+
continue
|
|
65
|
+
else:
|
|
66
|
+
raise NotImplementedError(
|
|
67
|
+
f"Report type '{report_type}' is not supported. "
|
|
68
|
+
f"Supported types: {SUPPORTED_REPORT_TYPES}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
source = source_sets.get(source_name)
|
|
72
|
+
if not source:
|
|
73
|
+
logger.warning(
|
|
74
|
+
f"{report_type} '{source_name}' not found for report '{report_name}', skipping recording."
|
|
75
|
+
)
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
population = source["population"]
|
|
79
|
+
node_ids, compartment_nodes = resolve_source_nodes(
|
|
80
|
+
source, report_type, cells, population
|
|
81
|
+
)
|
|
82
|
+
recording_sites_per_cell = build_recording_sites(
|
|
83
|
+
cells, node_ids, population, report_type, report_cfg, compartment_nodes
|
|
84
|
+
)
|
|
85
|
+
variable_name = report_cfg.get("variable_name", "v")
|
|
86
|
+
|
|
87
|
+
for node_id, recording_sites in recording_sites_per_cell.items():
|
|
88
|
+
cell = cells.get((population, node_id))
|
|
89
|
+
if not cell or recording_sites is None:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
cell.configure_recording(recording_sites, variable_name, report_name)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_recording_sites(
|
|
96
|
+
cells_or_traces, node_ids, population, report_type, report_cfg, compartment_nodes
|
|
97
|
+
):
|
|
98
|
+
"""Build per-cell recording sites based on source type and report
|
|
99
|
+
configuration.
|
|
100
|
+
|
|
101
|
+
This function resolves the segments (section, name, seg.x) where variables
|
|
102
|
+
should be recorded for each cell, based on either a node set (standard
|
|
103
|
+
compartment reports) or a compartment set (predefined segment list).
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
cells_or_traces : dict
|
|
108
|
+
Either a mapping from (population, node_id) to Cell objects (live sim),
|
|
109
|
+
or from gid_key strings to trace dicts (gathered traces on rank 0).
|
|
110
|
+
|
|
111
|
+
node_ids : list of int
|
|
112
|
+
List of node IDs for which recordings should be configured.
|
|
113
|
+
|
|
114
|
+
population : str
|
|
115
|
+
Name of the population to which the cells belong.
|
|
116
|
+
|
|
117
|
+
report_type : str
|
|
118
|
+
The report type, either 'compartment_set' or 'compartment'.
|
|
119
|
+
|
|
120
|
+
report_cfg : dict
|
|
121
|
+
Configuration dictionary specifying report parameters
|
|
122
|
+
|
|
123
|
+
compartment_nodes : list or None
|
|
124
|
+
Optional list of [node_id, section_name, seg_x] defining segment locations
|
|
125
|
+
for each cell (used if report_type == 'compartment_set').
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
dict
|
|
130
|
+
Mapping from node ID to list of recording site tuples:
|
|
131
|
+
(section_object, section_name, seg_x).
|
|
132
|
+
"""
|
|
133
|
+
targets_per_cell = {}
|
|
134
|
+
|
|
135
|
+
for node_id in node_ids:
|
|
136
|
+
# Handle both (pop, id) and "pop_id" keys
|
|
137
|
+
key = (population, node_id)
|
|
138
|
+
cell_or_trace = cells_or_traces.get(key) or cells_or_traces.get(f"{population}_{node_id}")
|
|
139
|
+
if not cell_or_trace:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if isinstance(cell_or_trace, dict): # Trace dict, not Cell
|
|
143
|
+
if report_type == "compartment_set":
|
|
144
|
+
# Find all entries matching node_id
|
|
145
|
+
targets = [
|
|
146
|
+
(None, section_name, segx)
|
|
147
|
+
for nid, section_name, segx in compartment_nodes
|
|
148
|
+
if nid == node_id
|
|
149
|
+
]
|
|
150
|
+
elif report_type == "compartment":
|
|
151
|
+
section_name = report_cfg.get("sections", "soma")
|
|
152
|
+
segx = 0.5 if report_cfg.get("compartments", "center") == "center" else 0.0
|
|
153
|
+
targets = [(None, f"{section_name}[0]", segx)]
|
|
154
|
+
else:
|
|
155
|
+
raise NotImplementedError(
|
|
156
|
+
f"Unsupported report type '{report_type}' in trace-based output."
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
# Cell object
|
|
160
|
+
if report_type == "compartment_set":
|
|
161
|
+
targets = cell_or_trace.resolve_segments_from_compartment_set(
|
|
162
|
+
node_id, compartment_nodes
|
|
163
|
+
)
|
|
164
|
+
elif report_type == "compartment":
|
|
165
|
+
targets = cell_or_trace.resolve_segments_from_config(report_cfg)
|
|
166
|
+
else:
|
|
167
|
+
raise NotImplementedError(
|
|
168
|
+
f"Report type '{report_type}' is not supported. "
|
|
169
|
+
f"Supported types: {SUPPORTED_REPORT_TYPES}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
targets_per_cell[node_id] = targets
|
|
173
|
+
|
|
174
|
+
return targets_per_cell
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def extract_spikes_from_cells(
|
|
178
|
+
cells: Dict[Any, Any],
|
|
179
|
+
location: str = "soma",
|
|
180
|
+
threshold: float = -20.0,
|
|
181
|
+
) -> Dict[str, Dict[int, list]]:
|
|
182
|
+
"""Extract spike times from recorded cells, grouped by population.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
cells : dict
|
|
187
|
+
Mapping from (population, gid) → Cell object, or similar.
|
|
188
|
+
|
|
189
|
+
location : str
|
|
190
|
+
Recording location passed to Cell.get_recorded_spikes().
|
|
191
|
+
|
|
192
|
+
threshold : float
|
|
193
|
+
Voltage threshold (mV) used for spike detection.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
spikes_by_population : dict
|
|
198
|
+
{population → {gid_int → [spike_times_ms]}}
|
|
199
|
+
"""
|
|
200
|
+
spikes_by_pop: defaultdict[str, Dict[int, List[float]]] = defaultdict(dict)
|
|
201
|
+
|
|
202
|
+
for key, cell in cells.items():
|
|
203
|
+
# Resolve the key to (population, gid)
|
|
204
|
+
if isinstance(key, tuple):
|
|
205
|
+
pop, gid = key
|
|
206
|
+
elif isinstance(key, str):
|
|
207
|
+
try:
|
|
208
|
+
pop, gid_str = key.rsplit("_", 1)
|
|
209
|
+
gid = int(gid_str)
|
|
210
|
+
except Exception:
|
|
211
|
+
raise ValueError(
|
|
212
|
+
f"Cell key '{key}' could not be parsed as 'population_gid'"
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
raise ValueError(f"Cell key '{key}' is not a recognized format.")
|
|
216
|
+
|
|
217
|
+
if not hasattr(cell, "get_recorded_spikes"):
|
|
218
|
+
raise TypeError(
|
|
219
|
+
f"Cannot extract spikes: cell entry {key} is not a Cell object (got {type(cell)}). "
|
|
220
|
+
"If you have precomputed traces, pass them as `spikes_by_pop`."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
times = cell.get_recorded_spikes(location=location, threshold=threshold)
|
|
224
|
+
# Always assign, even if empty
|
|
225
|
+
spikes_by_pop[pop][gid] = list(times) if times is not None else []
|
|
226
|
+
|
|
227
|
+
return dict(spikes_by_pop)
|