bmtool 0.7.5.1__py3-none-any.whl → 0.7.7__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 CHANGED
@@ -769,7 +769,7 @@ class FI(object):
769
769
  --------
770
770
  tuple
771
771
  (current_amplitudes, spike_counts) where:
772
- - current_amplitudes: List of current injection amplitudes (nA)
772
+ - current_amplitudes: List of current injection amplitudes (pA)
773
773
  - spike_counts: List of spike counts corresponding to each amplitude
774
774
  """
775
775
  print("Running simulations for FI curve...")
@@ -781,14 +781,14 @@ class FI(object):
781
781
  print()
782
782
  print("Results")
783
783
  # lets make a df so the results line up nice
784
- data = {"Injection (nA):": self.amps, "number of spikes": self.nspks}
784
+ data = {"Injection (pA):": [amp * 1000 for amp in self.amps], "number of spikes": self.nspks}
785
785
  df = pd.DataFrame(data)
786
786
  print(df)
787
- # print(f'Injection (nA): ' + ', '.join(f'{x:g}' for x in self.amps))
787
+ # print(f'Injection (pA): ' + ', '.join(f'{x:g}' for x in self.amps))
788
788
  # print(f'Number of spikes: ' + ', '.join(f'{x:d}' for x in self.nspks))
789
789
  print()
790
790
 
791
- return self.amps, self.nspks
791
+ return [amp * 1000 for amp in self.amps], self.nspks
792
792
 
793
793
 
794
794
  class ZAP(CurrentClamp):
@@ -1040,6 +1040,8 @@ class Profiler:
1040
1040
  # initialize to None and then prefer config-derived paths if provided
1041
1041
  self.template_dir = None
1042
1042
  self.mechanism_dir = None
1043
+ self.templates = None # Initialize templates attribute
1044
+ self.config = config # Store config path
1043
1045
 
1044
1046
  # If a BMTK config is provided, load mechanisms/templates from it
1045
1047
  if config is not None:
@@ -1067,7 +1069,7 @@ class Profiler:
1067
1069
  load_templates_from_config(config)
1068
1070
  except Exception:
1069
1071
  # fall back to explicit dirs if config parsing/loading fails
1070
- pass
1072
+ print('failed')
1071
1073
 
1072
1074
  else:
1073
1075
  # fall back to explicit args if not set by config
@@ -1091,30 +1093,54 @@ class Profiler:
1091
1093
 
1092
1094
  def load_templates(self, hoc_template_file=None):
1093
1095
  if self.templates is None: # Can really only do this once
1094
- if (
1095
- self.mechanism_dir != "./"
1096
- and self.mechanism_dir != "."
1097
- and self.mechanism_dir != "././"
1098
- ):
1099
- neuron.load_mechanisms(self.mechanism_dir)
1100
- h_base = set(dir(h))
1101
-
1102
- cwd = os.getcwd()
1103
- os.chdir(self.template_dir)
1104
- if not hoc_template_file:
1105
- self.hoc_templates = glob.glob("*.hoc")
1106
- for hoc_template in self.hoc_templates:
1107
- h.load_file(str(hoc_template))
1096
+ # Check if we have a config file - if so, extract templates from node configs
1097
+ if hasattr(self, 'config') and self.config is not None:
1098
+ try:
1099
+ from bmtool.util.util import load_nodes_from_config
1100
+ nodes_networks = load_nodes_from_config(config=self.config)
1101
+ template_names = set()
1102
+ for nodes in nodes_networks:
1103
+ try:
1104
+ cell_template_names = nodes_networks[nodes]['model_template'].unique()
1105
+ # Clean up template names (remove 'hoc:' prefix if present)
1106
+ for template in cell_template_names:
1107
+ if isinstance(template, str):
1108
+ # Remove 'hoc:' prefix if present
1109
+ clean_name = template.replace('hoc:', '') if template.startswith('hoc:') else template
1110
+ template_names.add(clean_name)
1111
+ except:
1112
+ # If fails, means no model_templates in that network
1113
+ pass
1114
+
1115
+ self.templates = sorted(list(template_names))
1116
+ self.hoc_templates = [] # Templates loaded via config, not hoc files
1117
+
1118
+ except Exception as e:
1119
+ print(f"Failed to load templates from config: {e}")
1108
1120
  else:
1109
- self.hoc_templates = [hoc_template_file]
1110
- h.load_file(hoc_template_file)
1111
-
1112
- os.chdir(cwd)
1113
-
1114
- # h.load_file('biophys_components/hoc_templates/Template.hoc')
1115
- h_loaded = dir(h)
1116
-
1117
- self.templates = [x for x in h_loaded if x not in h_base]
1121
+ # Traditional loading with template_dir and mechanism_dir
1122
+ if (
1123
+ self.mechanism_dir != "./"
1124
+ and self.mechanism_dir != "."
1125
+ and self.mechanism_dir != "././"
1126
+ ):
1127
+ neuron.load_mechanisms(self.mechanism_dir)
1128
+ h_base = set(dir(h))
1129
+
1130
+ cwd = os.getcwd()
1131
+ os.chdir(self.template_dir)
1132
+ if not hoc_template_file:
1133
+ self.hoc_templates = glob.glob("*.hoc")
1134
+ for hoc_template in self.hoc_templates:
1135
+ h.load_file(str(hoc_template))
1136
+ else:
1137
+ self.hoc_templates = [hoc_template_file]
1138
+ h.load_file(hoc_template_file)
1139
+
1140
+ os.chdir(cwd)
1141
+
1142
+ h_loaded = dir(h)
1143
+ self.templates = [x for x in h_loaded if x not in h_base]
1118
1144
 
1119
1145
  return self.templates
1120
1146
 
@@ -1163,10 +1189,16 @@ class Profiler:
1163
1189
 
1164
1190
  if plot:
1165
1191
  plt.figure()
1166
- plt.plot(time, amp)
1192
+ t_array = np.array(time)
1193
+ amp_array = np.array(amp)
1194
+ t_idx = (t_array >= passive.inj_delay) & (t_array <= passive.inj_delay + passive.inj_dur)
1195
+ plt.plot(t_array[t_idx], amp_array[t_idx])
1167
1196
  if passive.method == "exp2":
1168
1197
  plt.plot(*passive.double_exponential_fit(), "r:", label="double exponential fit")
1169
1198
  plt.legend()
1199
+ elif passive.method == "exp":
1200
+ plt.plot(*passive.single_exponential_fit(), "r:", label="single exponential fit")
1201
+ plt.legend()
1170
1202
  plt.title("Passive Cell Current Injection")
1171
1203
  plt.xlabel("Time (ms)")
1172
1204
  plt.ylabel("Membrane Potential (mV)")
@@ -1228,7 +1260,7 @@ class Profiler:
1228
1260
  plot: bool
1229
1261
  automatically plot an fi curve
1230
1262
 
1231
- Returns the injection amplitudes (nA) used, number of spikes per amplitude supplied
1263
+ Returns the injection amplitudes (pA) used, number of spikes per amplitude supplied
1232
1264
  list(amps), list(# of spikes)
1233
1265
  """
1234
1266
  fi = FI(
@@ -1244,7 +1276,7 @@ class Profiler:
1244
1276
  plt.figure()
1245
1277
  plt.plot(amp, nspk)
1246
1278
  plt.title("FI Curve")
1247
- plt.xlabel("Injection (nA)")
1279
+ plt.xlabel("Injection (pA)")
1248
1280
  plt.ylabel("# Spikes")
1249
1281
  plt.show()
1250
1282
 
@@ -1301,6 +1333,372 @@ class Profiler:
1301
1333
 
1302
1334
  return time, amp
1303
1335
 
1336
+ def interactive_runner(self):
1337
+ """Interactive runner for single cell profiling with GUI widgets.
1338
+
1339
+ This method creates an interactive interface using ipywidgets that allows
1340
+ users to select templates and analysis methods, adjust parameters, and run
1341
+ simulations with real-time plotting.
1342
+ """
1343
+ try:
1344
+ import ipywidgets as widgets
1345
+ from IPython.display import display, clear_output
1346
+ import matplotlib.pyplot as plt
1347
+ except ImportError:
1348
+ raise ImportError("ipywidgets and matplotlib are required for interactive mode. Install with: pip install ipywidgets matplotlib")
1349
+
1350
+ # Get available templates
1351
+ available_templates = self.load_templates()
1352
+
1353
+ # Check what NEURON objects are available
1354
+ import neuron
1355
+ h = neuron.h
1356
+
1357
+ # Create widgets
1358
+ template_dropdown = widgets.Dropdown(
1359
+ options=available_templates,
1360
+ value=available_templates[0] if available_templates else None,
1361
+ description='Template:',
1362
+ style={'description_width': '80px'},
1363
+ layout=widgets.Layout(width='300px')
1364
+ )
1365
+
1366
+ method_dropdown = widgets.Dropdown(
1367
+ options=['passive_properties', 'current_injection', 'fi_curve', 'impedance_amplitude_profile'],
1368
+ value='passive_properties',
1369
+ description='Method:',
1370
+ style={'description_width': '80px'},
1371
+ layout=widgets.Layout(width='300px')
1372
+ )
1373
+
1374
+ # Default values based on method - from basic_settings in single_cell_tuning.ipynb
1375
+ method_defaults = {
1376
+ 'passive_properties': {
1377
+ 'inj_amp': -20.0,
1378
+ 'inj_delay': 1500.0,
1379
+ 'inj_dur': 1000.0,
1380
+ 'tstop': 2500.0,
1381
+ 'tau_method': 'exp2'
1382
+ },
1383
+ 'current_injection': {
1384
+ 'inj_amp': 50.0,
1385
+ 'inj_delay': 1500.0,
1386
+ 'inj_dur': 1000.0,
1387
+ 'tstop': 3000.0
1388
+ },
1389
+ 'fi_curve': {
1390
+ 'i_start': -100.0,
1391
+ 'i_stop': 800.0,
1392
+ 'i_increment': 20.0,
1393
+ 'inj_delay': 1500.0,
1394
+ 'inj_dur': 1000.0
1395
+ },
1396
+ 'impedance_amplitude_profile': {
1397
+ 'inj_amp': 100.0,
1398
+ 'inj_delay': 1000.0,
1399
+ 'inj_dur': 15000.0,
1400
+ 'tstop': 15500.0,
1401
+ 'fstart': 0.0,
1402
+ 'fend': 15.0,
1403
+ 'chirp_type': 'linear'
1404
+ }
1405
+ }
1406
+
1407
+ # Common parameters - always plot results, no need for toggle
1408
+
1409
+ # Method-specific parameters - styled like synapses.py sliders
1410
+ slider_style = {'description_width': 'initial'}
1411
+ slider_layout = None # Use default width for longer sliders
1412
+ text_style = {'description_width': 'initial'}
1413
+ text_layout = widgets.Layout(width='200px')
1414
+
1415
+ # Initialize sliders with default values for passive_properties (initial method)
1416
+ defaults = method_defaults['passive_properties']
1417
+
1418
+ inj_amp_slider = widgets.FloatSlider(value=defaults['inj_amp'], min=-500.0, max=1000.0, step=10.0, description='Injection Amp (pA):', style=slider_style)
1419
+ inj_delay_slider = widgets.FloatSlider(value=defaults['inj_delay'], min=0.0, max=3000.0, step=10.0, description='Injection Delay (ms):', style=slider_style)
1420
+ inj_dur_slider = widgets.FloatSlider(value=defaults['inj_dur'], min=100.0, max=20000.0, step=100.0, description='Injection Duration (ms):', style=slider_style)
1421
+ tstop_slider = widgets.FloatSlider(value=defaults['tstop'], min=500.0, max=25000.0, step=100.0, description='Total Time (ms):', style=slider_style)
1422
+
1423
+ # FI curve specific
1424
+ fi_defaults = method_defaults['fi_curve']
1425
+ i_start_slider = widgets.FloatSlider(value=fi_defaults['i_start'], min=-500.0, max=500.0, step=10.0, description='I Start (pA):', style=slider_style)
1426
+ i_stop_slider = widgets.FloatSlider(value=fi_defaults['i_stop'], min=0.0, max=2000.0, step=50.0, description='I Stop (pA):', style=slider_style)
1427
+ i_increment_slider = widgets.FloatSlider(value=fi_defaults['i_increment'], min=10.0, max=500.0, step=10.0, description='I Increment (pA):', style=slider_style)
1428
+
1429
+ # ZAP specific
1430
+ zap_defaults = method_defaults['impedance_amplitude_profile']
1431
+ fstart_slider = widgets.FloatSlider(value=zap_defaults['fstart'], min=0.0, max=50.0, step=1.0, description='Start Freq (Hz):', style=slider_style)
1432
+ fend_slider = widgets.FloatSlider(value=zap_defaults['fend'], min=1.0, max=100.0, step=1.0, description='End Freq (Hz):', style=slider_style)
1433
+ chirp_dropdown = widgets.Dropdown(options=['linear', 'exponential'], value=zap_defaults['chirp_type'], description='Chirp Type:', style=slider_style)
1434
+
1435
+ # Passive properties specific
1436
+ tau_method_dropdown = widgets.Dropdown(
1437
+ options=['simple', 'exp', 'exp2'],
1438
+ value=defaults['tau_method'],
1439
+ description='Tau Method:',
1440
+ style=slider_style
1441
+ )
1442
+
1443
+ # Sections
1444
+ record_sec_text = widgets.Text(value='soma', description='Record Section:', style=text_style, layout=text_layout)
1445
+ inj_sec_text = widgets.Text(value='soma', description='Injection Section:', style=text_style, layout=text_layout)
1446
+
1447
+ # Post init function
1448
+ post_init_text = widgets.Text(value='', description='Post Init Function:', placeholder='e.g., insert_mechs(123)', style={'description_width': 'initial'}, layout=widgets.Layout(width='300px'))
1449
+
1450
+ run_button = widgets.Button(
1451
+ description='Run Analysis',
1452
+ button_style='primary',
1453
+ icon='play',
1454
+ layout=widgets.Layout(width='140px')
1455
+ )
1456
+
1457
+ reset_button = widgets.Button(
1458
+ description='Reset to Defaults',
1459
+ button_style='warning',
1460
+ icon='refresh',
1461
+ layout=widgets.Layout(width='150px')
1462
+ )
1463
+
1464
+ output_area = widgets.Output(
1465
+ layout=widgets.Layout(border='1px solid #ccc', padding='10px', margin='10px 0 0 0')
1466
+ )
1467
+
1468
+ # Layout containers - organized like synapse tuner
1469
+ # Top row - template and method selection
1470
+ selection_row = widgets.HBox([
1471
+ template_dropdown,
1472
+ method_dropdown
1473
+ ], layout=widgets.Layout(margin='0 0 10px 0'))
1474
+
1475
+ # Button row - main controls
1476
+ button_row = widgets.HBox([
1477
+ run_button,
1478
+ reset_button
1479
+ ], layout=widgets.Layout(margin='0 0 10px 0'))
1480
+
1481
+ # Section row - recording and injection sections
1482
+ section_row = widgets.HBox([
1483
+ record_sec_text,
1484
+ inj_sec_text,
1485
+ post_init_text
1486
+ ], layout=widgets.Layout(margin='0 0 10px 0'))
1487
+
1488
+ # Parameter columns - organized in columns like synapse tuner
1489
+ injection_params_col1 = widgets.VBox([
1490
+ inj_amp_slider,
1491
+ inj_delay_slider
1492
+ ], layout=widgets.Layout(margin='0 10px 0 0'))
1493
+
1494
+ injection_params_col2 = widgets.VBox([
1495
+ inj_dur_slider,
1496
+ tstop_slider
1497
+ ], layout=widgets.Layout(margin='0 0 0 10px'))
1498
+
1499
+ # Passive properties specific columns
1500
+ passive_params_col1 = widgets.VBox([
1501
+ inj_amp_slider,
1502
+ inj_delay_slider
1503
+ ], layout=widgets.Layout(margin='0 10px 0 0'))
1504
+
1505
+ passive_params_col2 = widgets.VBox([
1506
+ inj_dur_slider,
1507
+ tstop_slider,
1508
+ tau_method_dropdown
1509
+ ], layout=widgets.Layout(margin='0 0 0 10px'))
1510
+
1511
+ fi_params_col1 = widgets.VBox([
1512
+ i_start_slider,
1513
+ i_stop_slider
1514
+ ], layout=widgets.Layout(margin='0 10px 0 0'))
1515
+
1516
+ fi_params_col2 = widgets.VBox([
1517
+ i_increment_slider,
1518
+ inj_dur_slider # Use duration for FI curve too
1519
+ ], layout=widgets.Layout(margin='0 0 0 10px'))
1520
+
1521
+ zap_params_col1 = widgets.VBox([
1522
+ inj_amp_slider,
1523
+ inj_delay_slider,
1524
+ inj_dur_slider
1525
+ ], layout=widgets.Layout(margin='0 10px 0 0'))
1526
+
1527
+ zap_params_col2 = widgets.VBox([
1528
+ tstop_slider,
1529
+ fstart_slider,
1530
+ fend_slider,
1531
+ chirp_dropdown
1532
+ ], layout=widgets.Layout(margin='0 0 0 10px'))
1533
+
1534
+ # Function to update slider values based on method defaults
1535
+ def update_slider_values(method):
1536
+ """Update slider values to match the defaults for the selected method"""
1537
+ if method in method_defaults:
1538
+ defaults = method_defaults[method]
1539
+
1540
+ # Update common sliders if they exist in defaults
1541
+ if 'inj_amp' in defaults:
1542
+ inj_amp_slider.value = defaults['inj_amp']
1543
+ if 'inj_delay' in defaults:
1544
+ inj_delay_slider.value = defaults['inj_delay']
1545
+ if 'inj_dur' in defaults:
1546
+ inj_dur_slider.value = defaults['inj_dur']
1547
+ if 'tstop' in defaults:
1548
+ tstop_slider.value = defaults['tstop']
1549
+
1550
+ # Update method-specific sliders
1551
+ if method == 'fi_curve':
1552
+ if 'i_start' in defaults:
1553
+ i_start_slider.value = defaults['i_start']
1554
+ if 'i_stop' in defaults:
1555
+ i_stop_slider.value = defaults['i_stop']
1556
+ if 'i_increment' in defaults:
1557
+ i_increment_slider.value = defaults['i_increment']
1558
+
1559
+ elif method == 'impedance_amplitude_profile':
1560
+ if 'fstart' in defaults:
1561
+ fstart_slider.value = defaults['fstart']
1562
+ if 'fend' in defaults:
1563
+ fend_slider.value = defaults['fend']
1564
+ if 'chirp_type' in defaults:
1565
+ chirp_dropdown.value = defaults['chirp_type']
1566
+
1567
+ elif method == 'passive_properties':
1568
+ if 'tau_method' in defaults:
1569
+ tau_method_dropdown.value = defaults['tau_method']
1570
+
1571
+
1572
+ # Function to update parameter visibility based on selected method
1573
+ def update_params(*args):
1574
+ method = method_dropdown.value
1575
+
1576
+ # Update slider values to defaults for the selected method
1577
+ update_slider_values(method)
1578
+
1579
+ # Update parameter column visibility
1580
+ if method == 'passive_properties':
1581
+ param_columns.children = [widgets.HBox([passive_params_col1, passive_params_col2])]
1582
+ elif method == 'current_injection':
1583
+ param_columns.children = [widgets.HBox([injection_params_col1, injection_params_col2])]
1584
+ elif method == 'fi_curve':
1585
+ param_columns.children = [widgets.HBox([fi_params_col1, fi_params_col2])]
1586
+ elif method == 'impedance_amplitude_profile':
1587
+ param_columns.children = [widgets.HBox([zap_params_col1, zap_params_col2])]
1588
+
1589
+ method_dropdown.observe(update_params, 'value')
1590
+
1591
+ # Initialize parameter columns container
1592
+ param_columns = widgets.VBox([widgets.HBox([passive_params_col1, passive_params_col2])])
1593
+
1594
+ # Run function
1595
+ def run_analysis(b):
1596
+ output_area.clear_output() # Clear immediately on click
1597
+ with output_area:
1598
+
1599
+ template = template_dropdown.value
1600
+ method = method_dropdown.value
1601
+ record_sec = record_sec_text.value
1602
+ inj_sec = inj_sec_text.value
1603
+ post_init = post_init_text.value if post_init_text.value else None
1604
+
1605
+ kwargs = {
1606
+ 'record_sec': record_sec,
1607
+ 'inj_sec': inj_sec,
1608
+ 'plot': True # Always plot results
1609
+ }
1610
+
1611
+ if post_init:
1612
+ kwargs['post_init_function'] = post_init
1613
+
1614
+ # Add method-specific parameters
1615
+ if method == 'passive_properties':
1616
+ kwargs.update({
1617
+ 'inj_amp': inj_amp_slider.value,
1618
+ 'inj_delay': inj_delay_slider.value,
1619
+ 'inj_dur': inj_dur_slider.value,
1620
+ 'tstop': tstop_slider.value,
1621
+ 'method': tau_method_dropdown.value
1622
+ })
1623
+ elif method == 'current_injection':
1624
+ kwargs.update({
1625
+ 'inj_amp': inj_amp_slider.value,
1626
+ 'inj_delay': inj_delay_slider.value,
1627
+ 'inj_dur': inj_dur_slider.value,
1628
+ 'tstop': tstop_slider.value
1629
+ })
1630
+ elif method == 'fi_curve':
1631
+ kwargs.update({
1632
+ 'i_start': i_start_slider.value,
1633
+ 'i_stop': i_stop_slider.value,
1634
+ 'i_increment': i_increment_slider.value,
1635
+ 'tstart': inj_delay_slider.value,
1636
+ 'tdur': inj_dur_slider.value
1637
+ })
1638
+ elif method == 'impedance_amplitude_profile':
1639
+ kwargs.update({
1640
+ 'inj_amp': inj_amp_slider.value,
1641
+ 'inj_delay': inj_delay_slider.value,
1642
+ 'inj_dur': inj_dur_slider.value,
1643
+ 'tstop': tstop_slider.value,
1644
+ 'fstart': fstart_slider.value,
1645
+ 'fend': fend_slider.value,
1646
+ 'chirp_type': chirp_dropdown.value
1647
+ })
1648
+
1649
+ print("="*60)
1650
+ print(f"Running {method} for template: {template}")
1651
+ print("="*60)
1652
+ print("Parameters:")
1653
+ for key, value in kwargs.items():
1654
+ print(f" {key}: {value}")
1655
+ print("-"*60)
1656
+
1657
+ try:
1658
+ if method == 'passive_properties':
1659
+ result = self.passive_properties(template, **kwargs)
1660
+
1661
+ elif method == 'current_injection':
1662
+ result = self.current_injection(template, **kwargs)
1663
+ elif method == 'fi_curve':
1664
+ result = self.fi_curve(template, **kwargs)
1665
+ elif method == 'impedance_amplitude_profile':
1666
+ result = self.impedance_amplitude_profile(template, **kwargs)
1667
+
1668
+
1669
+ except Exception as e:
1670
+ print("="*60)
1671
+ print(f"✗ Error running analysis: {e}")
1672
+ print("="*60)
1673
+ import traceback
1674
+ traceback.print_exc()
1675
+
1676
+ # Reset function
1677
+ def reset_to_defaults(b):
1678
+ output_area.clear_output() # Clear immediately on click
1679
+ with output_area:
1680
+ method = method_dropdown.value
1681
+ update_slider_values(method)
1682
+ print(f"Reset all parameters to defaults for {method}")
1683
+
1684
+ run_button.on_click(run_analysis)
1685
+ reset_button.on_click(reset_to_defaults)
1686
+
1687
+ # Create main UI layout - matching synapse tuner structure
1688
+ ui = widgets.VBox([
1689
+ selection_row,
1690
+ button_row,
1691
+ section_row,
1692
+ param_columns
1693
+ ], layout=widgets.Layout(padding='10px'))
1694
+
1695
+ # Display the interface - UI on top, output below (like synapse tuner)
1696
+ display(ui)
1697
+ display(output_area)
1698
+
1699
+ # Initial update
1700
+ update_params()
1701
+
1304
1702
 
1305
1703
  # Example usage
1306
1704
  # profiler = Profiler('./temp/templates', './temp/mechanisms/modfiles')