bluecellulab 2.6.70__py3-none-any.whl → 2.6.72__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.
- bluecellulab/cell/core.py +159 -4
- bluecellulab/circuit/config/sonata_simulation_config.py +18 -2
- bluecellulab/reports/manager.py +23 -10
- bluecellulab/reports/utils.py +133 -62
- bluecellulab/reports/writers/compartment.py +45 -28
- bluecellulab/reports/writers/spikes.py +45 -20
- bluecellulab/stimulus/circuit_stimulus_definitions.py +3 -9
- bluecellulab/tools.py +9 -79
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/METADATA +1 -1
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/RECORD +14 -14
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/WHEEL +0 -0
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/licenses/AUTHORS.txt +0 -0
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/licenses/LICENSE +0 -0
- {bluecellulab-2.6.70.dist-info → bluecellulab-2.6.72.dist-info}/top_level.txt +0 -0
bluecellulab/cell/core.py
CHANGED
|
@@ -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
|
)
|
|
@@ -840,7 +840,18 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
840
840
|
connected to that cell."""
|
|
841
841
|
if self.sonata_proxy is None:
|
|
842
842
|
raise BluecellulabError("Cell: add_synapse_replay requires a sonata proxy.")
|
|
843
|
-
|
|
843
|
+
|
|
844
|
+
file_path = Path(stimulus.spike_file).expanduser()
|
|
845
|
+
if not file_path.is_absolute():
|
|
846
|
+
config_dir = stimulus.config_dir
|
|
847
|
+
if config_dir is not None:
|
|
848
|
+
file_path = Path(config_dir) / file_path
|
|
849
|
+
|
|
850
|
+
file_path = file_path.resolve()
|
|
851
|
+
|
|
852
|
+
if not file_path.exists():
|
|
853
|
+
raise FileNotFoundError(f"Spike file not found: {str(file_path)}")
|
|
854
|
+
synapse_spikes: dict = get_synapse_replay_spikes(str(file_path))
|
|
844
855
|
for synapse_id, synapse in self.synapses.items():
|
|
845
856
|
source_population = synapse.syn_description["source_population_name"]
|
|
846
857
|
pre_gid = CellId(
|
|
@@ -900,6 +911,150 @@ class Cell(InjectableMixin, PlottableMixin):
|
|
|
900
911
|
def __del__(self):
|
|
901
912
|
self.delete()
|
|
902
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
|
+
|
|
903
1058
|
def add_currents_recordings(
|
|
904
1059
|
self,
|
|
905
1060
|
section,
|
|
@@ -55,8 +55,9 @@ class SonataSimulationConfig:
|
|
|
55
55
|
inputs = self.impl.config.get("inputs")
|
|
56
56
|
if inputs is None:
|
|
57
57
|
return result
|
|
58
|
+
config_dir = self._get_config_dir()
|
|
58
59
|
for value in inputs.values():
|
|
59
|
-
stimulus = Stimulus.from_sonata(value)
|
|
60
|
+
stimulus = Stimulus.from_sonata(value, config_dir=config_dir)
|
|
60
61
|
if stimulus:
|
|
61
62
|
result.append(stimulus)
|
|
62
63
|
return result
|
|
@@ -84,7 +85,11 @@ class SonataSimulationConfig:
|
|
|
84
85
|
filepath = self.impl.config.get("compartment_sets_file")
|
|
85
86
|
if not filepath:
|
|
86
87
|
raise ValueError("No 'compartment_sets_file' entry found in SONATA config.")
|
|
87
|
-
|
|
88
|
+
config_dir = self._get_config_dir()
|
|
89
|
+
full_path = Path(filepath)
|
|
90
|
+
if config_dir is not None and not full_path.is_absolute():
|
|
91
|
+
full_path = Path(config_dir) / filepath
|
|
92
|
+
with open(full_path, 'r') as f:
|
|
88
93
|
return json.load(f)
|
|
89
94
|
|
|
90
95
|
@lru_cache(maxsize=1)
|
|
@@ -125,6 +130,8 @@ class SonataSimulationConfig:
|
|
|
125
130
|
"""Resolve the full path for the report output file."""
|
|
126
131
|
output_dir = Path(self.output_root_path)
|
|
127
132
|
file_name = report_cfg.get("file_name", f"{report_key}.h5")
|
|
133
|
+
if not file_name.endswith(".h5"):
|
|
134
|
+
file_name += ".h5"
|
|
128
135
|
return output_dir / file_name
|
|
129
136
|
|
|
130
137
|
@property
|
|
@@ -214,3 +221,12 @@ class SonataSimulationConfig:
|
|
|
214
221
|
connection_override: ConnectionOverrides
|
|
215
222
|
) -> None:
|
|
216
223
|
self._connection_overrides.append(connection_override)
|
|
224
|
+
|
|
225
|
+
def _get_config_dir(self):
|
|
226
|
+
# Prefer config_path, fallback to _simulation_config_path
|
|
227
|
+
config_path = getattr(self.impl, "config_path", None)
|
|
228
|
+
if config_path is None:
|
|
229
|
+
sim_config_path = getattr(self.impl, "_simulation_config_path", None)
|
|
230
|
+
if sim_config_path is not None:
|
|
231
|
+
config_path = Path(sim_config_path)
|
|
232
|
+
return str(config_path.parent) if config_path is not None else None
|
bluecellulab/reports/manager.py
CHANGED
|
@@ -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)
|
bluecellulab/reports/utils.py
CHANGED
|
@@ -17,50 +17,13 @@ from collections import defaultdict
|
|
|
17
17
|
import logging
|
|
18
18
|
from typing import Dict, Any, List
|
|
19
19
|
|
|
20
|
-
from bluecellulab.tools import
|
|
20
|
+
from bluecellulab.tools import (
|
|
21
|
+
resolve_source_nodes,
|
|
22
|
+
)
|
|
21
23
|
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
def _configure_recording(cell, report_cfg, source, source_type, report_name):
|
|
26
|
-
"""Configure recording of a variable on a single cell.
|
|
27
|
-
|
|
28
|
-
This function sets up the recording of the specified variable (e.g., membrane voltage)
|
|
29
|
-
in the target cell, for each resolved segment.
|
|
30
|
-
|
|
31
|
-
Parameters
|
|
32
|
-
----------
|
|
33
|
-
cell : Any
|
|
34
|
-
The cell object on which to configure recordings.
|
|
35
|
-
|
|
36
|
-
report_cfg : dict
|
|
37
|
-
The configuration dictionary for this report.
|
|
38
|
-
|
|
39
|
-
source : dict
|
|
40
|
-
The source definition specifying nodes or compartments.
|
|
41
|
-
|
|
42
|
-
source_type : str
|
|
43
|
-
Either "node_set" or "compartment_set".
|
|
44
|
-
|
|
45
|
-
report_name : str
|
|
46
|
-
The name of the report (used in logging).
|
|
47
|
-
"""
|
|
48
|
-
variable = report_cfg.get("variable_name", "v")
|
|
49
|
-
|
|
50
|
-
node_id = cell.cell_id
|
|
51
|
-
compartment_nodes = source.get("compartment_set") if source_type == "compartment_set" else None
|
|
52
|
-
|
|
53
|
-
targets = resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type)
|
|
54
|
-
for sec, sec_name, seg in targets:
|
|
55
|
-
try:
|
|
56
|
-
cell.add_variable_recording(variable=variable, section=sec, segx=seg)
|
|
57
|
-
except AttributeError:
|
|
58
|
-
logger.warning(f"Recording for variable '{variable}' is not implemented in Cell.")
|
|
59
|
-
return
|
|
60
|
-
except Exception as e:
|
|
61
|
-
logger.warning(
|
|
62
|
-
f"Failed to record '{variable}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
|
|
63
|
-
)
|
|
26
|
+
SUPPORTED_REPORT_TYPES = {"compartment", "compartment_set"}
|
|
64
27
|
|
|
65
28
|
|
|
66
29
|
def configure_all_reports(cells, simulation_config):
|
|
@@ -83,39 +46,132 @@ def configure_all_reports(cells, simulation_config):
|
|
|
83
46
|
|
|
84
47
|
for report_name, report_cfg in report_entries.items():
|
|
85
48
|
report_type = report_cfg.get("type", "compartment")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if report_type != "compartment":
|
|
89
|
-
raise NotImplementedError(f"Report type '{report_type}' is not supported.")
|
|
90
|
-
|
|
91
|
-
if section == "compartment_set":
|
|
92
|
-
source_type = "compartment_set"
|
|
49
|
+
if report_type == "compartment_set":
|
|
93
50
|
source_sets = simulation_config.get_compartment_sets()
|
|
94
|
-
source_name = report_cfg.get("
|
|
51
|
+
source_name = report_cfg.get("compartment_set")
|
|
95
52
|
if not source_name:
|
|
96
|
-
logger.warning(
|
|
53
|
+
logger.warning(
|
|
54
|
+
f"Report '{report_name}' does not specify a node set in 'compartment_set' for {report_type}."
|
|
55
|
+
)
|
|
97
56
|
continue
|
|
98
|
-
|
|
99
|
-
source_type = "node_set"
|
|
57
|
+
elif report_type == "compartment":
|
|
100
58
|
source_sets = simulation_config.get_node_sets()
|
|
101
59
|
source_name = report_cfg.get("cells")
|
|
102
60
|
if not source_name:
|
|
103
|
-
logger.warning(
|
|
61
|
+
logger.warning(
|
|
62
|
+
f"Report '{report_name}' does not specify a node set in 'cells' for {report_type}."
|
|
63
|
+
)
|
|
104
64
|
continue
|
|
65
|
+
else:
|
|
66
|
+
raise NotImplementedError(
|
|
67
|
+
f"Report type '{report_type}' is not supported. "
|
|
68
|
+
f"Supported types: {SUPPORTED_REPORT_TYPES}"
|
|
69
|
+
)
|
|
105
70
|
|
|
106
71
|
source = source_sets.get(source_name)
|
|
107
72
|
if not source:
|
|
108
|
-
logger.warning(
|
|
73
|
+
logger.warning(
|
|
74
|
+
f"{report_type} '{source_name}' not found for report '{report_name}', skipping recording."
|
|
75
|
+
)
|
|
109
76
|
continue
|
|
110
77
|
|
|
111
78
|
population = source["population"]
|
|
112
|
-
node_ids,
|
|
113
|
-
|
|
114
|
-
|
|
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():
|
|
115
88
|
cell = cells.get((population, node_id))
|
|
116
|
-
if not cell:
|
|
89
|
+
if not cell or recording_sites is None:
|
|
117
90
|
continue
|
|
118
|
-
|
|
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
|
|
119
175
|
|
|
120
176
|
|
|
121
177
|
def extract_spikes_from_cells(
|
|
@@ -144,13 +200,28 @@ def extract_spikes_from_cells(
|
|
|
144
200
|
spikes_by_pop: defaultdict[str, Dict[int, List[float]]] = defaultdict(dict)
|
|
145
201
|
|
|
146
202
|
for key, cell in cells.items():
|
|
203
|
+
# Resolve the key to (population, gid)
|
|
147
204
|
if isinstance(key, tuple):
|
|
148
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
|
+
)
|
|
149
214
|
else:
|
|
150
|
-
raise ValueError(f"Cell key {key} is not a
|
|
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
|
+
)
|
|
151
222
|
|
|
152
223
|
times = cell.get_recorded_spikes(location=location, threshold=threshold)
|
|
153
|
-
|
|
154
|
-
|
|
224
|
+
# Always assign, even if empty
|
|
225
|
+
spikes_by_pop[pop][gid] = list(times) if times is not None else []
|
|
155
226
|
|
|
156
227
|
return dict(spikes_by_pop)
|
|
@@ -16,10 +16,11 @@ from pathlib import Path
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import h5py
|
|
18
18
|
from typing import Dict, List
|
|
19
|
+
|
|
19
20
|
from .base_writer import BaseReportWriter
|
|
20
21
|
from bluecellulab.reports.utils import (
|
|
22
|
+
build_recording_sites,
|
|
21
23
|
resolve_source_nodes,
|
|
22
|
-
resolve_segments,
|
|
23
24
|
)
|
|
24
25
|
import logging
|
|
25
26
|
|
|
@@ -31,52 +32,67 @@ class CompartmentReportWriter(BaseReportWriter):
|
|
|
31
32
|
|
|
32
33
|
def write(self, cells: Dict, tstart=0):
|
|
33
34
|
report_name = self.cfg.get("name", "unnamed")
|
|
34
|
-
# section = self.cfg.get("sections")
|
|
35
35
|
variable = self.cfg.get("variable_name", "v")
|
|
36
|
+
report_type = self.cfg.get("type", "compartment")
|
|
36
37
|
|
|
38
|
+
# Resolve source set
|
|
37
39
|
source_sets = self.cfg["_source_sets"]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
if report_type == "compartment":
|
|
41
|
+
src_name = self.cfg.get("cells")
|
|
42
|
+
elif report_type == "compartment_set":
|
|
43
|
+
src_name = self.cfg.get("compartment_set")
|
|
44
|
+
else:
|
|
45
|
+
raise NotImplementedError(
|
|
46
|
+
f"Unsupported report type '{report_type}' in configuration for report '{report_name}'"
|
|
47
|
+
)
|
|
48
|
+
|
|
40
49
|
src = source_sets.get(src_name)
|
|
41
50
|
if not src:
|
|
42
|
-
logger.warning(f"{
|
|
51
|
+
logger.warning(f"{report_type} '{src_name}' not found – skipping '{report_name}'.")
|
|
43
52
|
return
|
|
44
53
|
|
|
45
54
|
population = src["population"]
|
|
46
|
-
node_ids, comp_nodes = resolve_source_nodes(src,
|
|
55
|
+
node_ids, comp_nodes = resolve_source_nodes(src, report_type, cells, population)
|
|
56
|
+
recording_sites_per_cell = build_recording_sites(
|
|
57
|
+
cells, node_ids, population, report_type, self.cfg, comp_nodes
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Detect trace mode
|
|
61
|
+
sample_cell = next(iter(cells.values()))
|
|
62
|
+
is_trace_mode = isinstance(sample_cell, dict)
|
|
47
63
|
|
|
48
64
|
data_matrix: List[np.ndarray] = []
|
|
49
65
|
node_id_list: List[int] = []
|
|
50
66
|
idx_ptr: List[int] = [0]
|
|
51
67
|
elem_ids: List[int] = []
|
|
52
68
|
|
|
53
|
-
for nid in
|
|
69
|
+
for nid in sorted(recording_sites_per_cell):
|
|
70
|
+
recording_sites = recording_sites_per_cell[nid]
|
|
54
71
|
cell = cells.get((population, nid)) or cells.get(f"{population}_{nid}")
|
|
55
72
|
if cell is None:
|
|
73
|
+
logger.warning(f"Cell or trace for ({population}, {nid}) not found – skipping.")
|
|
56
74
|
continue
|
|
57
75
|
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
node_id_list.append(nid)
|
|
63
|
-
elem_ids.append(len(elem_ids))
|
|
64
|
-
idx_ptr.append(idx_ptr[-1] + 1)
|
|
65
|
-
continue
|
|
66
|
-
|
|
67
|
-
targets = resolve_segments(cell, self.cfg, nid, comp_nodes, source_type)
|
|
68
|
-
for sec, sec_name, seg in targets:
|
|
69
|
-
try:
|
|
70
|
-
if hasattr(cell, "get_variable_recording"):
|
|
71
|
-
trace = cell.get_variable_recording(variable=variable, section=sec, segx=seg)
|
|
72
|
-
else:
|
|
73
|
-
trace = np.asarray(cell["voltage"], dtype=np.float32)
|
|
74
|
-
data_matrix.append(trace)
|
|
76
|
+
if is_trace_mode:
|
|
77
|
+
voltage = np.asarray(cell["voltage"], dtype=np.float32)
|
|
78
|
+
for sec, sec_name, seg in recording_sites:
|
|
79
|
+
data_matrix.append(voltage)
|
|
75
80
|
node_id_list.append(nid)
|
|
76
81
|
elem_ids.append(len(elem_ids))
|
|
77
82
|
idx_ptr.append(idx_ptr[-1] + 1)
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
else:
|
|
84
|
+
for sec, sec_name, seg in recording_sites:
|
|
85
|
+
try:
|
|
86
|
+
if hasattr(cell, "get_variable_recording"):
|
|
87
|
+
trace = cell.get_variable_recording(variable=variable, section=sec, segx=seg)
|
|
88
|
+
else:
|
|
89
|
+
trace = np.asarray(cell["voltage"], dtype=np.float32)
|
|
90
|
+
data_matrix.append(trace)
|
|
91
|
+
node_id_list.append(nid)
|
|
92
|
+
elem_ids.append(len(elem_ids))
|
|
93
|
+
idx_ptr.append(idx_ptr[-1] + 1)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.warning(f"Failed recording {nid}:{sec_name}@{seg}: {e}")
|
|
80
96
|
|
|
81
97
|
if not data_matrix:
|
|
82
98
|
logger.warning(f"No data for report '{report_name}'.")
|
|
@@ -148,7 +164,8 @@ class CompartmentReportWriter(BaseReportWriter):
|
|
|
148
164
|
if dt_report < sim_dt:
|
|
149
165
|
logger.warning(
|
|
150
166
|
f"Requested report dt={dt_report} ms is finer than simulation dt={sim_dt} ms. "
|
|
151
|
-
f"Clamping report dt to {sim_dt} ms."
|
|
167
|
+
f"Clamping report dt to {sim_dt} ms. "
|
|
168
|
+
f"To achieve finer temporal resolution, reduce the simulation dt in your config."
|
|
152
169
|
)
|
|
153
170
|
dt_report = sim_dt
|
|
154
171
|
|
|
@@ -161,7 +178,7 @@ class CompartmentReportWriter(BaseReportWriter):
|
|
|
161
178
|
# Downsample the data if needed
|
|
162
179
|
# Compute start and end indices in the original data
|
|
163
180
|
start_index = int(round((start_time - tstart) / sim_dt))
|
|
164
|
-
end_index = int(round((end_time - tstart) / sim_dt))
|
|
181
|
+
end_index = int(round((end_time - tstart) / sim_dt))
|
|
165
182
|
|
|
166
183
|
# Now slice and downsample
|
|
167
184
|
data_matrix_downsampled = [
|
|
@@ -25,37 +25,62 @@ class SpikeReportWriter(BaseReportWriter):
|
|
|
25
25
|
"""Writes SONATA spike report from pop→gid→times mapping."""
|
|
26
26
|
|
|
27
27
|
def write(self, spikes_by_pop: Dict[str, Dict[int, list]]):
|
|
28
|
+
"""Write SONATA spike report with per-population groups.
|
|
29
|
+
|
|
30
|
+
Creates empty datasets for populations without spikes.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Remove any existing file
|
|
28
34
|
if self.output_path.exists():
|
|
29
35
|
self.output_path.unlink()
|
|
30
36
|
|
|
37
|
+
# Make sure parent directory exists
|
|
31
38
|
self.output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
# Always create the file and /spikes group
|
|
41
|
+
with h5py.File(self.output_path, "w") as f:
|
|
42
|
+
spikes_group = f.create_group("spikes")
|
|
43
|
+
|
|
44
|
+
for pop, gid_map in spikes_by_pop.items():
|
|
45
|
+
all_node_ids: List[int] = []
|
|
46
|
+
all_timestamps: List[float] = []
|
|
47
|
+
|
|
48
|
+
for node_id, times in gid_map.items():
|
|
49
|
+
all_node_ids.extend([node_id] * len(times))
|
|
50
|
+
all_timestamps.extend(times)
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
if not all_timestamps:
|
|
53
|
+
logger.warning(f"No spikes to write for population '{pop}'.")
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
# Sort by time for consistency (will be empty arrays if no spikes)
|
|
56
|
+
sorted_indices = np.argsort(all_timestamps)
|
|
57
|
+
node_ids_sorted = np.array(all_node_ids, dtype=np.uint64)[
|
|
58
|
+
sorted_indices
|
|
59
|
+
]
|
|
60
|
+
timestamps_sorted = np.array(all_timestamps, dtype=np.float64)[
|
|
61
|
+
sorted_indices
|
|
62
|
+
]
|
|
47
63
|
|
|
48
|
-
with h5py.File(self.output_path, 'a') as f:
|
|
49
|
-
spikes_group = f.require_group("spikes")
|
|
50
64
|
if pop in spikes_group:
|
|
51
|
-
logger.warning(
|
|
65
|
+
logger.warning(
|
|
66
|
+
f"Overwriting existing group for population '{pop}' in {self.output_path}."
|
|
67
|
+
)
|
|
52
68
|
del spikes_group[pop]
|
|
53
69
|
|
|
54
70
|
group = spikes_group.create_group(pop)
|
|
55
|
-
sorting_enum = h5py.enum_dtype({'none': 0, 'by_id': 1, 'by_time': 2}, basetype='u1')
|
|
56
|
-
group.attrs.create("sorting", 2, dtype=sorting_enum) # 2 = by_time
|
|
57
|
-
|
|
58
|
-
timestamps_ds = group.create_dataset("timestamps", data=timestamps_sorted)
|
|
59
|
-
group.create_dataset("node_ids", data=node_ids_sorted)
|
|
60
71
|
|
|
72
|
+
# SONATA requires the 'sorting' attribute
|
|
73
|
+
sorting_enum = h5py.enum_dtype(
|
|
74
|
+
{"none": 0, "by_id": 1, "by_time": 2}, basetype="u1"
|
|
75
|
+
)
|
|
76
|
+
if timestamps_sorted.size > 0:
|
|
77
|
+
group.attrs.create("sorting", 2, dtype=sorting_enum) # 2 = by_time
|
|
78
|
+
else:
|
|
79
|
+
group.attrs.create("sorting", 0, dtype=sorting_enum) # 0 = none
|
|
80
|
+
|
|
81
|
+
# Always create datasets (even empty)
|
|
82
|
+
timestamps_ds = group.create_dataset(
|
|
83
|
+
"timestamps", data=timestamps_sorted
|
|
84
|
+
)
|
|
61
85
|
timestamps_ds.attrs["units"] = "ms" # SONATA-required
|
|
86
|
+
group.create_dataset("node_ids", data=node_ids_sorted)
|
|
@@ -19,7 +19,6 @@ Run-time validates the data via Pydantic.
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
from enum import Enum
|
|
22
|
-
from pathlib import Path
|
|
23
22
|
from typing import Optional
|
|
24
23
|
import warnings
|
|
25
24
|
|
|
@@ -212,7 +211,7 @@ class Stimulus:
|
|
|
212
211
|
raise ValueError(f"Unknown pattern {pattern}")
|
|
213
212
|
|
|
214
213
|
@classmethod
|
|
215
|
-
def from_sonata(cls, stimulus_entry: dict) -> Optional[Stimulus]:
|
|
214
|
+
def from_sonata(cls, stimulus_entry: dict, config_dir: Optional[str] = None) -> Optional[Stimulus]:
|
|
216
215
|
pattern = Pattern.from_sonata(stimulus_entry["module"])
|
|
217
216
|
if pattern == Pattern.NOISE:
|
|
218
217
|
return Noise(
|
|
@@ -259,6 +258,7 @@ class Stimulus:
|
|
|
259
258
|
delay=stimulus_entry["delay"],
|
|
260
259
|
duration=stimulus_entry["duration"],
|
|
261
260
|
spike_file=stimulus_entry["spike_file"],
|
|
261
|
+
config_dir=config_dir,
|
|
262
262
|
)
|
|
263
263
|
elif pattern == Pattern.SHOT_NOISE:
|
|
264
264
|
return ShotNoise(
|
|
@@ -361,13 +361,7 @@ class RelativeLinear(Stimulus):
|
|
|
361
361
|
@dataclass(frozen=True, config=dict(extra="forbid"))
|
|
362
362
|
class SynapseReplay(Stimulus):
|
|
363
363
|
spike_file: str
|
|
364
|
-
|
|
365
|
-
@field_validator("spike_file")
|
|
366
|
-
@classmethod
|
|
367
|
-
def spike_file_exists(cls, v):
|
|
368
|
-
if not Path(v).exists():
|
|
369
|
-
raise ValueError(f"spike_file {v} does not exist")
|
|
370
|
-
return v
|
|
364
|
+
config_dir: Optional[str] = None
|
|
371
365
|
|
|
372
366
|
|
|
373
367
|
@dataclass(frozen=True, config=dict(extra="forbid"))
|
bluecellulab/tools.py
CHANGED
|
@@ -29,7 +29,6 @@ from bluecellulab.exceptions import UnsteadyCellError
|
|
|
29
29
|
from bluecellulab.simulation.neuron_globals import set_neuron_globals
|
|
30
30
|
from bluecellulab.simulation.parallel import IsolatedProcess
|
|
31
31
|
from bluecellulab.utils import CaptureOutput
|
|
32
|
-
from bluecellulab.type_aliases import NeuronSection
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
logger = logging.getLogger(__name__)
|
|
@@ -119,7 +118,7 @@ def calculate_SS_voltage_subprocess(
|
|
|
119
118
|
emodel_properties=emodel_properties,
|
|
120
119
|
)
|
|
121
120
|
|
|
122
|
-
sec = get_section(
|
|
121
|
+
sec = cell.get_section(section)
|
|
123
122
|
neuron_section = sec
|
|
124
123
|
cell.add_voltage_recording(cell.sections[section], segx)
|
|
125
124
|
cell.add_ramp(500, 5000, step_level, step_level, section=neuron_section, segx=segx)
|
|
@@ -313,7 +312,7 @@ def detect_spike_step_subprocess(
|
|
|
313
312
|
template_format=template_format,
|
|
314
313
|
emodel_properties=emodel_properties)
|
|
315
314
|
|
|
316
|
-
sec = get_section(
|
|
315
|
+
sec = cell.get_section(section)
|
|
317
316
|
cell.add_voltage_recording(cell.sections[section], segx)
|
|
318
317
|
|
|
319
318
|
neuron_section = sec
|
|
@@ -517,88 +516,19 @@ def validate_section_and_segment(cell: Cell, section_name: str, segment_position
|
|
|
517
516
|
raise ValueError(f"Segment position must be between 0.0 and 1.0, got {segment_position}.")
|
|
518
517
|
|
|
519
518
|
|
|
520
|
-
def
|
|
521
|
-
|
|
522
|
-
'dend[3]').
|
|
523
|
-
|
|
524
|
-
Raises:
|
|
525
|
-
ValueError or TypeError if the section is not found or invalid.
|
|
526
|
-
"""
|
|
527
|
-
if section_name in cell.sections:
|
|
528
|
-
section = cell.sections[section_name]
|
|
529
|
-
if hasattr(section, "nseg"):
|
|
530
|
-
return section
|
|
531
|
-
raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
|
|
532
|
-
|
|
533
|
-
available = ", ".join(cell.sections.keys())
|
|
534
|
-
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def get_sections(cell, section_name: str):
|
|
538
|
-
"""Return a list of NEURON sections.
|
|
539
|
-
|
|
540
|
-
If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
|
|
541
|
-
If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
|
|
542
|
-
|
|
543
|
-
Raises:
|
|
544
|
-
ValueError or TypeError if no valid sections are found.
|
|
545
|
-
"""
|
|
546
|
-
# Try to interpret as fully qualified section name
|
|
547
|
-
try:
|
|
548
|
-
return [get_section(cell, section_name)]
|
|
549
|
-
except ValueError:
|
|
550
|
-
pass # Not a precise match; try prefix match
|
|
551
|
-
|
|
552
|
-
# Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
|
|
553
|
-
matched = [
|
|
554
|
-
section for name, section in cell.sections.items()
|
|
555
|
-
if name.startswith(f"{section_name}[")
|
|
556
|
-
]
|
|
557
|
-
if matched:
|
|
558
|
-
return matched
|
|
559
|
-
|
|
560
|
-
available = ", ".join(cell.sections.keys())
|
|
561
|
-
raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
def resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type):
|
|
565
|
-
"""Determine which segments to record from one or more NEURON sections."""
|
|
566
|
-
section_name = report_cfg.get("sections", "soma")
|
|
567
|
-
compartment = report_cfg.get("compartments", "center")
|
|
568
|
-
|
|
569
|
-
if source_type == "compartment_set":
|
|
570
|
-
return [
|
|
571
|
-
(get_section(cell, sec), sec, seg)
|
|
572
|
-
for _, sec, seg in compartment_nodes if _ == node_id
|
|
573
|
-
]
|
|
574
|
-
|
|
575
|
-
sections = get_sections(cell, section_name)
|
|
576
|
-
targets = []
|
|
577
|
-
|
|
578
|
-
for sec in sections:
|
|
579
|
-
sec_name = sec.name().split(".")[-1]
|
|
580
|
-
if compartment == "center":
|
|
581
|
-
targets.append((sec, sec_name, 0.5))
|
|
582
|
-
elif compartment == "all":
|
|
583
|
-
for seg in sec:
|
|
584
|
-
targets.append((sec, sec_name, seg.x))
|
|
585
|
-
else:
|
|
586
|
-
raise ValueError(
|
|
587
|
-
f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
|
|
588
|
-
)
|
|
589
|
-
|
|
590
|
-
return targets
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
def resolve_source_nodes(source, source_type, cells, population):
|
|
594
|
-
if source_type == "compartment_set":
|
|
519
|
+
def resolve_source_nodes(source, report_type, cells, population):
|
|
520
|
+
if report_type == "compartment_set":
|
|
595
521
|
compartment_nodes = source.get("compartment_set", [])
|
|
596
522
|
node_ids = [entry[0] for entry in compartment_nodes]
|
|
597
|
-
|
|
523
|
+
elif report_type == "compartment":
|
|
598
524
|
node_ids = source.get("node_id")
|
|
599
525
|
if node_ids is None:
|
|
600
526
|
node_ids = [node_id for (pop, node_id) in cells.keys() if pop == population]
|
|
601
527
|
compartment_nodes = None
|
|
528
|
+
else:
|
|
529
|
+
raise NotImplementedError(
|
|
530
|
+
f"Unsupported source type '{report_type}' in configuration for report."
|
|
531
|
+
)
|
|
602
532
|
return node_ids, compartment_nodes
|
|
603
533
|
|
|
604
534
|
|
|
@@ -10,7 +10,7 @@ bluecellulab/plotwindow.py,sha256=ePU-EegZ1Sqk0SoYYiQ6k24ZO4s3Hgfpx10uePiJ5xM,27
|
|
|
10
10
|
bluecellulab/psection.py,sha256=FSOwRNuOTyB469BM-jPEf9l1J59FamXmzrQgBI6cnP4,6174
|
|
11
11
|
bluecellulab/psegment.py,sha256=PTgoGLqM4oFIdF_8QHFQCU59j-8TQmtq6PakiGUQhIo,3138
|
|
12
12
|
bluecellulab/rngsettings.py,sha256=2Ykb4Ylk3XTs58x1UIxjg8XJqjSpnCgKRZ8avXCDpxk,4237
|
|
13
|
-
bluecellulab/tools.py,sha256=
|
|
13
|
+
bluecellulab/tools.py,sha256=OamYjgBBBiUiXlf4zCDOjI-SuA6o9Ck8UsKsAi_8gEg,19483
|
|
14
14
|
bluecellulab/type_aliases.py,sha256=DvgjERv2Ztdw_sW63JrZTQGpJ0x5uMTFB5hcBHDb0WA,441
|
|
15
15
|
bluecellulab/utils.py,sha256=0NhwlzyLnSi8kziSfDsQf7pokO4qDkMJVAO33kSX4O0,2227
|
|
16
16
|
bluecellulab/verbosity.py,sha256=T0IgX7DrRo19faxrT4Xzb27gqxzoILQ8FzYKxvUeaPM,1342
|
|
@@ -21,7 +21,7 @@ bluecellulab/analysis/plotting.py,sha256=PqRoaZz33ULMw8A9YnZXXrxcUd84M_dwlYMTFhG
|
|
|
21
21
|
bluecellulab/analysis/utils.py,sha256=eMirP557D11BuedgSqjripDxOq1haIldNbnYNetV1bg,121
|
|
22
22
|
bluecellulab/cell/__init__.py,sha256=Sbc0QOsJ8E7tSwf3q7fsXuE_SevBN6ZmoCVyyU5zfII,208
|
|
23
23
|
bluecellulab/cell/cell_dict.py,sha256=VE7pi-NsMVRSmo-PSdbiLYmolDOu0Gc6JxFBkuQpFdk,1346
|
|
24
|
-
bluecellulab/cell/core.py,sha256=
|
|
24
|
+
bluecellulab/cell/core.py,sha256=ndfDwcYfINe-T9hniH-XL9TKPCa6c2sNa-HuCfDVKZE,44213
|
|
25
25
|
bluecellulab/cell/injector.py,sha256=GB0pmyZMOHNV-lzd_t4cLoDE87S58IMbe7-qU1zBWvE,19033
|
|
26
26
|
bluecellulab/cell/plotting.py,sha256=t2qDUabFtsBb-nJMkDh5UfsgW-wvQ2wfDwAVZ8-hWPo,4032
|
|
27
27
|
bluecellulab/cell/random.py,sha256=pemekc11geRBKD8Ghb82tvKOZLfFWkUz1IKLc_NWg-A,1773
|
|
@@ -50,35 +50,35 @@ bluecellulab/circuit/config/__init__.py,sha256=aaoJXRKBJzpxxREo9NxKc-_CCPmVeuR1m
|
|
|
50
50
|
bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=V3eqOzskX7VrMDpl-nMQVEhDg8QWgRmRduyJBii5sgI,6974
|
|
51
51
|
bluecellulab/circuit/config/definition.py,sha256=cotKRDHOjzZKNgNSrZ29voU-66W8jaGt5yuge1hyv18,2953
|
|
52
52
|
bluecellulab/circuit/config/sections.py,sha256=QRnU44-OFvHxcF1LX4bAEP9dk3I6UKsuPNBbWkdfmRE,7151
|
|
53
|
-
bluecellulab/circuit/config/sonata_simulation_config.py,sha256=
|
|
53
|
+
bluecellulab/circuit/config/sonata_simulation_config.py,sha256=M3dD9B69VK01lRaR84PiAj8th-x8hrAE80IpGDLqvi8,8056
|
|
54
54
|
bluecellulab/hoc/Cell.hoc,sha256=z77qRQG_-afj-RLX0xN6V-K6Duq3bR7vmlDrGWPdh4E,16435
|
|
55
55
|
bluecellulab/hoc/RNGSettings.hoc,sha256=okJBdqlPXET8BrpG1Q31GdaxHfpe3CE0L5P7MAhfQTE,1227
|
|
56
56
|
bluecellulab/hoc/TDistFunc.hoc,sha256=WKX-anvL83xGuGPH9g1oIORB17UM4Pi3-iIXzKO-pUQ,2663
|
|
57
57
|
bluecellulab/hoc/TStim.hoc,sha256=noBJbM_ZqF6T6MEgBeowNzz21I9QeYZ5brGgUvCSm4k,8473
|
|
58
58
|
bluecellulab/hoc/fileUtils.hoc,sha256=LSM7BgyjYVqo2DGSOKvg4W8IIusbsL45JVYK0vgwitU,2539
|
|
59
59
|
bluecellulab/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
bluecellulab/reports/manager.py,sha256=
|
|
61
|
-
bluecellulab/reports/utils.py,sha256=
|
|
60
|
+
bluecellulab/reports/manager.py,sha256=bKtqxOnZcta4HwrlWN51ysfMmvcbISksUpOKF0pJFV0,4021
|
|
61
|
+
bluecellulab/reports/utils.py,sha256=wnYFo1xKbBRu1KZTIea83Wvakoo_SQ94QiqOD26cEe0,8271
|
|
62
62
|
bluecellulab/reports/writers/__init__.py,sha256=Q8Y2GC83jseH5QY9IlEQsvaUQIH-BzgqhVjGRWD5j90,826
|
|
63
63
|
bluecellulab/reports/writers/base_writer.py,sha256=P5ramFD2oFIroEBCH1pAscbkfcBIVYFIBg4pVpSP2IU,1042
|
|
64
|
-
bluecellulab/reports/writers/compartment.py,sha256=
|
|
65
|
-
bluecellulab/reports/writers/spikes.py,sha256=
|
|
64
|
+
bluecellulab/reports/writers/compartment.py,sha256=vtBRBNHm6NRnX09RZCszC9MKpiZqqVovlhwsfitzaRU,7960
|
|
65
|
+
bluecellulab/reports/writers/spikes.py,sha256=FJm74SKeMb0h_oNucwWoNrh8xLObzinQTOHH0v1xKeU,3375
|
|
66
66
|
bluecellulab/simulation/__init__.py,sha256=P2ebt0SFw-08J3ihN-LeRn95HeF79tzA-Q0ReLm32dM,214
|
|
67
67
|
bluecellulab/simulation/neuron_globals.py,sha256=wvh6HfVYXoUsAcy17wqI_4SOqBCovRGEEaW9ANDgiIQ,4943
|
|
68
68
|
bluecellulab/simulation/parallel.py,sha256=oQ_oV2EKr8gP4yF2Dq8q9MiblDyi89_wBgLzQkLV_U0,1514
|
|
69
69
|
bluecellulab/simulation/report.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
bluecellulab/simulation/simulation.py,sha256=OfrzeHvLmOV2ptYcGie_fVGrtkDfizLpE6ZyQWVxnIE,6492
|
|
71
71
|
bluecellulab/stimulus/__init__.py,sha256=DgIgVaSyR-URf3JZzvO6j-tjCerzvktuK-ep8pjMRPQ,37
|
|
72
|
-
bluecellulab/stimulus/circuit_stimulus_definitions.py,sha256=
|
|
72
|
+
bluecellulab/stimulus/circuit_stimulus_definitions.py,sha256=Xdd4j3oiGHNsuGEjI-KLy0EQd-Qd7C9JvOSLJwIr7pE,17113
|
|
73
73
|
bluecellulab/stimulus/factory.py,sha256=4fvVFFjOGHSqBidLe_W1zQozfMEeePXWO6yYCs30-SM,30780
|
|
74
74
|
bluecellulab/stimulus/stimulus.py,sha256=a_hKJUtZmIgjiFjbJf6RzUPokELqn0IHCgIWGw5XLm8,30322
|
|
75
75
|
bluecellulab/synapse/__init__.py,sha256=RW8XoAMXOvK7OG1nHl_q8jSEKLj9ZN4oWf2nY9HAwuk,192
|
|
76
76
|
bluecellulab/synapse/synapse_factory.py,sha256=NHwRMYMrnRVm_sHmyKTJ1bdoNmWZNU4UPOGu7FCi-PE,6987
|
|
77
77
|
bluecellulab/synapse/synapse_types.py,sha256=zs_yBvGTH4QrbQF3nEViidyq1WM_ZcTSFdjUxB3khW0,16871
|
|
78
78
|
bluecellulab/validation/validation.py,sha256=D35LE_131CtIzeeePAPz1OFzncGjAcvF02lSIvGvL2E,21331
|
|
79
|
-
bluecellulab-2.6.
|
|
80
|
-
bluecellulab-2.6.
|
|
81
|
-
bluecellulab-2.6.
|
|
82
|
-
bluecellulab-2.6.
|
|
83
|
-
bluecellulab-2.6.
|
|
84
|
-
bluecellulab-2.6.
|
|
79
|
+
bluecellulab-2.6.72.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
|
|
80
|
+
bluecellulab-2.6.72.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
|
|
81
|
+
bluecellulab-2.6.72.dist-info/METADATA,sha256=wlI-kK0juc84JZFmdgzQLXarBPJ8gWpc2riGNVHZsg8,8359
|
|
82
|
+
bluecellulab-2.6.72.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
83
|
+
bluecellulab-2.6.72.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
|
|
84
|
+
bluecellulab-2.6.72.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|