bluecellulab 2.6.71__py3-none-any.whl → 2.6.73__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 +147 -3
- bluecellulab/circuit/circuit_access/bluepy_circuit_access.py +2 -1
- bluecellulab/circuit/circuit_access/sonata_circuit_access.py +2 -2
- bluecellulab/circuit/config/bluepy_simulation_config.py +10 -0
- bluecellulab/circuit/config/sonata_simulation_config.py +2 -0
- bluecellulab/circuit/node_id.py +1 -1
- bluecellulab/circuit_simulation.py +2 -2
- bluecellulab/plotwindow.py +1 -1
- 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/tools.py +9 -79
- bluecellulab/utils.py +1 -1
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.dist-info}/METADATA +3 -3
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.dist-info}/RECORD +20 -20
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.dist-info}/WHEEL +0 -0
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.dist-info}/licenses/AUTHORS.txt +0 -0
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.dist-info}/licenses/LICENSE +0 -0
- {bluecellulab-2.6.71.dist-info → bluecellulab-2.6.73.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
|
)
|
|
@@ -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:
|
|
@@ -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
|
|
@@ -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
|
bluecellulab/circuit/node_id.py
CHANGED
|
@@ -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
|
bluecellulab/plotwindow.py
CHANGED
|
@@ -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:]
|
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)
|
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
|
|
bluecellulab/utils.py
CHANGED
|
@@ -53,7 +53,7 @@ class NumpyEncoder(json.JSONEncoder):
|
|
|
53
53
|
),
|
|
54
54
|
):
|
|
55
55
|
return int(obj)
|
|
56
|
-
elif isinstance(obj, (np.
|
|
56
|
+
elif isinstance(obj, (np.float64, np.float16, np.float32, np.float64)):
|
|
57
57
|
return float(obj)
|
|
58
58
|
elif isinstance(obj, np.ndarray):
|
|
59
59
|
return obj.tolist()
|
|
@@ -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
|
============
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
bluecellulab/__init__.py,sha256=1d_CKIJLIpon7o13h3lBnV_-33obZEPwa9KDTjlFPD8,880
|
|
2
|
-
bluecellulab/circuit_simulation.py,sha256=
|
|
2
|
+
bluecellulab/circuit_simulation.py,sha256=GGAkJq0Jk_ClB1XTyrf8g6Sirntj0E9IBeI76acJ2yU,36769
|
|
3
3
|
bluecellulab/connection.py,sha256=-xT0mU7ppeHI_qjCKj17TtxXVVcUDgBsaMKt9ODmcEU,4640
|
|
4
4
|
bluecellulab/dendrogram.py,sha256=FjS6RZ6xcp5zJoY5d5qv_edqPM13tL2-UANgbZuDBjY,6427
|
|
5
5
|
bluecellulab/exceptions.py,sha256=1lKD92VIyD8cUggAI1SLxeKzj_09Ik_TlHCzPLCvDHg,2379
|
|
6
6
|
bluecellulab/graph.py,sha256=o-9NnRrli0AqfGcw0-nhjaICH2IeqCH76uAlbZbo2dQ,3419
|
|
7
7
|
bluecellulab/importer.py,sha256=ePxoquSRGGK123mR_VWDLKf9CE-N28NXAAY1DCm3T7Y,2900
|
|
8
8
|
bluecellulab/neuron_interpreter.py,sha256=Nerehh6SNIRcTH8NHvyZvL_axadeix67uer-lRGuw9U,2185
|
|
9
|
-
bluecellulab/plotwindow.py,sha256=
|
|
9
|
+
bluecellulab/plotwindow.py,sha256=496tNvrd7xQ9SbSLRXF1VPIvWWqA2jl03-ZPMJ-wx_0,2756
|
|
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
|
-
bluecellulab/utils.py,sha256=
|
|
15
|
+
bluecellulab/utils.py,sha256=tATZ814TtQO3fTsm5Q0KiANCDLFDZYtkfFQlI-Kxx_U,2228
|
|
16
16
|
bluecellulab/verbosity.py,sha256=T0IgX7DrRo19faxrT4Xzb27gqxzoILQ8FzYKxvUeaPM,1342
|
|
17
17
|
bluecellulab/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
bluecellulab/analysis/analysis.py,sha256=qZN2R7IKMOda8sVEDU61TkTDNUbaZjIWbwP3pM6Fcm8,24184
|
|
@@ -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
|
|
@@ -38,31 +38,31 @@ bluecellulab/cell/ballstick/morphology.asc,sha256=EO0VIRilJAwpiDP2hIevwusfvYptNY
|
|
|
38
38
|
bluecellulab/circuit/__init__.py,sha256=Khpa13nzNvDlDS2JduyoFTukEduEkWCc5ML_JwGpmZs,361
|
|
39
39
|
bluecellulab/circuit/format.py,sha256=90gWOXg6HK0R9a4WFSnnRH8XezxmzOGk5dRpJHbvbbU,1674
|
|
40
40
|
bluecellulab/circuit/iotools.py,sha256=Q65xYDaiensMtrulC3OLsS2_hcWr_Kje0nXFrAizMMo,1589
|
|
41
|
-
bluecellulab/circuit/node_id.py,sha256=
|
|
41
|
+
bluecellulab/circuit/node_id.py,sha256=g_f_PvjaX59yXBTWRIpXibbgFny6xBWVErjVErahTTc,1259
|
|
42
42
|
bluecellulab/circuit/simulation_access.py,sha256=8LX5nbKw_Hu7AR3pGLdTFGwehvhaj-r8Mh1q3dVoiVg,7745
|
|
43
43
|
bluecellulab/circuit/synapse_properties.py,sha256=TvUMiXZAAeYo1zKkus3z1EUvrE9QCIQ3Ze-jSnPSJWY,6374
|
|
44
44
|
bluecellulab/circuit/validate.py,sha256=wntnr7oIDyasqD1nM-kqz1NpfWDxBGhx0Ep3e5hHXIw,3593
|
|
45
45
|
bluecellulab/circuit/circuit_access/__init__.py,sha256=sgp6m5kP-pq60V1IFGUiSUR1OW01zdxXNNUJmPA8anI,201
|
|
46
|
-
bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=
|
|
46
|
+
bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=Ivj7RqIK0OYUuraTpQU1h_oeE2somBn_Tnji6XcjFt8,14383
|
|
47
47
|
bluecellulab/circuit/circuit_access/definition.py,sha256=_sUU0DkesGOFW82kS1G9vki0o0aQP76z3Ltk7Vo9KK4,4435
|
|
48
|
-
bluecellulab/circuit/circuit_access/sonata_circuit_access.py,sha256=
|
|
48
|
+
bluecellulab/circuit/circuit_access/sonata_circuit_access.py,sha256=OfqVS3z_t7dsnRuihF15fW-KuOCd7BXqRoFblpo2VL4,10441
|
|
49
49
|
bluecellulab/circuit/config/__init__.py,sha256=aaoJXRKBJzpxxREo9NxKc-_CCPmVeuR1mcViRXcLrC4,215
|
|
50
|
-
bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=
|
|
50
|
+
bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=3l0TqoQhsltvQnF-rKK_zFEu0-L6dAhW-Ch6z9C4wZw,7216
|
|
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
|
|
@@ -76,9 +76,9 @@ bluecellulab/synapse/__init__.py,sha256=RW8XoAMXOvK7OG1nHl_q8jSEKLj9ZN4oWf2nY9HA
|
|
|
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.73.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
|
|
80
|
+
bluecellulab-2.6.73.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
|
|
81
|
+
bluecellulab-2.6.73.dist-info/METADATA,sha256=5caaaN9HbahBUG9GX5Q4jhWNe4NiREQHB2WqfPdj8Sc,8354
|
|
82
|
+
bluecellulab-2.6.73.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
83
|
+
bluecellulab-2.6.73.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
|
|
84
|
+
bluecellulab-2.6.73.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|