dendrotweaks 0.3.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.
Files changed (56) hide show
  1. dendrotweaks/__init__.py +10 -0
  2. dendrotweaks/analysis/__init__.py +11 -0
  3. dendrotweaks/analysis/ephys_analysis.py +482 -0
  4. dendrotweaks/analysis/morphometric_analysis.py +106 -0
  5. dendrotweaks/membrane/__init__.py +6 -0
  6. dendrotweaks/membrane/default_mod/AMPA.mod +65 -0
  7. dendrotweaks/membrane/default_mod/AMPA_NMDA.mod +100 -0
  8. dendrotweaks/membrane/default_mod/CaDyn.mod +54 -0
  9. dendrotweaks/membrane/default_mod/GABAa.mod +65 -0
  10. dendrotweaks/membrane/default_mod/Leak.mod +27 -0
  11. dendrotweaks/membrane/default_mod/NMDA.mod +72 -0
  12. dendrotweaks/membrane/default_mod/vecstim.mod +76 -0
  13. dendrotweaks/membrane/default_templates/NEURON_template.py +354 -0
  14. dendrotweaks/membrane/default_templates/default.py +73 -0
  15. dendrotweaks/membrane/default_templates/standard_channel.mod +87 -0
  16. dendrotweaks/membrane/default_templates/template_jaxley.py +108 -0
  17. dendrotweaks/membrane/default_templates/template_jaxley_new.py +108 -0
  18. dendrotweaks/membrane/distributions.py +324 -0
  19. dendrotweaks/membrane/groups.py +103 -0
  20. dendrotweaks/membrane/io/__init__.py +11 -0
  21. dendrotweaks/membrane/io/ast.py +201 -0
  22. dendrotweaks/membrane/io/code_generators.py +312 -0
  23. dendrotweaks/membrane/io/converter.py +108 -0
  24. dendrotweaks/membrane/io/factories.py +144 -0
  25. dendrotweaks/membrane/io/grammar.py +417 -0
  26. dendrotweaks/membrane/io/loader.py +90 -0
  27. dendrotweaks/membrane/io/parser.py +499 -0
  28. dendrotweaks/membrane/io/reader.py +212 -0
  29. dendrotweaks/membrane/mechanisms.py +574 -0
  30. dendrotweaks/model.py +1916 -0
  31. dendrotweaks/model_io.py +75 -0
  32. dendrotweaks/morphology/__init__.py +5 -0
  33. dendrotweaks/morphology/domains.py +100 -0
  34. dendrotweaks/morphology/io/__init__.py +5 -0
  35. dendrotweaks/morphology/io/factories.py +212 -0
  36. dendrotweaks/morphology/io/reader.py +66 -0
  37. dendrotweaks/morphology/io/validation.py +212 -0
  38. dendrotweaks/morphology/point_trees.py +681 -0
  39. dendrotweaks/morphology/reduce/__init__.py +16 -0
  40. dendrotweaks/morphology/reduce/reduce.py +155 -0
  41. dendrotweaks/morphology/reduce/reduced_cylinder.py +129 -0
  42. dendrotweaks/morphology/sec_trees.py +1112 -0
  43. dendrotweaks/morphology/seg_trees.py +157 -0
  44. dendrotweaks/morphology/trees.py +567 -0
  45. dendrotweaks/path_manager.py +261 -0
  46. dendrotweaks/simulators.py +235 -0
  47. dendrotweaks/stimuli/__init__.py +3 -0
  48. dendrotweaks/stimuli/iclamps.py +73 -0
  49. dendrotweaks/stimuli/populations.py +265 -0
  50. dendrotweaks/stimuli/synapses.py +203 -0
  51. dendrotweaks/utils.py +239 -0
  52. dendrotweaks-0.3.1.dist-info/METADATA +70 -0
  53. dendrotweaks-0.3.1.dist-info/RECORD +56 -0
  54. dendrotweaks-0.3.1.dist-info/WHEEL +5 -0
  55. dendrotweaks-0.3.1.dist-info/licenses/LICENSE +674 -0
  56. dendrotweaks-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ __version__ = "0.3.1"
2
+
3
+ from dendrotweaks.model import Model
4
+ from dendrotweaks.simulators import NEURONSimulator
5
+ from dendrotweaks.membrane.distributions import Distribution
6
+ from dendrotweaks.path_manager import PathManager
7
+ from dendrotweaks.stimuli import Synapse, Population, IClamp
8
+
9
+ from dendrotweaks.utils import download_example_data
10
+ from dendrotweaks.utils import apply_dark_theme
@@ -0,0 +1,11 @@
1
+
2
+ from dendrotweaks.analysis.morphometric_analysis import calculate_domain_statistics
3
+ from dendrotweaks.analysis.morphometric_analysis import calculate_cell_statistics
4
+ from dendrotweaks.analysis.ephys_analysis import calculate_fI_curve, detect_somatic_spikes
5
+ from dendrotweaks.analysis.ephys_analysis import plot_fI_curve, plot_somatic_spikes
6
+ from dendrotweaks.analysis.ephys_analysis import calculate_time_constant, calculate_input_resistance, calculate_passive_properties
7
+ from dendrotweaks.analysis.ephys_analysis import plot_passive_properties
8
+ from dendrotweaks.analysis.ephys_analysis import calculate_voltage_attenuation
9
+ from dendrotweaks.analysis.ephys_analysis import plot_voltage_attenuation
10
+ from dendrotweaks.analysis.ephys_analysis import calculate_dendritic_nonlinearity
11
+ from dendrotweaks.analysis.ephys_analysis import plot_dendritic_nonlinearity
@@ -0,0 +1,482 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+
5
+ from scipy.signal import find_peaks, peak_widths
6
+ from scipy.optimize import curve_fit
7
+
8
+
9
+ # =============================================================================
10
+ # PASSIVE PROPERTIES
11
+ # =============================================================================
12
+
13
+ def get_somatic_data(model):
14
+ """
15
+ Get the somatic voltage, time, time step, and injected current.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Model
20
+ The neuron model.
21
+
22
+ Returns
23
+ -------
24
+ tuple
25
+ A tuple containing the voltage, time, time step, and injected current.
26
+ """
27
+ seg = model.seg_tree.root
28
+ iclamp = model.iclamps[seg]
29
+
30
+ v = np.array(model.simulator.vs[seg])
31
+ t = np.array(model.simulator.t)
32
+ dt = model.simulator.dt
33
+
34
+ return v, t, dt, iclamp
35
+
36
+
37
+ def calculate_input_resistance(model):
38
+ """
39
+ Calculate the input resistance of the neuron model.
40
+
41
+ Parameters
42
+ ----------
43
+ model : Model
44
+ The neuron model.
45
+
46
+ Returns
47
+ -------
48
+ dict
49
+ A dictionary containing the onset and offset voltages, the input resistance, and the injected current.
50
+ """
51
+
52
+ v, t, dt, iclamp = get_somatic_data(model)
53
+
54
+ v_min = np.min(v)
55
+
56
+ amp = iclamp.amp
57
+ start_ts = iclamp.delay / dt
58
+ end_ts = int((iclamp.delay + iclamp.dur) / dt)
59
+ v_onset = v[int(start_ts)]
60
+ v_offset = v[int(end_ts)]
61
+
62
+ R_in = (v_onset - v_offset) / amp
63
+ print(f"Input resistance: {R_in:.2f} MOhm")
64
+
65
+ return {
66
+ 'onset_voltage': v_onset,
67
+ 'offset_voltage': v_offset,
68
+ 'input_resistance': R_in,
69
+ 'current_amplitude': amp
70
+ }
71
+
72
+
73
+ def _exp_decay(t, A, tau):
74
+ return A * np.exp(-t / tau)
75
+
76
+
77
+ def calculate_time_constant(model):
78
+ """
79
+ Calculate the membrane time constant of the neuron model.
80
+
81
+ Parameters
82
+ ----------
83
+ model : Model
84
+ The neuron model.
85
+
86
+ Returns
87
+ -------
88
+ dict
89
+ A dictionary containing the time constant and the exponential fit.
90
+ """
91
+ v, t, dt, iclamp = get_somatic_data(model)
92
+
93
+ start_ts = int(iclamp.delay / dt)
94
+ stop_ts = int((iclamp.delay + iclamp.dur) / dt)
95
+ min_ts = np.argmin(v[start_ts:stop_ts]) + start_ts
96
+ v_min = np.min(v[start_ts: min_ts])
97
+ v_decay = v[start_ts: min_ts] - v_min
98
+ t_decay = t[start_ts: min_ts] - t[start_ts]
99
+ popt, _ = curve_fit(_exp_decay, t_decay, v_decay, p0=[1, 100])
100
+ tau = popt[1]
101
+ A = popt[0]
102
+ print(f"Membrane time constant: {tau:.2f} ms")
103
+ return {
104
+ 'time_constant': tau,
105
+ 'A': A,
106
+ 'start_time': start_ts * dt,
107
+ 'decay_time': t_decay,
108
+ 'decay_voltage': v_decay
109
+ }
110
+
111
+ def calculate_passive_properties(model):
112
+ """
113
+ Calculate the passive properties of the neuron model.
114
+
115
+ Parameters
116
+ ----------
117
+ model : Model
118
+ The neuron model.
119
+
120
+ Returns
121
+ -------
122
+ dict
123
+ A dictionary containing the input resistance, time constant, and the exponential fit.
124
+ """
125
+
126
+ data_rin = calculate_input_resistance(model)
127
+ data_tau = calculate_time_constant(model)
128
+
129
+ return {**data_rin, **data_tau}
130
+
131
+ def plot_passive_properties(data, ax=None):
132
+
133
+ if ax is None:
134
+ _, ax = plt.subplots()
135
+
136
+ R_in = data['input_resistance']
137
+ tau = data['time_constant']
138
+ v_onset = data['onset_voltage']
139
+ v_offset = data['offset_voltage']
140
+ t_decay = data['decay_time']
141
+ v_decay = data['decay_voltage']
142
+ A = data['A']
143
+ start_t = data['start_time']
144
+
145
+ ax.set_title(f"R_in: {R_in:.2f} MOhm, Tau: {tau:.2f} ms")
146
+ ax.axhline(v_onset, color='gray', linestyle='--', label='V onset')
147
+ ax.axhline(v_offset, color='gray', linestyle='--', label='V offset')
148
+
149
+ # Shift the exp_decay output along the y-axis
150
+ shifted_exp_decay = _exp_decay(t_decay, A, tau) + v_offset
151
+ ax.plot(t_decay + start_t, shifted_exp_decay, color='red', label='Exp. fit', linestyle='--')
152
+ ax.legend()
153
+
154
+
155
+ # =============================================================================
156
+ # ACTIVE PROPERTIES
157
+ # =============================================================================
158
+
159
+ def detect_somatic_spikes(model, **kwargs):
160
+ """
161
+ Detect somatic spikes in the model and calculate spike amplitudes and widths.
162
+
163
+ Returns:
164
+ dict: A dictionary containing spike metrics.
165
+ """
166
+ seg = model.seg_tree.root
167
+
168
+ v = np.array(model.simulator.vs[seg])
169
+ t = np.array(model.simulator.t)
170
+ dt = model.simulator.dt
171
+
172
+ baseline = np.median(v)
173
+ height = kwargs.get('height', baseline)
174
+ distance = kwargs.get('distance', int(2/dt))
175
+ prominence = kwargs.get('prominence', 50)
176
+ wlen = kwargs.get('wlen', int(20/dt))
177
+
178
+ peaks, properties = find_peaks(v, height=height, distance=distance, prominence=prominence, wlen=wlen)
179
+ half_widths, _, left_bases, right_bases = peak_widths(v, peaks, rel_height=0.5)
180
+ half_widths *= dt
181
+ left_bases *= dt
182
+ right_bases *= dt
183
+
184
+ return {
185
+ 'spike_times': t[peaks],
186
+ 'spike_values': properties['peak_heights'],
187
+ 'half_widths': half_widths,
188
+ 'amplitudes': properties['prominences'],
189
+ 'left_bases': left_bases,
190
+ 'right_bases': right_bases,
191
+ 'stimulus_duration': model.iclamps[seg].dur
192
+ }
193
+
194
+
195
+ def plot_somatic_spikes(data, ax=None, show_metrics=False):
196
+ """Plot detected spikes on the provided axis or create a new figure.
197
+
198
+ Args:
199
+ model: The neuron model
200
+ ax: Optional matplotlib axis for plotting
201
+
202
+ Returns:
203
+ matplotlib.axes.Axes: The plot axis
204
+ """
205
+
206
+ spike_times = data['spike_times']
207
+ spike_values = data['spike_values']
208
+ half_widths = data['half_widths']
209
+ amplitudes = data['amplitudes']
210
+ right_bases = data['right_bases']
211
+ left_bases = data['left_bases']
212
+ duration_ms = data['stimulus_duration']
213
+
214
+ n_spikes = len(spike_times)
215
+
216
+ if n_spikes == 0:
217
+ return
218
+
219
+ print(f"Detected {n_spikes} spikes")
220
+ print(f"Average spike half-width: {np.mean(half_widths):.2f} ms")
221
+ print(f"Average spike amplitude: {np.mean(amplitudes):.2f} mV")
222
+ print(f"Spike frequency: {n_spikes / duration_ms * 1000:.2f} Hz")
223
+
224
+ ax.plot(spike_times, spike_values, 'o', color='red')
225
+ ax.set_xlabel('Time (ms)')
226
+ ax.set_ylabel('Amplitude (mV)')
227
+ ax.set_title(f'Somatic spikes ({len(spike_times)} detected)')
228
+
229
+ if show_metrics:
230
+ for t, v, w, a, lb, rb in zip(spike_times, spike_values, half_widths, amplitudes, left_bases, right_bases):
231
+ # plot spike amplitude
232
+ ax.plot([t, t], [v, v - a], color='red', linestyle='--')
233
+ # plot spike width
234
+ ax.plot([t - 10*w/2, t + 10*w/2], [v - a/2, v - a/2], color='lawngreen', linestyle='--')
235
+
236
+
237
+ def calculate_fI_curve(model, duration=1000, min_amp=0, max_amp=1, n=5, **kwargs):
238
+ """
239
+ Calculate the frequency-current (f-I) curve of the neuron model.
240
+
241
+ Parameters
242
+ ----------
243
+ model : Model
244
+ The neuron model.
245
+ duration : int
246
+ Duration of the simulation in ms.
247
+ min_amp : float
248
+ Minimum amplitude of the current injection in nA.
249
+ max_amp : float
250
+ Maximum amplitude of the current injection in nA.
251
+ n : int
252
+ Number of amplitudes to test.
253
+
254
+ Returns
255
+ -------
256
+ dict
257
+ A dictionary containing the current amplitudes, firing rates, and voltages.
258
+ """
259
+
260
+ seg = model.seg_tree.root
261
+ duration = duration
262
+
263
+ amps = np.round(np.linspace(min_amp, max_amp, n), 4)
264
+ iclamp = model.iclamps[seg]
265
+ rates = []
266
+ vs = {}
267
+ for amp in amps:
268
+ iclamp.amp = amp
269
+ model.simulator.run(duration)
270
+ spike_data = detect_somatic_spikes(model, **kwargs)
271
+ n_spikes = len(spike_data['spike_times'])
272
+ rate = n_spikes / iclamp.dur * 1000
273
+ rates.append(rate)
274
+ vs[amp] = model.simulator.vs[seg]
275
+
276
+ return {
277
+ 'current_amplitudes': amps,
278
+ 'firing_rates': rates,
279
+ 'voltages': vs,
280
+ 'time': model.simulator.t
281
+ }
282
+
283
+
284
+ def plot_fI_curve(data, ax=None, **kwargs):
285
+
286
+ if ax is None:
287
+ _, ax = plt.subplots(1, 2, figsize=(5, 5))
288
+
289
+ amps = data['current_amplitudes']
290
+ rates = data['firing_rates']
291
+ vs = data['voltages']
292
+ t = data['time']
293
+
294
+ for i, (amp, v) in enumerate(vs.items()):
295
+ ax[0].plot(t, np.array(v) - i*200, label=f'{amp} nA')
296
+ # ax[0].set_xlabel('Time (ms)')
297
+ # ax[0].set_ylabel('Voltage (mV)')
298
+ ax[0].set_title('Somatic spikes')
299
+ ax[0].legend()
300
+ ax[0].spines['top'].set_visible(False)
301
+ ax[0].spines['right'].set_visible(False)
302
+ ax[0].spines['bottom'].set_visible(False)
303
+ ax[0].spines['left'].set_visible(False)
304
+ ax[0].set_xticks([])
305
+ ax[0].set_yticks([])
306
+
307
+ ax[1].plot(amps, rates, color='gray', zorder=0)
308
+ for a, r in zip(amps, rates):
309
+ ax[1].scatter(a, r, s=50, edgecolor='white')
310
+ ax[1].set_xlabel('Current (nA)')
311
+ ax[1].set_ylabel('Firing rate (Hz)')
312
+ ax[1].set_title('f-I curve')
313
+
314
+
315
+ # =============================================================================
316
+ # DENDRITIC PROPERTIES
317
+ # =============================================================================
318
+
319
+ def calculate_voltage_attenuation(model):
320
+ """
321
+ Calculate the voltage attenuation along the dendrites.
322
+
323
+ Parameters
324
+ ----------
325
+ model : Model
326
+ The neuron model.
327
+
328
+ Returns
329
+ -------
330
+ dict
331
+ A dictionary containing the path distances, minimum voltages, and voltage attenuations
332
+ """
333
+
334
+ # Assuming one stimulation site and multiple recording sites including the stimulated site
335
+ stimulated_segs = list(model.iclamps.keys())
336
+ if len(stimulated_segs) != 1:
337
+ print("Only one stimulation site is supported")
338
+ return None
339
+ recorded_segs = list(model.recordings.keys())
340
+ if len(recorded_segs) < 2:
341
+ print("At least two recording sites are required")
342
+ return None
343
+
344
+ stimulated_seg = stimulated_segs[0]
345
+
346
+ iclamp = model.iclamps[stimulated_seg]
347
+ amp = iclamp.amp
348
+
349
+ if amp >= 0:
350
+ print("Stimulus amplitude must be negative")
351
+ return None
352
+
353
+ path_distances = [seg.path_distance() for seg in recorded_segs]
354
+
355
+ start_ts = int(iclamp.delay / model.simulator.dt)
356
+ stop_ts = int((iclamp.delay + iclamp.dur) / model.simulator.dt)
357
+
358
+ voltage_at_stimulated = np.array(model.simulator.vs[stimulated_seg])[start_ts:stop_ts]
359
+ voltages = [np.array(model.simulator.vs[seg])[start_ts:stop_ts] for seg in recorded_segs]
360
+
361
+ # Calculate voltage displacement from the resting potential
362
+ delta_v_at_stimulated = voltage_at_stimulated[0] - np.min(voltage_at_stimulated)
363
+ delta_vs = [v[0] - np.min(v) for v in voltages]
364
+
365
+ min_voltages = [np.min(v) for v in voltages]
366
+
367
+ attenuation = [dv / delta_v_at_stimulated for dv in delta_vs]
368
+
369
+ return {
370
+ 'path_distances': path_distances,
371
+ 'min_voltages': min_voltages,
372
+ 'attenuation': attenuation
373
+ }
374
+
375
+
376
+ def plot_voltage_attenuation(data, ax=None):
377
+
378
+ path_distances = data['path_distances']
379
+ attenuation = data['attenuation']
380
+
381
+ if ax is None:
382
+ _, ax = plt.subplots()
383
+
384
+ ax.plot(path_distances, attenuation, 'o-')
385
+ ax.set_ylim(-0.1, 1.1)
386
+ ax.set_xlabel('Path distance (um)')
387
+ ax.set_ylabel('Voltage attenuation')
388
+ ax.set_title('Voltage attenuation')
389
+
390
+ def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=None):
391
+ """Calculate the expected and observed voltage changes for a range of synaptic weights.
392
+
393
+ Parameters
394
+ ----------
395
+ model : Model
396
+ The neuron model.
397
+ duration : int
398
+ Duration of the simulation in ms.
399
+ max_weight : int
400
+ Maximum synaptic weight to test.
401
+
402
+ Returns
403
+ -------
404
+ dict
405
+ A dictionary containing the expected and observed voltage changes.
406
+ """
407
+
408
+ recorded_segs = list(model.recordings.keys())
409
+ seg = recorded_segs[0]
410
+
411
+ populations = [pop for pops in model.populations.values() for pop in pops.values()]
412
+ if len(populations) != 1:
413
+ print("Only one population is supported")
414
+ return None
415
+ population = populations[0]
416
+ if population.N != 1:
417
+ print("Only one synapse should be placed on the dendrite")
418
+ return None
419
+
420
+ start_ts = int(population.input_params['start'] / model.simulator.dt)
421
+
422
+ vs = {}
423
+ delta_vs = []
424
+ min_weight = 1
425
+ if max_weight is None or min_weight is None or n is None:
426
+ max_weight = population.input_params['weight']
427
+ n = max_weight + 1
428
+
429
+ weights = np.linspace(min_weight, max_weight, n, dtype=int)
430
+ weights = np.unique(weights)
431
+
432
+ for w in weights:
433
+ population.update_input_params(weight=w)
434
+ model.simulator.run(duration)
435
+ v = np.array(model.simulator.vs[seg])
436
+ v_start = v[start_ts]
437
+ v_max = np.max(v[start_ts:])
438
+ delta_v = v_max - v_start
439
+ delta_vs.append(delta_v)
440
+ vs[w] = v
441
+ unitary_delta_v = delta_vs[0]
442
+ expected_delta_vs = [w * unitary_delta_v for w in weights]
443
+
444
+ return {
445
+ 'expected_response': expected_delta_vs,
446
+ 'observed_response': delta_vs,
447
+ 'voltages': vs,
448
+ 'weights': weights,
449
+ 'time': model.simulator.t
450
+ }
451
+
452
+
453
+ def plot_dendritic_nonlinearity(data, ax=None, **kwargs):
454
+
455
+ if ax is None:
456
+ _, ax = plt.subplots(1, 2, figsize=(10, 5))
457
+
458
+ expected_delta_vs = data['expected_response']
459
+ delta_vs = data['observed_response']
460
+ vs = data['voltages']
461
+ t = data['time']
462
+
463
+ for i, (weight, v) in enumerate(vs.items()):
464
+ ax[0].plot(t, np.array(v) - i*200, label=f'{weight} synapses')
465
+ ax[0].set_title('Voltage traces')
466
+ ax[0].legend()
467
+ ax[0].spines['top'].set_visible(False)
468
+ ax[0].spines['right'].set_visible(False)
469
+ ax[0].spines['bottom'].set_visible(False)
470
+ ax[0].spines['left'].set_visible(False)
471
+ ax[0].set_xticks([])
472
+ ax[0].set_yticks([])
473
+
474
+ ax[1].plot(expected_delta_vs, delta_vs, 'o-')
475
+ ax[1].plot(expected_delta_vs, expected_delta_vs, color='gray', linestyle='--')
476
+ ax[1].set_xlabel('Expected voltage change (mV)')
477
+ ax[1].set_ylabel('Observed voltage change (mV)')
478
+ ax[1].set_title('Dendritic nonlinearity')
479
+
480
+
481
+
482
+
@@ -0,0 +1,106 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+
5
+ def get_node_data(nodes):
6
+
7
+ data = {
8
+ 'idx': [node.idx for node in nodes],
9
+ 'depth': [node.depth for node in nodes],
10
+ 'length': [node.length for node in nodes],
11
+ 'diam': [node.diam for node in nodes],
12
+ 'area': [node.area for node in nodes],
13
+ 'n_children': [len(node.children) for node in nodes]
14
+ }
15
+
16
+ return pd.DataFrame(data)
17
+
18
+
19
+ def calculate_section_statistics(sections, param_name=None):
20
+
21
+ df = get_node_data(sections)
22
+
23
+ depth_counts = df['depth'].value_counts().sort_index()
24
+ stats = {
25
+ 'N_sections': len(df),
26
+ 'N_bifurcations': (df['n_children'] == 2).sum(),
27
+ 'N_terminations': (df['n_children'] == 0).sum(),
28
+ 'depth': {
29
+ 'min': df['depth'].min(),
30
+ 'max': df['depth'].max(),
31
+ 'counts': depth_counts.to_dict(),
32
+ },
33
+ 'diam': {
34
+ 'min': np.round(df['diam'].min(), 2),
35
+ 'max': np.round(df['diam'].max(), 2),
36
+ 'mean': np.round(df['diam'].mean(), 2),
37
+ 'std': np.round(df['diam'].std(), 2)
38
+ },
39
+ 'length': {
40
+ 'min': np.round(df['length'].min(), 2),
41
+ 'max': np.round(df['length'].max(), 2),
42
+ 'mean': np.round(df['length'].mean(), 2),
43
+ 'std': np.round(df['length'].std(), 2)
44
+ },
45
+ 'area': {
46
+ 'min': np.round(df['area'].min(), 2),
47
+ 'max': np.round(df['area'].max(), 2),
48
+ 'mean': np.round(df['area'].mean(), 2),
49
+ 'std': np.round(df['area'].std(), 2)
50
+ },
51
+ 'total_length': np.round(df['length'].sum(), 2),
52
+ 'total_area': np.round(df['area'].sum(), 2)
53
+ }
54
+
55
+ return stats
56
+
57
+
58
+ def calculate_cell_statistics(tree):
59
+
60
+ all_sections = []
61
+ for domain in tree.domains.values():
62
+ all_sections.extend(domain.sections)
63
+
64
+ return calculate_section_statistics(all_sections)
65
+
66
+
67
+ def calculate_domain_statistics(tree, domain_names=None, param_name=None):
68
+
69
+ if domain_names is None:
70
+ return calculate_cell_statistics(tree)
71
+ if not isinstance(domain_names, list):
72
+ raise ValueError("domain_names must be a list of strings")
73
+
74
+ domains = [domain for domain in tree.domains.values() if domain.name in domain_names]
75
+
76
+ stats = {}
77
+
78
+ for domain in domains:
79
+ stats[domain.name] = calculate_section_statistics(domain.sections)
80
+
81
+ return stats
82
+
83
+
84
+ def calculate_segment_statistics(model, segments):
85
+
86
+ df = model.get_node_data(segments)
87
+
88
+ stats = {
89
+ 'N_segments': len(df),
90
+ 'N_bifurcations': (df['n_children'] == 2).sum(),
91
+ 'N_terminations': (df['n_children'] == 0).sum(),
92
+ 'diam': (np.round(df['diam'].mean(), 2), np.round(df['diam'].std(), 2), np.round(df['diam'].min(), 2), np.round(df['diam'].max(), 2)),
93
+ 'length': (np.round(df['length'].mean(), 2), np.round(df['length'].std(), 2), np.round(df['length'].min(), 2), np.round(df['length'].max(), 2)),
94
+ 'area': (np.round(df['area'].mean(), 2), np.round(df['area'].std(), 2), np.round(df['area'].min(), 2), np.round(df['area'].max(), 2)),
95
+ 'total_lenght': np.round(df['length'].sum(), 2),
96
+ 'total_area': np.round(df['area'].sum(), 2)
97
+ }
98
+
99
+
100
+ def update_histogram(model, param_name, segments, **kwargs):
101
+ if param not in ['diam', 'length', 'area']:
102
+ raise ValueError(f"Invalid parameter: {param}")
103
+ values = [seg.get_param_value(param_name) for seg in segments]
104
+ hist, edges = np.histogram(values, **kwargs)
105
+ return hist, edges
106
+
@@ -0,0 +1,6 @@
1
+ from dendrotweaks.membrane.mechanisms import Mechanism, IonChannel, CaDynamics, StandardIonChannel, LeakChannel
2
+ from dendrotweaks.membrane.mechanisms import LeakChannel
3
+ from dendrotweaks.membrane.groups import SegmentGroup
4
+ from dendrotweaks.membrane.distributions import Distribution
5
+
6
+ import dendrotweaks.membrane.io as io
@@ -0,0 +1,65 @@
1
+ TITLE AMPA synapse
2
+
3
+ COMMENT
4
+ Custom AMPA synapse model developed for DendroTweaks
5
+ ENDCOMMENT
6
+
7
+ NEURON {
8
+ POINT_PROCESS AMPA
9
+ NONSPECIFIC_CURRENT i
10
+ RANGE gmax
11
+ RANGE tau_rise, tau_decay
12
+ RANGE i, g, e
13
+ }
14
+
15
+ UNITS {
16
+ (nA) = (nanoamp)
17
+ (mV) = (millivolt)
18
+ (uS) = (microsiemens)
19
+ }
20
+
21
+ PARAMETER {
22
+ gmax = 0 (uS)
23
+ e = 0 (mV)
24
+ tau_rise = 0.1 (ms)
25
+ tau_decay = 2.5 (ms)
26
+ }
27
+
28
+ ASSIGNED {
29
+ v (mV)
30
+ i (nA)
31
+ g (uS)
32
+ factor (1)
33
+ }
34
+
35
+ STATE {
36
+ A (1)
37
+ B (1)
38
+ }
39
+
40
+ BREAKPOINT {
41
+ SOLVE state METHOD cnexp
42
+ g = gmax * (B - A)
43
+ i = g * (v - e)
44
+ }
45
+
46
+ DERIVATIVE state {
47
+ A' = -A / tau_rise
48
+ B' = -B / tau_decay
49
+ }
50
+
51
+ INITIAL {
52
+ LOCAL tp
53
+ A = 0
54
+ B = 0
55
+
56
+ tp = (tau_rise * tau_decay) / (tau_decay - tau_rise) * log(tau_decay / tau_rise)
57
+ factor = -exp(-tp / tau_rise) + exp(-tp / tau_decay)
58
+ factor = 1 / factor
59
+
60
+ }
61
+
62
+ NET_RECEIVE(weight (1)) {
63
+ A = A + weight * factor
64
+ B = B + weight * factor
65
+ }