bmtool 0.7.6__py3-none-any.whl → 0.7.8__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 bmtool might be problematic. Click here for more details.

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