bmtool 0.7.4.1__py3-none-any.whl → 0.7.5.1__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.
- bmtool/singlecell.py +53 -8
- bmtool/synapses.py +777 -54
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/METADATA +1 -1
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/RECORD +8 -8
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/WHEEL +0 -0
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/entry_points.txt +0 -0
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/licenses/LICENSE +0 -0
- {bmtool-0.7.4.1.dist-info → bmtool-0.7.5.1.dist-info}/top_level.txt +0 -0
bmtool/singlecell.py
CHANGED
@@ -10,6 +10,8 @@ import pandas as pd
|
|
10
10
|
from neuron import h
|
11
11
|
from scipy.optimize import curve_fit
|
12
12
|
|
13
|
+
from bmtool.util.util import load_templates_from_config, load_config
|
14
|
+
|
13
15
|
|
14
16
|
def load_biophys1():
|
15
17
|
"""
|
@@ -1025,19 +1027,62 @@ class ZAP(CurrentClamp):
|
|
1025
1027
|
|
1026
1028
|
|
1027
1029
|
class Profiler:
|
1028
|
-
"""All in one single cell profiler
|
1030
|
+
"""All in one single cell profiler
|
1031
|
+
|
1032
|
+
This Profiler now supports being initialized with either explicit
|
1033
|
+
`template_dir` and `mechanism_dir` paths or with a BMTK `config` file
|
1034
|
+
(which should contain `components.templates_dir` and
|
1035
|
+
`components.mechanisms_dir`). When `config` is provided it will be used
|
1036
|
+
to load mechanisms and templates via the utility helpers.
|
1037
|
+
"""
|
1029
1038
|
|
1030
|
-
def __init__(self, template_dir: str = None, mechanism_dir: str = None, dt=None):
|
1039
|
+
def __init__(self, template_dir: str = None, mechanism_dir: str = None, dt=None, config: str = None):
|
1040
|
+
# initialize to None and then prefer config-derived paths if provided
|
1031
1041
|
self.template_dir = None
|
1032
1042
|
self.mechanism_dir = None
|
1033
1043
|
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1044
|
+
# If a BMTK config is provided, load mechanisms/templates from it
|
1045
|
+
if config is not None:
|
1046
|
+
try:
|
1047
|
+
# load and apply the config values for directories
|
1048
|
+
conf = load_config(config)
|
1049
|
+
# conf behaves like a dict returned by bmtk Config.from_json
|
1050
|
+
try:
|
1051
|
+
comps = conf["components"]
|
1052
|
+
except Exception:
|
1053
|
+
comps = getattr(conf, "components", None)
|
1054
|
+
|
1055
|
+
if comps is not None:
|
1056
|
+
# support dict-like and object-like components
|
1057
|
+
try:
|
1058
|
+
self.template_dir = comps.get("templates_dir")
|
1059
|
+
except Exception:
|
1060
|
+
self.template_dir = getattr(comps, "templates_dir", None)
|
1061
|
+
try:
|
1062
|
+
self.mechanism_dir = comps.get("mechanisms_dir")
|
1063
|
+
except Exception:
|
1064
|
+
self.mechanism_dir = getattr(comps, "mechanisms_dir", None)
|
1065
|
+
|
1066
|
+
# actually load mechanisms and templates using the helper
|
1067
|
+
load_templates_from_config(config)
|
1068
|
+
except Exception:
|
1069
|
+
# fall back to explicit dirs if config parsing/loading fails
|
1070
|
+
pass
|
1071
|
+
|
1072
|
+
else:
|
1073
|
+
# fall back to explicit args if not set by config
|
1074
|
+
if not self.template_dir:
|
1075
|
+
self.template_dir = template_dir
|
1076
|
+
if not self.mechanism_dir:
|
1077
|
+
self.mechanism_dir = mechanism_dir
|
1078
|
+
|
1079
|
+
# template_dir is required for loading templates later
|
1080
|
+
if self.template_dir is None:
|
1081
|
+
raise ValueError("Profiler requires either 'template_dir' or a 'config' containing components.templates_dir")
|
1082
|
+
|
1083
|
+
self.templates = None
|
1039
1084
|
|
1040
|
-
|
1085
|
+
self.load_templates()
|
1041
1086
|
|
1042
1087
|
h.load_file("stdrun.hoc")
|
1043
1088
|
if dt is not None:
|
bmtool/synapses.py
CHANGED
@@ -8,6 +8,7 @@ import ipywidgets as widgets
|
|
8
8
|
import matplotlib.pyplot as plt
|
9
9
|
import neuron
|
10
10
|
import numpy as np
|
11
|
+
import pandas as pd
|
11
12
|
from IPython.display import clear_output, display
|
12
13
|
from ipywidgets import HBox, VBox
|
13
14
|
from neuron import h
|
@@ -18,10 +19,10 @@ from scipy.optimize import curve_fit, minimize, minimize_scalar
|
|
18
19
|
from scipy.signal import find_peaks
|
19
20
|
from tqdm.notebook import tqdm
|
20
21
|
|
21
|
-
from bmtool.util.util import load_templates_from_config
|
22
|
+
from bmtool.util.util import load_templates_from_config, load_nodes_from_config, load_edges_from_config, load_config
|
22
23
|
|
23
24
|
DEFAULT_GENERAL_SETTINGS = {
|
24
|
-
"vclamp":
|
25
|
+
"vclamp": False,
|
25
26
|
"rise_interval": (0.1, 0.9),
|
26
27
|
"tstart": 500.0,
|
27
28
|
"tdur": 100.0,
|
@@ -43,17 +44,18 @@ DEFAULT_GAP_JUNCTION_GENERAL_SETTINGS = {
|
|
43
44
|
class SynapseTuner:
|
44
45
|
def __init__(
|
45
46
|
self,
|
47
|
+
conn_type_settings: Optional[Dict[str, dict]] = None,
|
48
|
+
connection: Optional[str] = None,
|
49
|
+
current_name: str = "i",
|
46
50
|
mechanisms_dir: Optional[str] = None,
|
47
51
|
templates_dir: Optional[str] = None,
|
48
52
|
config: Optional[str] = None,
|
49
|
-
conn_type_settings: Optional[dict] = None,
|
50
|
-
connection: Optional[str] = None,
|
51
53
|
general_settings: Optional[dict] = None,
|
52
54
|
json_folder_path: Optional[str] = None,
|
53
|
-
|
54
|
-
|
55
|
-
slider_vars: Optional[list] = None,
|
55
|
+
other_vars_to_record: Optional[List[str]] = None,
|
56
|
+
slider_vars: Optional[List[str]] = None,
|
56
57
|
hoc_cell: Optional[object] = None,
|
58
|
+
network: Optional[str] = None,
|
57
59
|
) -> None:
|
58
60
|
"""
|
59
61
|
Initialize the SynapseTuner class with connection type settings, mechanisms, and template directories.
|
@@ -80,8 +82,28 @@ class SynapseTuner:
|
|
80
82
|
List of synaptic variables you would like sliders set up for the STP sliders method by default will use all parameters in spec_syn_param.
|
81
83
|
hoc_cell : Optional[object]
|
82
84
|
An already loaded NEURON cell object. If provided, template loading and cell setup will be skipped.
|
85
|
+
network : Optional[str]
|
86
|
+
Name of the specific network dataset to access from the loaded edges data (e.g., 'network_to_network').
|
87
|
+
If not provided, will use all available networks. When a config file is provided, this enables
|
88
|
+
the network dropdown feature in InteractiveTuner for switching between different networks.
|
89
|
+
|
90
|
+
Network Dropdown Feature:
|
91
|
+
-------------------------
|
92
|
+
When initialized with a BMTK config file, the tuner automatically:
|
93
|
+
1. Loads all available network datasets from the config
|
94
|
+
2. Creates a network dropdown in InteractiveTuner (if multiple networks exist)
|
95
|
+
3. Allows dynamic switching between networks, which rebuilds connection types
|
96
|
+
4. Updates connection dropdown options when network is changed
|
97
|
+
5. Preserves current connection if it exists in the new network, otherwise selects the first available
|
83
98
|
"""
|
84
99
|
self.hoc_cell = hoc_cell
|
100
|
+
# Store config and network information for network dropdown functionality
|
101
|
+
self.config = config # Store config path for network dropdown functionality
|
102
|
+
self.available_networks = [] # Store available networks from config file
|
103
|
+
self.current_network = network # Store current network selection
|
104
|
+
# Cache for loaded dynamics params JSON by filename to avoid repeated disk reads
|
105
|
+
self._syn_params_cache = {}
|
106
|
+
h.load_file('stdrun.hoc')
|
85
107
|
|
86
108
|
if hoc_cell is None:
|
87
109
|
if config is None and (mechanisms_dir is None or templates_dir is None):
|
@@ -95,11 +117,37 @@ class SynapseTuner:
|
|
95
117
|
else:
|
96
118
|
# loads both mech and templates
|
97
119
|
load_templates_from_config(config)
|
120
|
+
# Load available networks from config for network dropdown feature
|
121
|
+
self._load_available_networks()
|
122
|
+
# Prebuild connection type settings for each available network to
|
123
|
+
# make network switching in the UI fast. This will make __init__ slower
|
124
|
+
# but dramatically speed up response when changing the network dropdown.
|
125
|
+
self._prebuilt_conn_type_settings = {}
|
126
|
+
try:
|
127
|
+
for net in self.available_networks:
|
128
|
+
self._prebuilt_conn_type_settings[net] = self._build_conn_type_settings_from_config(config, network=net)
|
129
|
+
except Exception as e:
|
130
|
+
print(f"Warning: error prebuilding conn_type_settings for networks: {e}")
|
98
131
|
|
99
132
|
if conn_type_settings is None:
|
100
|
-
|
133
|
+
if config is not None:
|
134
|
+
print("Building conn_type_settings from BMTK config files...")
|
135
|
+
# If we prebuilt per-network settings, use the one for the requested network
|
136
|
+
if hasattr(self, '_prebuilt_conn_type_settings') and network in getattr(self, '_prebuilt_conn_type_settings', {}):
|
137
|
+
conn_type_settings = self._prebuilt_conn_type_settings[network]
|
138
|
+
else:
|
139
|
+
conn_type_settings = self._build_conn_type_settings_from_config(config, network=network)
|
140
|
+
print(f"Found {len(conn_type_settings)} connection types: {list(conn_type_settings.keys())}")
|
141
|
+
|
142
|
+
# If connection is not specified, use the first available connection
|
143
|
+
if connection is None and conn_type_settings:
|
144
|
+
connection = list(conn_type_settings.keys())[0]
|
145
|
+
print(f"No connection specified, using first available: {connection}")
|
146
|
+
else:
|
147
|
+
raise ValueError("conn_type_settings must be provided if config is not specified.")
|
148
|
+
|
101
149
|
if connection is None:
|
102
|
-
raise ValueError("connection must be provided.")
|
150
|
+
raise ValueError("connection must be provided or inferable from conn_type_settings.")
|
103
151
|
if connection not in conn_type_settings:
|
104
152
|
raise ValueError(f"connection '{connection}' not found in conn_type_settings.")
|
105
153
|
|
@@ -113,12 +161,20 @@ class SynapseTuner:
|
|
113
161
|
else:
|
114
162
|
# Merge defaults with user-provided
|
115
163
|
self.general_settings = {**DEFAULT_GENERAL_SETTINGS, **general_settings}
|
164
|
+
|
165
|
+
# Store the initial connection name and set up connection
|
166
|
+
self.current_connection = connection
|
116
167
|
self.conn = self.conn_type_settings[connection]
|
168
|
+
self._current_cell_type = self.conn["spec_settings"]["post_cell"]
|
117
169
|
self.synaptic_props = self.conn["spec_syn_param"]
|
118
170
|
self.vclamp = self.general_settings["vclamp"]
|
119
171
|
self.current_name = current_name
|
120
172
|
self.other_vars_to_record = other_vars_to_record or []
|
121
173
|
self.ispk = None
|
174
|
+
self.input_mode = False # Add input_mode attribute
|
175
|
+
|
176
|
+
# Store original slider_vars for connection switching
|
177
|
+
self.original_slider_vars = slider_vars or list(self.synaptic_props.keys())
|
122
178
|
|
123
179
|
if slider_vars:
|
124
180
|
# Start by filtering based on keys in slider_vars
|
@@ -174,6 +230,497 @@ class SynapseTuner:
|
|
174
230
|
|
175
231
|
self._set_up_recorders()
|
176
232
|
|
233
|
+
def _build_conn_type_settings_from_config(self, config_path: str, node_set: Optional[str] = None, network: Optional[str] = None) -> Dict[str, dict]:
|
234
|
+
"""
|
235
|
+
Build conn_type_settings from BMTK simulation and circuit config files using the method used by relation matrix function in util.
|
236
|
+
|
237
|
+
Parameters:
|
238
|
+
-----------
|
239
|
+
config_path : str
|
240
|
+
Path to the simulation config JSON file.
|
241
|
+
node_set : Optional[str]
|
242
|
+
Specific node set to filter connections for. If None, processes all connections.
|
243
|
+
network : Optional[str]
|
244
|
+
Name of the specific network dataset to access (e.g., 'network_to_network').
|
245
|
+
If None, processes all available networks.
|
246
|
+
|
247
|
+
Returns:
|
248
|
+
--------
|
249
|
+
Dict[str, dict]
|
250
|
+
Dictionary with connection names as keys and connection settings as values.
|
251
|
+
|
252
|
+
NOTE: a lot of this code could probs be made a bit more simple or just removed i kinda tried a bunch of things and it works now
|
253
|
+
but is kinda complex and some code is probs note needed
|
254
|
+
|
255
|
+
"""
|
256
|
+
# Load configuration and get nodes and edges using util.py methods
|
257
|
+
config = load_config(config_path)
|
258
|
+
# Ensure the config dict knows its source path so path substitutions can be resolved
|
259
|
+
try:
|
260
|
+
# load_config may return a dict; store path used so callers can resolve $COMPONENTS_DIR
|
261
|
+
config['config_path'] = config_path
|
262
|
+
except Exception:
|
263
|
+
pass
|
264
|
+
nodes = load_nodes_from_config(config_path)
|
265
|
+
edges = load_edges_from_config(config_path)
|
266
|
+
|
267
|
+
conn_type_settings = {}
|
268
|
+
|
269
|
+
# If a specific network is requested, only process that one
|
270
|
+
if network:
|
271
|
+
if network not in edges:
|
272
|
+
print(f"Warning: Network '{network}' not found in edges. Available networks: {list(edges.keys())}")
|
273
|
+
return conn_type_settings
|
274
|
+
edge_datasets = {network: edges[network]}
|
275
|
+
else:
|
276
|
+
edge_datasets = edges
|
277
|
+
|
278
|
+
# Process each edge dataset using the util.py approach
|
279
|
+
for edge_dataset_name, edge_df in edge_datasets.items():
|
280
|
+
if edge_df.empty:
|
281
|
+
continue
|
282
|
+
|
283
|
+
# Create merged DataFrames with source and target node information like util.py does
|
284
|
+
source_node_df = None
|
285
|
+
target_node_df = None
|
286
|
+
|
287
|
+
# First, try to deterministically parse the edge_dataset_name for patterns like '<src>_to_<tgt>'
|
288
|
+
# e.g., 'network_to_network', 'extnet_to_network'
|
289
|
+
if '_to_' in edge_dataset_name:
|
290
|
+
parts = edge_dataset_name.split('_to_')
|
291
|
+
if len(parts) == 2:
|
292
|
+
src_name, tgt_name = parts
|
293
|
+
if src_name in nodes:
|
294
|
+
source_node_df = nodes[src_name].add_prefix('source_')
|
295
|
+
if tgt_name in nodes:
|
296
|
+
target_node_df = nodes[tgt_name].add_prefix('target_')
|
297
|
+
|
298
|
+
# If not found by parsing name, fall back to inspecting a sample edge row which contains
|
299
|
+
# explicit 'source_population' and 'target_population' fields (this avoids reversing source/target)
|
300
|
+
if source_node_df is None or target_node_df is None:
|
301
|
+
sample_edge = edge_df.iloc[0] if len(edge_df) > 0 else None
|
302
|
+
if sample_edge is not None:
|
303
|
+
# Use explicit population names from the edge entry
|
304
|
+
source_pop_name = sample_edge.get('source_population', '')
|
305
|
+
target_pop_name = sample_edge.get('target_population', '')
|
306
|
+
if source_pop_name in nodes:
|
307
|
+
source_node_df = nodes[source_pop_name].add_prefix('source_')
|
308
|
+
if target_pop_name in nodes:
|
309
|
+
target_node_df = nodes[target_pop_name].add_prefix('target_')
|
310
|
+
|
311
|
+
# As a last resort, attempt to heuristically match by prefix/suffix of the dataset name
|
312
|
+
if source_node_df is None or target_node_df is None:
|
313
|
+
for pop_name, node_df in nodes.items():
|
314
|
+
if source_node_df is None and (edge_dataset_name.startswith(pop_name) or edge_dataset_name.endswith(pop_name)):
|
315
|
+
source_node_df = node_df.add_prefix('source_')
|
316
|
+
elif target_node_df is None and (edge_dataset_name.startswith(pop_name) or edge_dataset_name.endswith(pop_name)):
|
317
|
+
target_node_df = node_df.add_prefix('target_')
|
318
|
+
|
319
|
+
# If we still don't have the node data, skip this edge dataset
|
320
|
+
if source_node_df is None or target_node_df is None:
|
321
|
+
print(f"Warning: Could not find node data for edge dataset {edge_dataset_name}")
|
322
|
+
continue
|
323
|
+
|
324
|
+
# Merge edge data with source node info
|
325
|
+
edges_with_source = pd.merge(
|
326
|
+
edge_df.reset_index(),
|
327
|
+
source_node_df,
|
328
|
+
how='left',
|
329
|
+
left_on='source_node_id',
|
330
|
+
right_index=True
|
331
|
+
)
|
332
|
+
|
333
|
+
# Merge with target node info
|
334
|
+
edges_with_nodes = pd.merge(
|
335
|
+
edges_with_source,
|
336
|
+
target_node_df,
|
337
|
+
how='left',
|
338
|
+
left_on='target_node_id',
|
339
|
+
right_index=True
|
340
|
+
)
|
341
|
+
|
342
|
+
# Get unique edge types from the merged dataset
|
343
|
+
if 'edge_type_id' in edges_with_nodes.columns:
|
344
|
+
edge_types = edges_with_nodes['edge_type_id'].unique()
|
345
|
+
else:
|
346
|
+
edge_types = [0] # Single edge type
|
347
|
+
|
348
|
+
# Process each edge type
|
349
|
+
for edge_type_id in edge_types:
|
350
|
+
# Filter edges for this type
|
351
|
+
if 'edge_type_id' in edges_with_nodes.columns:
|
352
|
+
edge_type_data = edges_with_nodes[edges_with_nodes['edge_type_id'] == edge_type_id]
|
353
|
+
else:
|
354
|
+
edge_type_data = edges_with_nodes
|
355
|
+
|
356
|
+
if len(edge_type_data) == 0:
|
357
|
+
continue
|
358
|
+
|
359
|
+
# Get representative edge for this type
|
360
|
+
edge_info = edge_type_data.iloc[0]
|
361
|
+
|
362
|
+
# Skip gap junctions
|
363
|
+
if 'is_gap_junction' in edge_info and pd.notna(edge_info['is_gap_junction']) and edge_info['is_gap_junction']:
|
364
|
+
continue
|
365
|
+
|
366
|
+
# Get population names from the merged data (this is the key improvement!)
|
367
|
+
source_pop = edge_info.get('source_pop_name', '')
|
368
|
+
target_pop = edge_info.get('target_pop_name', '')
|
369
|
+
|
370
|
+
# Get target cell template from the merged data
|
371
|
+
target_model_template = edge_info.get('target_model_template', '')
|
372
|
+
if target_model_template.startswith('hoc:'):
|
373
|
+
target_cell_type = target_model_template.replace('hoc:', '')
|
374
|
+
else:
|
375
|
+
target_cell_type = target_model_template
|
376
|
+
|
377
|
+
# Create connection name using the actual population names
|
378
|
+
if source_pop and target_pop:
|
379
|
+
conn_name = f"{source_pop}2{target_pop}"
|
380
|
+
else:
|
381
|
+
conn_name = f"{edge_dataset_name}_type_{edge_type_id}"
|
382
|
+
|
383
|
+
# Get synaptic model template
|
384
|
+
model_template = edge_info.get('model_template', 'exp2syn')
|
385
|
+
|
386
|
+
# Build connection settings early so we can attach metadata like dynamics file name
|
387
|
+
conn_settings = {
|
388
|
+
'spec_settings': {
|
389
|
+
'post_cell': target_cell_type,
|
390
|
+
'vclamp_amp': -70.0, # Default voltage clamp amplitude
|
391
|
+
'sec_x': 0.5, # Default location on section
|
392
|
+
'sec_id': 0, # Default to soma
|
393
|
+
# level_of_detail may be overridden by dynamics params below
|
394
|
+
'level_of_detail': model_template,
|
395
|
+
},
|
396
|
+
'spec_syn_param': {}
|
397
|
+
}
|
398
|
+
|
399
|
+
# Load synaptic parameters from dynamics_params file if available.
|
400
|
+
# NOTE: the edge DataFrame produced by load_edges_from_config/load_sonata_edges_to_dataframe
|
401
|
+
# already contains the 'dynamics_params' column (from the CSV) or the
|
402
|
+
# flattened H5 dynamics_params attributes (prefixed with 'dynamics_params/').
|
403
|
+
# Prefer the direct 'dynamics_params' column value from the merged DataFrame
|
404
|
+
# rather than performing ad-hoc string parsing here.
|
405
|
+
syn_params = {}
|
406
|
+
dynamics_file_name = None
|
407
|
+
# Prefer a top-level 'dynamics_params' column if present
|
408
|
+
if 'dynamics_params' in edge_info and pd.notna(edge_info.get('dynamics_params')):
|
409
|
+
val = edge_info.get('dynamics_params')
|
410
|
+
# Some CSV loaders can produce bytes or numpy types; coerce to str
|
411
|
+
try:
|
412
|
+
dynamics_file_name = str(val).strip()
|
413
|
+
except Exception:
|
414
|
+
dynamics_file_name = None
|
415
|
+
|
416
|
+
# If we found a dynamics file name, use it directly (skip token parsing)
|
417
|
+
if dynamics_file_name and dynamics_file_name.upper() != 'NULL':
|
418
|
+
try:
|
419
|
+
conn_settings['spec_settings']['dynamics_params_file'] = dynamics_file_name
|
420
|
+
# use a cache to avoid re-reading the same JSON multiple times
|
421
|
+
if dynamics_file_name in self._syn_params_cache:
|
422
|
+
syn_params = self._syn_params_cache[dynamics_file_name]
|
423
|
+
else:
|
424
|
+
syn_params = self._load_synaptic_params_from_config(config, dynamics_file_name)
|
425
|
+
# cache result (even if empty dict) to avoid repeated lookups
|
426
|
+
self._syn_params_cache[dynamics_file_name] = syn_params
|
427
|
+
except Exception as e:
|
428
|
+
print(f"Warning: could not load dynamics_params file '{dynamics_file_name}' for edge {edge_dataset_name}: {e}")
|
429
|
+
|
430
|
+
# If a dynamics params JSON filename was provided, prefer using its basename
|
431
|
+
# as the connection name so that the UI matches the JSON definitions.
|
432
|
+
if dynamics_file_name:
|
433
|
+
try:
|
434
|
+
json_base = os.path.splitext(os.path.basename(dynamics_file_name))[0]
|
435
|
+
# Ensure uniqueness in conn_type_settings
|
436
|
+
if json_base in conn_type_settings:
|
437
|
+
# Append edge_type_id to disambiguate
|
438
|
+
json_base = f"{json_base}_type_{edge_type_id}"
|
439
|
+
conn_name = json_base
|
440
|
+
except Exception:
|
441
|
+
pass
|
442
|
+
|
443
|
+
# If the dynamics params defined a level_of_detail, override the default
|
444
|
+
if isinstance(syn_params, dict) and 'level_of_detail' in syn_params:
|
445
|
+
conn_settings['spec_settings']['level_of_detail'] = syn_params.get('level_of_detail', model_template)
|
446
|
+
|
447
|
+
# Add synaptic parameters, excluding level_of_detail
|
448
|
+
for key, value in syn_params.items():
|
449
|
+
if key != 'level_of_detail':
|
450
|
+
conn_settings['spec_syn_param'][key] = value
|
451
|
+
else:
|
452
|
+
# Fallback: some SONATA/H5 edge files expose dynamics params as flattened
|
453
|
+
# columns named like 'dynamics_params/<param>'. If no filename was given,
|
454
|
+
# gather any such columns from edge_info and use them as spec_syn_param.
|
455
|
+
for col in edge_info.index:
|
456
|
+
if isinstance(col, str) and col.startswith('dynamics_params/'):
|
457
|
+
param_key = col.split('/', 1)[1]
|
458
|
+
try:
|
459
|
+
val = edge_info[col]
|
460
|
+
if pd.notna(val):
|
461
|
+
conn_settings['spec_syn_param'][param_key] = val
|
462
|
+
except Exception:
|
463
|
+
# Ignore malformed entries
|
464
|
+
pass
|
465
|
+
|
466
|
+
# Add weight from edge info if available
|
467
|
+
if 'syn_weight' in edge_info and pd.notna(edge_info['syn_weight']):
|
468
|
+
conn_settings['spec_syn_param']['initW'] = float(edge_info['syn_weight'])
|
469
|
+
|
470
|
+
# Handle afferent section information
|
471
|
+
if 'afferent_section_id' in edge_info and pd.notna(edge_info['afferent_section_id']):
|
472
|
+
conn_settings['spec_settings']['sec_id'] = int(edge_info['afferent_section_id'])
|
473
|
+
|
474
|
+
if 'afferent_section_pos' in edge_info and pd.notna(edge_info['afferent_section_pos']):
|
475
|
+
conn_settings['spec_settings']['sec_x'] = float(edge_info['afferent_section_pos'])
|
476
|
+
|
477
|
+
# Store in connection settings
|
478
|
+
conn_type_settings[conn_name] = conn_settings
|
479
|
+
|
480
|
+
return conn_type_settings
|
481
|
+
|
482
|
+
def _load_available_networks(self) -> None:
|
483
|
+
"""
|
484
|
+
Load available network names from the config file for the network dropdown feature.
|
485
|
+
|
486
|
+
This method is automatically called during initialization when a config file is provided.
|
487
|
+
It populates the available_networks list which enables the network dropdown in
|
488
|
+
InteractiveTuner when multiple networks are available.
|
489
|
+
|
490
|
+
Network Dropdown Behavior:
|
491
|
+
-------------------------
|
492
|
+
- If only one network exists: No network dropdown is shown
|
493
|
+
- If multiple networks exist: Network dropdown appears next to connection dropdown
|
494
|
+
- Networks are loaded from the edges data in the config file
|
495
|
+
- Current network defaults to the first available if not specified during init
|
496
|
+
"""
|
497
|
+
if self.config is None:
|
498
|
+
self.available_networks = []
|
499
|
+
return
|
500
|
+
|
501
|
+
try:
|
502
|
+
edges = load_edges_from_config(self.config)
|
503
|
+
self.available_networks = list(edges.keys())
|
504
|
+
|
505
|
+
# Set current network to first available if not specified
|
506
|
+
if self.current_network is None and self.available_networks:
|
507
|
+
self.current_network = self.available_networks[0]
|
508
|
+
except Exception as e:
|
509
|
+
print(f"Warning: Could not load networks from config: {e}")
|
510
|
+
self.available_networks = []
|
511
|
+
|
512
|
+
def _load_synaptic_params_from_config(self, config: dict, dynamics_params: str) -> dict:
|
513
|
+
"""
|
514
|
+
Load synaptic parameters from dynamics params file using config information.
|
515
|
+
|
516
|
+
Parameters:
|
517
|
+
-----------
|
518
|
+
config : dict
|
519
|
+
BMTK configuration dictionary
|
520
|
+
dynamics_params : str
|
521
|
+
Dynamics parameters filename
|
522
|
+
|
523
|
+
Returns:
|
524
|
+
--------
|
525
|
+
dict
|
526
|
+
Synaptic parameters dictionary
|
527
|
+
"""
|
528
|
+
try:
|
529
|
+
# Get the synaptic models directory from config
|
530
|
+
synaptic_models_dir = config.get('components', {}).get('synaptic_models_dir', '')
|
531
|
+
if synaptic_models_dir:
|
532
|
+
# Handle path variables
|
533
|
+
if synaptic_models_dir.startswith('$'):
|
534
|
+
# This is a placeholder, try to resolve it
|
535
|
+
config_dir = os.path.dirname(config.get('config_path', ''))
|
536
|
+
synaptic_models_dir = synaptic_models_dir.replace('$COMPONENTS_DIR',
|
537
|
+
os.path.join(config_dir, 'components'))
|
538
|
+
synaptic_models_dir = synaptic_models_dir.replace('$BASE_DIR', config_dir)
|
539
|
+
|
540
|
+
dynamics_file = os.path.join(synaptic_models_dir, dynamics_params)
|
541
|
+
|
542
|
+
if os.path.exists(dynamics_file):
|
543
|
+
with open(dynamics_file, 'r') as f:
|
544
|
+
return json.load(f)
|
545
|
+
else:
|
546
|
+
print(f"Warning: Dynamics params file not found: {dynamics_file}")
|
547
|
+
except Exception as e:
|
548
|
+
print(f"Warning: Error loading synaptic parameters: {e}")
|
549
|
+
|
550
|
+
return {}
|
551
|
+
|
552
|
+
@classmethod
|
553
|
+
def list_connections_from_config(cls, config_path: str, network: Optional[str] = None) -> Dict[str, dict]:
|
554
|
+
"""
|
555
|
+
Class method to list all available connections from a BMTK config file without creating a tuner.
|
556
|
+
|
557
|
+
Parameters:
|
558
|
+
-----------
|
559
|
+
config_path : str
|
560
|
+
Path to the simulation config JSON file.
|
561
|
+
network : Optional[str]
|
562
|
+
Name of the specific network dataset to access (e.g., 'network_to_network').
|
563
|
+
If None, processes all available networks.
|
564
|
+
|
565
|
+
Returns:
|
566
|
+
--------
|
567
|
+
Dict[str, dict]
|
568
|
+
Dictionary with connection names as keys and connection info as values.
|
569
|
+
"""
|
570
|
+
# Create a temporary instance just to use the parsing methods
|
571
|
+
temp_tuner = cls.__new__(cls) # Create without calling __init__
|
572
|
+
conn_type_settings = temp_tuner._build_conn_type_settings_from_config(config_path, network=network)
|
573
|
+
|
574
|
+
# Create a summary of connections with key info
|
575
|
+
connections_summary = {}
|
576
|
+
for conn_name, settings in conn_type_settings.items():
|
577
|
+
connections_summary[conn_name] = {
|
578
|
+
'post_cell': settings['spec_settings']['post_cell'],
|
579
|
+
'synapse_type': settings['spec_settings']['level_of_detail'],
|
580
|
+
'parameters': list(settings['spec_syn_param'].keys())
|
581
|
+
}
|
582
|
+
|
583
|
+
return connections_summary
|
584
|
+
|
585
|
+
def _switch_connection(self, new_connection: str) -> None:
|
586
|
+
"""
|
587
|
+
Switch to a different connection type and update all related properties.
|
588
|
+
|
589
|
+
Parameters:
|
590
|
+
-----------
|
591
|
+
new_connection : str
|
592
|
+
Name of the new connection type to switch to.
|
593
|
+
"""
|
594
|
+
if new_connection not in self.conn_type_settings:
|
595
|
+
raise ValueError(f"Connection '{new_connection}' not found in conn_type_settings.")
|
596
|
+
|
597
|
+
# Update current connection
|
598
|
+
self.current_connection = new_connection
|
599
|
+
self.conn = self.conn_type_settings[new_connection]
|
600
|
+
self.synaptic_props = self.conn["spec_syn_param"]
|
601
|
+
|
602
|
+
# Update slider vars for new connection
|
603
|
+
if hasattr(self, 'original_slider_vars'):
|
604
|
+
# Filter slider vars based on new connection's parameters
|
605
|
+
self.slider_vars = {
|
606
|
+
key: value for key, value in self.synaptic_props.items()
|
607
|
+
if key in self.original_slider_vars
|
608
|
+
}
|
609
|
+
|
610
|
+
# Check for missing keys and try to get them from the synapse
|
611
|
+
for key in self.original_slider_vars:
|
612
|
+
if key not in self.synaptic_props:
|
613
|
+
try:
|
614
|
+
# We'll get this after recreating the synapse
|
615
|
+
pass
|
616
|
+
except AttributeError as e:
|
617
|
+
print(f"Warning: Could not access '{key}' for connection '{new_connection}': {e}")
|
618
|
+
else:
|
619
|
+
self.slider_vars = self.synaptic_props
|
620
|
+
|
621
|
+
# Need to recreate the cell if it's different
|
622
|
+
if self.hoc_cell is None:
|
623
|
+
# Check if we need a different cell type
|
624
|
+
new_cell_type = self.conn["spec_settings"]["post_cell"]
|
625
|
+
if not hasattr(self, '_current_cell_type') or self._current_cell_type != new_cell_type:
|
626
|
+
self._current_cell_type = new_cell_type
|
627
|
+
self._set_up_cell()
|
628
|
+
|
629
|
+
# Recreate synapse for new connection
|
630
|
+
self._set_up_synapse()
|
631
|
+
|
632
|
+
# Update any missing slider vars from the new synapse
|
633
|
+
if hasattr(self, 'original_slider_vars'):
|
634
|
+
for key in self.original_slider_vars:
|
635
|
+
if key not in self.synaptic_props:
|
636
|
+
try:
|
637
|
+
value = getattr(self.syn, key)
|
638
|
+
self.slider_vars[key] = value
|
639
|
+
except AttributeError as e:
|
640
|
+
print(f"Warning: Could not access '{key}' for connection '{new_connection}': {e}")
|
641
|
+
|
642
|
+
# Recreate NetCon connections with new synapse
|
643
|
+
self.nc = h.NetCon(
|
644
|
+
self.nstim,
|
645
|
+
self.syn,
|
646
|
+
self.general_settings["threshold"],
|
647
|
+
self.general_settings["delay"],
|
648
|
+
self.general_settings["weight"],
|
649
|
+
)
|
650
|
+
self.nc2 = h.NetCon(
|
651
|
+
self.nstim2,
|
652
|
+
self.syn,
|
653
|
+
self.general_settings["threshold"],
|
654
|
+
self.general_settings["delay"],
|
655
|
+
self.general_settings["weight"],
|
656
|
+
)
|
657
|
+
|
658
|
+
# Recreate voltage clamp with potentially new cell
|
659
|
+
self.vcl = h.VClamp(self.cell.soma[0](0.5))
|
660
|
+
|
661
|
+
# Recreate recorders for new synapse
|
662
|
+
self._set_up_recorders()
|
663
|
+
|
664
|
+
# Reset NEURON state
|
665
|
+
h.finitialize()
|
666
|
+
|
667
|
+
print(f"Successfully switched to connection: {new_connection}")
|
668
|
+
|
669
|
+
def _switch_network(self, new_network: str) -> None:
|
670
|
+
"""
|
671
|
+
Switch to a different network and rebuild conn_type_settings for the new network.
|
672
|
+
|
673
|
+
This method is called when the user selects a different network from the network
|
674
|
+
dropdown in InteractiveTuner. It performs a complete rebuild of the connection
|
675
|
+
types available for the new network.
|
676
|
+
|
677
|
+
Parameters:
|
678
|
+
-----------
|
679
|
+
new_network : str
|
680
|
+
Name of the new network to switch to.
|
681
|
+
|
682
|
+
Network Switching Process:
|
683
|
+
-------------------------
|
684
|
+
1. Validates the new network exists in available_networks
|
685
|
+
2. Rebuilds conn_type_settings using the new network's edge data
|
686
|
+
3. Updates the connection dropdown with new network's available connections
|
687
|
+
4. Preserves current connection if it exists in new network
|
688
|
+
5. Falls back to first available connection if current doesn't exist
|
689
|
+
6. Recreates synapses and NEURON objects for the new connection
|
690
|
+
7. Updates UI components to reflect the changes
|
691
|
+
"""
|
692
|
+
if new_network not in self.available_networks:
|
693
|
+
print(f"Warning: Network '{new_network}' not found in available networks: {self.available_networks}")
|
694
|
+
return
|
695
|
+
|
696
|
+
if new_network == self.current_network:
|
697
|
+
return # No change needed
|
698
|
+
|
699
|
+
# Update current network
|
700
|
+
self.current_network = new_network
|
701
|
+
|
702
|
+
# Switch conn_type_settings using prebuilt data if available, otherwise build on-demand
|
703
|
+
if self.config:
|
704
|
+
print(f"Switching connections for network: {new_network}")
|
705
|
+
if hasattr(self, '_prebuilt_conn_type_settings') and new_network in self._prebuilt_conn_type_settings:
|
706
|
+
self.conn_type_settings = self._prebuilt_conn_type_settings[new_network]
|
707
|
+
else:
|
708
|
+
# Fallback: build on-demand (slower)
|
709
|
+
self.conn_type_settings = self._build_conn_type_settings_from_config(self.config, network=new_network)
|
710
|
+
|
711
|
+
# Update available connections and select first one if current doesn't exist
|
712
|
+
available_connections = list(self.conn_type_settings.keys())
|
713
|
+
if self.current_connection not in available_connections and available_connections:
|
714
|
+
self.current_connection = available_connections[0]
|
715
|
+
print(f"Connection '{self.current_connection}' not available in new network. Switched to: {available_connections[0]}")
|
716
|
+
|
717
|
+
# Switch to the (potentially new) connection
|
718
|
+
if self.current_connection in self.conn_type_settings:
|
719
|
+
self._switch_connection(self.current_connection)
|
720
|
+
|
721
|
+
print(f"Successfully switched to network: {new_network}")
|
722
|
+
print(f"Available connections: {available_connections}")
|
723
|
+
|
177
724
|
def _update_spec_syn_param(self, json_folder_path: str) -> None:
|
178
725
|
"""
|
179
726
|
Update specific synaptic parameters using JSON files located in the specified folder.
|
@@ -213,11 +760,14 @@ class SynapseTuner:
|
|
213
760
|
- `_set_up_cell()` should be called before setting up the synapse.
|
214
761
|
- Synapse location, type, and properties are specified within `spec_syn_param` and `spec_settings`.
|
215
762
|
"""
|
216
|
-
|
217
|
-
|
218
|
-
self.conn["spec_settings"]["
|
763
|
+
try:
|
764
|
+
self.syn = getattr(h, self.conn["spec_settings"]["level_of_detail"])(
|
765
|
+
list(self.cell.all)[self.conn["spec_settings"]["sec_id"]](
|
766
|
+
self.conn["spec_settings"]["sec_x"]
|
767
|
+
)
|
219
768
|
)
|
220
|
-
|
769
|
+
except:
|
770
|
+
raise Exception("Make sure the mod file exist you are trying to load check spelling!")
|
221
771
|
for key, value in self.conn["spec_syn_param"].items():
|
222
772
|
if isinstance(value, (int, float)):
|
223
773
|
if hasattr(self.syn, key):
|
@@ -741,6 +1291,8 @@ class SynapseTuner:
|
|
741
1291
|
Sets up interactive sliders for tuning short-term plasticity (STP) parameters in a Jupyter Notebook.
|
742
1292
|
|
743
1293
|
This method creates an interactive UI with sliders for:
|
1294
|
+
- Network selection dropdown (if multiple networks available and config provided)
|
1295
|
+
- Connection type selection dropdown
|
744
1296
|
- Input frequency
|
745
1297
|
- Delay between pulse trains
|
746
1298
|
- Duration of stimulation (for continuous input mode)
|
@@ -752,10 +1304,21 @@ class SynapseTuner:
|
|
752
1304
|
- Toggling voltage clamp mode
|
753
1305
|
- Switching between standard and continuous input modes
|
754
1306
|
|
1307
|
+
Network Dropdown Feature:
|
1308
|
+
------------------------
|
1309
|
+
When the SynapseTuner is initialized with a BMTK config file containing multiple networks:
|
1310
|
+
- A network dropdown appears next to the connection dropdown
|
1311
|
+
- Users can dynamically switch between networks (e.g., 'network_to_network', 'external_to_network')
|
1312
|
+
- Switching networks rebuilds available connections and updates the connection dropdown
|
1313
|
+
- The current connection is preserved if it exists in the new network
|
1314
|
+
- If multiple networks exist but only one is specified during init, that network is used as default
|
1315
|
+
|
755
1316
|
Notes:
|
756
1317
|
------
|
757
1318
|
Ideal for exploratory parameter tuning and interactive visualization of
|
758
1319
|
synapse behavior with different parameter values and stimulation protocols.
|
1320
|
+
The network dropdown feature enables comprehensive exploration of multi-network
|
1321
|
+
BMTK simulations without needing to reinitialize the tuner.
|
759
1322
|
"""
|
760
1323
|
# Widgets setup (Sliders)
|
761
1324
|
freqs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 35, 50, 100, 200]
|
@@ -766,6 +1329,26 @@ class SynapseTuner:
|
|
766
1329
|
duration0 = 300
|
767
1330
|
vlamp_status = self.vclamp
|
768
1331
|
|
1332
|
+
# Connection dropdown
|
1333
|
+
connection_options = list(self.conn_type_settings.keys())
|
1334
|
+
w_connection = widgets.Dropdown(
|
1335
|
+
options=connection_options,
|
1336
|
+
value=self.current_connection,
|
1337
|
+
description="Connection:",
|
1338
|
+
style={'description_width': 'initial'}
|
1339
|
+
)
|
1340
|
+
|
1341
|
+
# Network dropdown - only shown if config was provided and multiple networks are available
|
1342
|
+
# This enables users to switch between different network datasets dynamically
|
1343
|
+
w_network = None
|
1344
|
+
if self.config is not None and len(self.available_networks) > 1:
|
1345
|
+
w_network = widgets.Dropdown(
|
1346
|
+
options=self.available_networks,
|
1347
|
+
value=self.current_network,
|
1348
|
+
description="Network:",
|
1349
|
+
style={'description_width': 'initial'}
|
1350
|
+
)
|
1351
|
+
|
769
1352
|
w_run = widgets.Button(description="Run Train", icon="history", button_style="primary")
|
770
1353
|
w_single = widgets.Button(description="Single Event", icon="check", button_style="success")
|
771
1354
|
w_vclamp = widgets.ToggleButton(
|
@@ -774,6 +1357,17 @@ class SynapseTuner:
|
|
774
1357
|
icon="fast-backward",
|
775
1358
|
button_style="warning",
|
776
1359
|
)
|
1360
|
+
|
1361
|
+
# Voltage clamp amplitude input
|
1362
|
+
default_vclamp_amp = getattr(self.conn['spec_settings'], 'vclamp_amp', -70.0)
|
1363
|
+
w_vclamp_amp = widgets.FloatText(
|
1364
|
+
value=default_vclamp_amp,
|
1365
|
+
description="V_clamp (mV):",
|
1366
|
+
step=5.0,
|
1367
|
+
style={'description_width': 'initial'},
|
1368
|
+
layout=widgets.Layout(width='150px')
|
1369
|
+
)
|
1370
|
+
|
777
1371
|
w_input_mode = widgets.ToggleButton(
|
778
1372
|
value=False, description="Continuous input", icon="eject", button_style="info"
|
779
1373
|
)
|
@@ -785,54 +1379,178 @@ class SynapseTuner:
|
|
785
1379
|
options=durations, value=duration0, description="Duration"
|
786
1380
|
)
|
787
1381
|
|
1382
|
+
def create_dynamic_sliders():
|
1383
|
+
"""Create sliders based on current connection's parameters"""
|
1384
|
+
sliders = {}
|
1385
|
+
for key, value in self.slider_vars.items():
|
1386
|
+
if isinstance(value, (int, float)): # Only create sliders for numeric values
|
1387
|
+
if hasattr(self.syn, key):
|
1388
|
+
if value == 0:
|
1389
|
+
print(
|
1390
|
+
f"{key} was set to zero, going to try to set a range of values, try settings the {key} to a nonzero value if you dont like the range!"
|
1391
|
+
)
|
1392
|
+
slider = widgets.FloatSlider(
|
1393
|
+
value=value, min=0, max=1000, step=1, description=key
|
1394
|
+
)
|
1395
|
+
else:
|
1396
|
+
slider = widgets.FloatSlider(
|
1397
|
+
value=value, min=0, max=value * 20, step=value / 5, description=key
|
1398
|
+
)
|
1399
|
+
sliders[key] = slider
|
1400
|
+
else:
|
1401
|
+
print(f"skipping slider for {key} due to not being a synaptic variable")
|
1402
|
+
return sliders
|
1403
|
+
|
788
1404
|
# Generate sliders dynamically based on valid numeric entries in self.slider_vars
|
789
|
-
self.dynamic_sliders =
|
1405
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
790
1406
|
print(
|
791
1407
|
"Setting up slider! The sliders ranges are set by their init value so try changing that if you dont like the slider range!"
|
792
1408
|
)
|
793
|
-
for key, value in self.slider_vars.items():
|
794
|
-
if isinstance(value, (int, float)): # Only create sliders for numeric values
|
795
|
-
if hasattr(self.syn, key):
|
796
|
-
if value == 0:
|
797
|
-
print(
|
798
|
-
f"{key} was set to zero, going to try to set a range of values, try settings the {key} to a nonzero value if you dont like the range!"
|
799
|
-
)
|
800
|
-
slider = widgets.FloatSlider(
|
801
|
-
value=value, min=0, max=1000, step=1, description=key
|
802
|
-
)
|
803
|
-
else:
|
804
|
-
slider = widgets.FloatSlider(
|
805
|
-
value=value, min=0, max=value * 20, step=value / 5, description=key
|
806
|
-
)
|
807
|
-
self.dynamic_sliders[key] = slider
|
808
|
-
else:
|
809
|
-
print(f"skipping slider for {key} due to not being a synaptic variable")
|
810
1409
|
|
1410
|
+
# Create output widget for displaying results
|
1411
|
+
output_widget = widgets.Output()
|
1412
|
+
|
811
1413
|
def run_single_event(*args):
|
812
1414
|
clear_output()
|
813
1415
|
display(ui)
|
1416
|
+
display(output_widget)
|
1417
|
+
|
814
1418
|
self.vclamp = w_vclamp.value
|
1419
|
+
# Update voltage clamp amplitude if voltage clamp is enabled
|
1420
|
+
if self.vclamp:
|
1421
|
+
# Update the voltage clamp amplitude settings
|
1422
|
+
self.conn['spec_settings']['vclamp_amp'] = w_vclamp_amp.value
|
1423
|
+
# Update general settings if they exist
|
1424
|
+
if hasattr(self, 'general_settings'):
|
1425
|
+
self.general_settings['vclamp_amp'] = w_vclamp_amp.value
|
815
1426
|
# Update synaptic properties based on slider values
|
816
1427
|
self.ispk = None
|
817
|
-
|
1428
|
+
|
1429
|
+
# Clear previous results and run simulation
|
1430
|
+
output_widget.clear_output()
|
1431
|
+
with output_widget:
|
1432
|
+
self.SingleEvent()
|
1433
|
+
|
1434
|
+
def on_connection_change(*args):
|
1435
|
+
"""Handle connection dropdown change"""
|
1436
|
+
try:
|
1437
|
+
new_connection = w_connection.value
|
1438
|
+
if new_connection != self.current_connection:
|
1439
|
+
# Switch to new connection
|
1440
|
+
self._switch_connection(new_connection)
|
1441
|
+
|
1442
|
+
# Recreate dynamic sliders for new connection
|
1443
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
1444
|
+
|
1445
|
+
# Update UI
|
1446
|
+
update_ui_layout()
|
1447
|
+
update_ui()
|
1448
|
+
|
1449
|
+
except Exception as e:
|
1450
|
+
print(f"Error switching connection: {e}")
|
1451
|
+
|
1452
|
+
def on_network_change(*args):
|
1453
|
+
"""
|
1454
|
+
Handle network dropdown change events.
|
1455
|
+
|
1456
|
+
This callback is triggered when the user selects a different network from
|
1457
|
+
the network dropdown. It coordinates the complete switching process:
|
1458
|
+
1. Calls _switch_network() to rebuild connections for the new network
|
1459
|
+
2. Updates the connection dropdown options with new network's connections
|
1460
|
+
3. Recreates dynamic sliders for the new connection parameters
|
1461
|
+
4. Refreshes the entire UI to reflect all changes
|
1462
|
+
"""
|
1463
|
+
if w_network is None:
|
1464
|
+
return
|
1465
|
+
try:
|
1466
|
+
new_network = w_network.value
|
1467
|
+
if new_network != self.current_network:
|
1468
|
+
# Switch to new network
|
1469
|
+
self._switch_network(new_network)
|
1470
|
+
|
1471
|
+
# Update connection dropdown options with new network's connections
|
1472
|
+
connection_options = list(self.conn_type_settings.keys())
|
1473
|
+
w_connection.options = connection_options
|
1474
|
+
if connection_options:
|
1475
|
+
w_connection.value = self.current_connection
|
1476
|
+
|
1477
|
+
# Recreate dynamic sliders for new connection
|
1478
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
1479
|
+
|
1480
|
+
# Update UI
|
1481
|
+
update_ui_layout()
|
1482
|
+
update_ui()
|
1483
|
+
|
1484
|
+
except Exception as e:
|
1485
|
+
print(f"Error switching network: {e}")
|
1486
|
+
|
1487
|
+
def update_ui_layout():
|
1488
|
+
"""
|
1489
|
+
Update the UI layout with new sliders and network dropdown.
|
1490
|
+
|
1491
|
+
This function reconstructs the entire UI layout including:
|
1492
|
+
- Network dropdown (if available) and connection dropdown in the top row
|
1493
|
+
- Button controls and input mode toggles
|
1494
|
+
- Parameter sliders arranged in columns
|
1495
|
+
"""
|
1496
|
+
nonlocal ui, slider_columns
|
1497
|
+
|
1498
|
+
# Add the dynamic sliders to the UI
|
1499
|
+
slider_widgets = [slider for slider in self.dynamic_sliders.values()]
|
1500
|
+
|
1501
|
+
if slider_widgets:
|
1502
|
+
half = len(slider_widgets) // 2
|
1503
|
+
col1 = VBox(slider_widgets[:half])
|
1504
|
+
col2 = VBox(slider_widgets[half:])
|
1505
|
+
slider_columns = HBox([col1, col2])
|
1506
|
+
else:
|
1507
|
+
slider_columns = VBox([])
|
1508
|
+
|
1509
|
+
# Create button row with voltage clamp controls
|
1510
|
+
if w_vclamp.value: # Show voltage clamp amplitude input when toggle is on
|
1511
|
+
button_row = HBox([w_run, w_single, w_vclamp, w_vclamp_amp, w_input_mode])
|
1512
|
+
else: # Hide voltage clamp amplitude input when toggle is off
|
1513
|
+
button_row = HBox([w_run, w_single, w_vclamp, w_input_mode])
|
1514
|
+
|
1515
|
+
# Construct the top row - include network dropdown if available
|
1516
|
+
# This creates a horizontal layout with network dropdown (if present) and connection dropdown
|
1517
|
+
if w_network is not None:
|
1518
|
+
connection_row = HBox([w_network, w_connection])
|
1519
|
+
else:
|
1520
|
+
connection_row = HBox([w_connection])
|
1521
|
+
slider_row = HBox([w_input_freq, self.w_delay, self.w_duration])
|
1522
|
+
|
1523
|
+
ui = VBox([connection_row, button_row, slider_row, slider_columns])
|
818
1524
|
|
819
1525
|
# Function to update UI based on input mode
|
820
1526
|
def update_ui(*args):
|
821
1527
|
clear_output()
|
822
1528
|
display(ui)
|
1529
|
+
display(output_widget)
|
1530
|
+
|
823
1531
|
self.vclamp = w_vclamp.value
|
1532
|
+
# Update voltage clamp amplitude if voltage clamp is enabled
|
1533
|
+
if self.vclamp:
|
1534
|
+
self.conn['spec_settings']['vclamp_amp'] = w_vclamp_amp.value
|
1535
|
+
if hasattr(self, 'general_settings'):
|
1536
|
+
self.general_settings['vclamp_amp'] = w_vclamp_amp.value
|
1537
|
+
|
824
1538
|
self.input_mode = w_input_mode.value
|
825
1539
|
syn_props = {var: slider.value for var, slider in self.dynamic_sliders.items()}
|
826
1540
|
self._set_syn_prop(**syn_props)
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
1541
|
+
|
1542
|
+
# Clear previous results and run simulation
|
1543
|
+
output_widget.clear_output()
|
1544
|
+
with output_widget:
|
1545
|
+
if not self.input_mode:
|
1546
|
+
self._simulate_model(w_input_freq.value, self.w_delay.value, w_vclamp.value)
|
1547
|
+
else:
|
1548
|
+
self._simulate_model(w_input_freq.value, self.w_duration.value, w_vclamp.value)
|
1549
|
+
amp = self._response_amplitude()
|
1550
|
+
self._plot_model(
|
1551
|
+
[self.general_settings["tstart"] - self.nstim.interval / 3, self.tstop]
|
1552
|
+
)
|
1553
|
+
_ = self._calc_ppr_induction_recovery(amp)
|
836
1554
|
|
837
1555
|
# Function to switch between delay and duration sliders
|
838
1556
|
def switch_slider(*args):
|
@@ -843,8 +1561,21 @@ class SynapseTuner:
|
|
843
1561
|
self.w_delay.layout.display = "" # Show delay slider
|
844
1562
|
self.w_duration.layout.display = "none" # Hide duration slider
|
845
1563
|
|
846
|
-
#
|
1564
|
+
# Function to handle voltage clamp toggle
|
1565
|
+
def on_vclamp_toggle(*args):
|
1566
|
+
"""Handle voltage clamp toggle changes to show/hide amplitude input"""
|
1567
|
+
update_ui_layout()
|
1568
|
+
clear_output()
|
1569
|
+
display(ui)
|
1570
|
+
display(output_widget)
|
1571
|
+
|
1572
|
+
# Link widgets to their callback functions
|
1573
|
+
w_connection.observe(on_connection_change, names="value")
|
1574
|
+
# Link network dropdown callback only if network dropdown was created
|
1575
|
+
if w_network is not None:
|
1576
|
+
w_network.observe(on_network_change, names="value")
|
847
1577
|
w_input_mode.observe(switch_slider, names="value")
|
1578
|
+
w_vclamp.observe(on_vclamp_toggle, names="value")
|
848
1579
|
|
849
1580
|
# Hide the duration slider initially until the user selects it
|
850
1581
|
self.w_duration.layout.display = "none" # Hide duration slider
|
@@ -852,18 +1583,10 @@ class SynapseTuner:
|
|
852
1583
|
w_single.on_click(run_single_event)
|
853
1584
|
w_run.on_click(update_ui)
|
854
1585
|
|
855
|
-
#
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
slider_row = HBox([w_input_freq, self.w_delay, self.w_duration])
|
860
|
-
|
861
|
-
half = len(slider_widgets) // 2
|
862
|
-
col1 = VBox(slider_widgets[:half])
|
863
|
-
col2 = VBox(slider_widgets[half:])
|
864
|
-
slider_columns = HBox([col1, col2])
|
865
|
-
|
866
|
-
ui = VBox([button_row, slider_row, slider_columns])
|
1586
|
+
# Initial UI setup
|
1587
|
+
slider_columns = VBox([])
|
1588
|
+
ui = VBox([])
|
1589
|
+
update_ui_layout()
|
867
1590
|
|
868
1591
|
display(ui)
|
869
1592
|
update_ui()
|
@@ -5,8 +5,8 @@ bmtool/connectors.py,sha256=4SJRqBXM145_-nFycGrnlfjSyaoarOr0QkHl00jWq4I,74384
|
|
5
5
|
bmtool/graphs.py,sha256=gBTzI6c2BBK49dWGcfWh9c56TAooyn-KaiEy0Im1HcI,6717
|
6
6
|
bmtool/manage.py,sha256=lsgRejp02P-x6QpA7SXcyXdalPhRmypoviIA2uAitQs,608
|
7
7
|
bmtool/plot_commands.py,sha256=Dxm_RaT4CtHnfsltTtUopJ4KVbfhxtktEB_b7bFEXII,12716
|
8
|
-
bmtool/singlecell.py,sha256=
|
9
|
-
bmtool/synapses.py,sha256=
|
8
|
+
bmtool/singlecell.py,sha256=xqdLM2TjjnL8YyTy-c3WR6mElTv3E4zkZgkfUh4S5X0,47161
|
9
|
+
bmtool/synapses.py,sha256=k-xyZjElz2CHM2oGXqBnFOCd0NusoA6JASzPXHPCj5c,106068
|
10
10
|
bmtool/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
bmtool/analysis/entrainment.py,sha256=NQloQtVpEWjDzmkZwMWVcm3hSjErHBZfQl1mrBVoIE8,25321
|
12
12
|
bmtool/analysis/lfp.py,sha256=S2JvxkjcK3-EH93wCrhqNSFY6cX7fOq74pz64ibHKrc,26556
|
@@ -26,9 +26,9 @@ bmtool/util/commands.py,sha256=Nn-R-4e9g8ZhSPZvTkr38xeKRPfEMANB9Lugppj82UI,68564
|
|
26
26
|
bmtool/util/util.py,sha256=TAWdGd0tDuouS-JiusMs8WwP7kQpWHPr1nu0XG01TBQ,75056
|
27
27
|
bmtool/util/neuron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
bmtool/util/neuron/celltuner.py,sha256=lokRLUM1rsdSYBYrNbLBBo39j14mm8TBNVNRnSlhHCk,94868
|
29
|
-
bmtool-0.7.
|
30
|
-
bmtool-0.7.
|
31
|
-
bmtool-0.7.
|
32
|
-
bmtool-0.7.
|
33
|
-
bmtool-0.7.
|
34
|
-
bmtool-0.7.
|
29
|
+
bmtool-0.7.5.1.dist-info/licenses/LICENSE,sha256=qrXg2jj6kz5d0EnN11hllcQt2fcWVNumx0xNbV05nyM,1068
|
30
|
+
bmtool-0.7.5.1.dist-info/METADATA,sha256=H1XxiaHJuU-Bq_HTck5_z1fm08rTmSNxZYBA7hVcmNk,3597
|
31
|
+
bmtool-0.7.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
32
|
+
bmtool-0.7.5.1.dist-info/entry_points.txt,sha256=0-BHZ6nUnh0twWw9SXNTiRmKjDnb1VO2DfG_-oprhAc,45
|
33
|
+
bmtool-0.7.5.1.dist-info/top_level.txt,sha256=gpd2Sj-L9tWbuJEd5E8C8S8XkNm5yUE76klUYcM-eWM,7
|
34
|
+
bmtool-0.7.5.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|