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.
- dendrotweaks/__init__.py +10 -0
- dendrotweaks/analysis/__init__.py +11 -0
- dendrotweaks/analysis/ephys_analysis.py +482 -0
- dendrotweaks/analysis/morphometric_analysis.py +106 -0
- dendrotweaks/membrane/__init__.py +6 -0
- dendrotweaks/membrane/default_mod/AMPA.mod +65 -0
- dendrotweaks/membrane/default_mod/AMPA_NMDA.mod +100 -0
- dendrotweaks/membrane/default_mod/CaDyn.mod +54 -0
- dendrotweaks/membrane/default_mod/GABAa.mod +65 -0
- dendrotweaks/membrane/default_mod/Leak.mod +27 -0
- dendrotweaks/membrane/default_mod/NMDA.mod +72 -0
- dendrotweaks/membrane/default_mod/vecstim.mod +76 -0
- dendrotweaks/membrane/default_templates/NEURON_template.py +354 -0
- dendrotweaks/membrane/default_templates/default.py +73 -0
- dendrotweaks/membrane/default_templates/standard_channel.mod +87 -0
- dendrotweaks/membrane/default_templates/template_jaxley.py +108 -0
- dendrotweaks/membrane/default_templates/template_jaxley_new.py +108 -0
- dendrotweaks/membrane/distributions.py +324 -0
- dendrotweaks/membrane/groups.py +103 -0
- dendrotweaks/membrane/io/__init__.py +11 -0
- dendrotweaks/membrane/io/ast.py +201 -0
- dendrotweaks/membrane/io/code_generators.py +312 -0
- dendrotweaks/membrane/io/converter.py +108 -0
- dendrotweaks/membrane/io/factories.py +144 -0
- dendrotweaks/membrane/io/grammar.py +417 -0
- dendrotweaks/membrane/io/loader.py +90 -0
- dendrotweaks/membrane/io/parser.py +499 -0
- dendrotweaks/membrane/io/reader.py +212 -0
- dendrotweaks/membrane/mechanisms.py +574 -0
- dendrotweaks/model.py +1916 -0
- dendrotweaks/model_io.py +75 -0
- dendrotweaks/morphology/__init__.py +5 -0
- dendrotweaks/morphology/domains.py +100 -0
- dendrotweaks/morphology/io/__init__.py +5 -0
- dendrotweaks/morphology/io/factories.py +212 -0
- dendrotweaks/morphology/io/reader.py +66 -0
- dendrotweaks/morphology/io/validation.py +212 -0
- dendrotweaks/morphology/point_trees.py +681 -0
- dendrotweaks/morphology/reduce/__init__.py +16 -0
- dendrotweaks/morphology/reduce/reduce.py +155 -0
- dendrotweaks/morphology/reduce/reduced_cylinder.py +129 -0
- dendrotweaks/morphology/sec_trees.py +1112 -0
- dendrotweaks/morphology/seg_trees.py +157 -0
- dendrotweaks/morphology/trees.py +567 -0
- dendrotweaks/path_manager.py +261 -0
- dendrotweaks/simulators.py +235 -0
- dendrotweaks/stimuli/__init__.py +3 -0
- dendrotweaks/stimuli/iclamps.py +73 -0
- dendrotweaks/stimuli/populations.py +265 -0
- dendrotweaks/stimuli/synapses.py +203 -0
- dendrotweaks/utils.py +239 -0
- dendrotweaks-0.3.1.dist-info/METADATA +70 -0
- dendrotweaks-0.3.1.dist-info/RECORD +56 -0
- dendrotweaks-0.3.1.dist-info/WHEEL +5 -0
- dendrotweaks-0.3.1.dist-info/licenses/LICENSE +674 -0
- dendrotweaks-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
"""
|
2
|
+
This module contains functions for reducing dendritic subtrees into single cylinders.
|
3
|
+
The module incorporates code from neuron_reduce, which implements the algorithm described in:
|
4
|
+
Amsalem, O., Eyal, G., Rogozinski, N. et al. An efficient analytical reduction of detailed nonlinear neuron models. Nat Commun 11, 288 (2020). https://doi.org/10.1038/s41467-019-13932-6
|
5
|
+
The original code can be found at: https://github.com/orena1/neuron_reduce. Licensed under MIT License.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import neuron
|
9
|
+
from neuron import h
|
10
|
+
|
11
|
+
import math
|
12
|
+
import cmath
|
13
|
+
import collections
|
14
|
+
import numpy as np
|
15
|
+
|
16
|
+
from neuron_reduce.reducing_methods import push_section
|
17
|
+
from neuron_reduce.reducing_methods import measure_input_impedance_of_subtree
|
18
|
+
from neuron_reduce.reducing_methods import find_best_real_X
|
19
|
+
|
20
|
+
EXCLUDE_MECHANISMS = ['Leak', 'na_ion', 'k_ion', 'ca_ion', 'h_ion']
|
21
|
+
|
22
|
+
|
23
|
+
def map_segs_to_params(root, mechanisms):
|
24
|
+
segs_to_params = {}
|
25
|
+
for sec in root.subtree:
|
26
|
+
for seg in sec:
|
27
|
+
segs_to_params[seg] = {}
|
28
|
+
for mech_name, mech in mechanisms.items():
|
29
|
+
if mech_name in EXCLUDE_MECHANISMS:
|
30
|
+
continue
|
31
|
+
segs_to_params[seg][mech_name] = {}
|
32
|
+
for param_name in mech.range_params_with_suffix:
|
33
|
+
segs_to_params[seg][mech_name][param_name] = seg.get_param_value(param_name)
|
34
|
+
return segs_to_params
|
35
|
+
|
36
|
+
|
37
|
+
def map_segs_to_locs(root, reduction_frequency, new_cable_properties):
|
38
|
+
"""Maps segment names of the original subtree
|
39
|
+
to their new locations in the reduced cylinder.
|
40
|
+
|
41
|
+
This dictionary is used later to restore
|
42
|
+
the active conductances in the reduced cylinder.
|
43
|
+
"""
|
44
|
+
segs_to_locs = {}
|
45
|
+
|
46
|
+
imp_obj, subtree_input_impedance = measure_input_impedance_of_subtree(root._ref,
|
47
|
+
reduction_frequency)
|
48
|
+
subtree_q = calculate_subtree_q(root._ref, reduction_frequency)
|
49
|
+
|
50
|
+
for sec in root.subtree:
|
51
|
+
for seg in sec:
|
52
|
+
|
53
|
+
mid_of_segment_loc = reduce_segment(seg._ref,
|
54
|
+
imp_obj,
|
55
|
+
subtree_input_impedance,
|
56
|
+
new_cable_properties.electrotonic_length,
|
57
|
+
subtree_q)
|
58
|
+
|
59
|
+
segs_to_locs[seg] = mid_of_segment_loc
|
60
|
+
|
61
|
+
return segs_to_locs
|
62
|
+
|
63
|
+
|
64
|
+
def calculate_subtree_q(root, reduction_frequency):
|
65
|
+
rm = 1.0 / root.gbar_Leak
|
66
|
+
rc = rm * (float(root.cm) / 1000000)
|
67
|
+
angular_freq = 2 * math.pi * reduction_frequency
|
68
|
+
q_imaginary = angular_freq * rc
|
69
|
+
q_subtree = complex(1, q_imaginary) # q=1+iwRC
|
70
|
+
q_subtree = cmath.sqrt(q_subtree)
|
71
|
+
return q_subtree
|
72
|
+
|
73
|
+
|
74
|
+
def reduce_segment(seg,
|
75
|
+
imp_obj,
|
76
|
+
root_input_impedance,
|
77
|
+
new_cable_electrotonic_length,
|
78
|
+
subtree_q):
|
79
|
+
|
80
|
+
sec = seg.sec
|
81
|
+
|
82
|
+
with push_section(sec):
|
83
|
+
orig_transfer_imp = imp_obj.transfer(seg.x) * 1000000 # ohms
|
84
|
+
orig_transfer_phase = imp_obj.transfer_phase(seg.x)
|
85
|
+
# creates a complex Impedance value with the given polar coordinates
|
86
|
+
orig_transfer_impedance = cmath.rect(
|
87
|
+
orig_transfer_imp, orig_transfer_phase)
|
88
|
+
|
89
|
+
new_electrotonic_location = find_best_real_X(root_input_impedance,
|
90
|
+
orig_transfer_impedance,
|
91
|
+
subtree_q,
|
92
|
+
new_cable_electrotonic_length)
|
93
|
+
new_relative_loc_in_section = (float(new_electrotonic_location) /
|
94
|
+
new_cable_electrotonic_length)
|
95
|
+
|
96
|
+
if new_relative_loc_in_section > 1: # PATCH
|
97
|
+
new_relative_loc_in_section = 0.999999
|
98
|
+
|
99
|
+
return new_relative_loc_in_section
|
100
|
+
|
101
|
+
|
102
|
+
def map_segs_to_reduced_segs(seg_to_locs, root):
|
103
|
+
"""Replaces the locations (x values)
|
104
|
+
with the corresponding segments of the reduced cylinder i.e. sec(x).
|
105
|
+
"""
|
106
|
+
locs_to_reduced_segs = {loc: root(loc)
|
107
|
+
for loc in seg_to_locs.values()}
|
108
|
+
segs_to_reduced_segs = {seg: locs_to_reduced_segs[loc]
|
109
|
+
for seg, loc in seg_to_locs.items()}
|
110
|
+
return segs_to_reduced_segs
|
111
|
+
|
112
|
+
|
113
|
+
def map_reduced_segs_to_params(segs_to_reduced_segs, segs_to_params):
|
114
|
+
reduced_segs_to_params = {}
|
115
|
+
for seg, reduced_seg in segs_to_reduced_segs.items():
|
116
|
+
if reduced_seg not in reduced_segs_to_params:
|
117
|
+
reduced_segs_to_params[reduced_seg] = collections.defaultdict(list)
|
118
|
+
for mech_name, mech_params in segs_to_params[seg].items():
|
119
|
+
for param_name, param_value in mech_params.items():
|
120
|
+
reduced_segs_to_params[reduced_seg][param_name].append(param_value)
|
121
|
+
return reduced_segs_to_params
|
122
|
+
|
123
|
+
|
124
|
+
def set_avg_params_to_reduced_segs(reduced_segs_to_params):
|
125
|
+
for reduced_seg, params in reduced_segs_to_params.items():
|
126
|
+
for param_name, param_values in params.items():
|
127
|
+
value = np.mean(param_values)
|
128
|
+
reduced_seg.set_param_value(param_name, value)
|
129
|
+
|
130
|
+
|
131
|
+
def interpolate_missing_values(reduced_segs_to_params, root):
|
132
|
+
|
133
|
+
non_mapped_segs = [seg for seg in root.segments
|
134
|
+
if seg not in reduced_segs_to_params]
|
135
|
+
|
136
|
+
xs = np.array([seg.x for seg in root.segments])
|
137
|
+
|
138
|
+
non_mapped_indices = np.where([seg in non_mapped_segs for seg in root.segments])[0]
|
139
|
+
mapped_indices = np.where([seg not in non_mapped_segs for seg in root.segments])[0]
|
140
|
+
|
141
|
+
print(f'Interpolated for ids {non_mapped_indices}')
|
142
|
+
|
143
|
+
for param in list(set([k for val in reduced_segs_to_params.values() for k in val.keys()])):
|
144
|
+
values = np.array([seg.get_param_value(param) for seg in root.segments])
|
145
|
+
if np.any(values != 0.) and np.any(values == 0.):
|
146
|
+
# Find the indices where param value is zero
|
147
|
+
# zero_indices = np.where(values == 0)[0]
|
148
|
+
# Interpolate the values for these indices
|
149
|
+
# values[zero_indices] = np.interp(xs[zero_indices], xs[values != 0], values[values != 0], left=0, right=0)
|
150
|
+
values[non_mapped_indices] = np.interp(xs[non_mapped_indices], xs[mapped_indices], values[mapped_indices], left=0, right=0)
|
151
|
+
print(f' {param} values: {values}')
|
152
|
+
# Set the values
|
153
|
+
for x, value in zip(xs, values):
|
154
|
+
seg = root(x)
|
155
|
+
seg.set_param_value(param, value)
|
@@ -0,0 +1,129 @@
|
|
1
|
+
"""
|
2
|
+
This module contains functions for reducing dendritic subtrees into single cylinders.
|
3
|
+
The module incorporates code from neuron_reduce, which implements the algorithm described in:
|
4
|
+
Amsalem, O., Eyal, G., Rogozinski, N. et al. An efficient analytical reduction of detailed nonlinear neuron models. Nat Commun 11, 288 (2020). https://doi.org/10.1038/s41467-019-13932-6
|
5
|
+
The original code can be found at: https://github.com/orena1/neuron_reduce. Licensed under MIT License.
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
import math
|
10
|
+
import cmath
|
11
|
+
|
12
|
+
from neuron_reduce.subtree_reductor_func import calculate_nsegs_from_lambda, calculate_nsegs_from_manual_arg
|
13
|
+
|
14
|
+
def apply_params_to_section(section: "Section", cable_params: "CableParams", nseg: int):
|
15
|
+
'''Apply new cable parameters to the given section in the model'''
|
16
|
+
# Geometry
|
17
|
+
section._ref.L = cable_params.length
|
18
|
+
section._ref.diam = cable_params.diam
|
19
|
+
# Segmentation
|
20
|
+
section.nseg = nseg
|
21
|
+
# Passive properties
|
22
|
+
section._ref.cm = cable_params.cm
|
23
|
+
section._ref.Ra = cable_params.ra
|
24
|
+
section._ref.gbar_Leak = 1.0 / cable_params.rm
|
25
|
+
section._ref.e_Leak = cable_params.e_pas
|
26
|
+
remove_intermediate_points(section)
|
27
|
+
update_section_geometry(section)
|
28
|
+
|
29
|
+
|
30
|
+
def remove_intermediate_points(sec: "Section") -> None:
|
31
|
+
'''Removes all intermediate points in the section, keeping only start and end points'''
|
32
|
+
point_tree = sec._tree._point_tree
|
33
|
+
|
34
|
+
first_point, *intermediate_points, last_point = sec.points
|
35
|
+
|
36
|
+
for point in intermediate_points:
|
37
|
+
point_tree.remove_node(point)
|
38
|
+
|
39
|
+
point_tree.sort()
|
40
|
+
sec.points = [first_point, last_point]
|
41
|
+
|
42
|
+
|
43
|
+
def update_section_geometry(sec: "Section"):
|
44
|
+
'''Updates section geometry by adjusting the end point to maintain section length and direction'''
|
45
|
+
length = sec.L
|
46
|
+
|
47
|
+
if len(sec.points) != 2:
|
48
|
+
raise ValueError("Section must have only two points (the start and end points)")
|
49
|
+
|
50
|
+
first_point, last_point = sec.points
|
51
|
+
|
52
|
+
# Calculate the vector from first to last point
|
53
|
+
vector = (
|
54
|
+
last_point.x - first_point.x,
|
55
|
+
last_point.y - first_point.y,
|
56
|
+
last_point.z - first_point.z
|
57
|
+
)
|
58
|
+
|
59
|
+
# Calculate the current distance
|
60
|
+
distance = math.sqrt(sum(component**2 for component in vector))
|
61
|
+
|
62
|
+
# Scale the vector to match the desired length
|
63
|
+
scale = length / distance
|
64
|
+
|
65
|
+
# Update last point coordinates
|
66
|
+
last_point.x = first_point.x + vector[0] * scale
|
67
|
+
last_point.y = first_point.y + vector[1] * scale
|
68
|
+
last_point.z = first_point.z + vector[2] * scale
|
69
|
+
|
70
|
+
# Set radius for all points
|
71
|
+
radius = round(sec.diam / 2, 3)
|
72
|
+
for point in sec.points:
|
73
|
+
point.r = radius
|
74
|
+
|
75
|
+
|
76
|
+
def _get_subtree_biophysical_properties(subtree_root_ref, frequency):
|
77
|
+
''' gets the biophysical cable properties (Rm, Ra, Rc) and q
|
78
|
+
for the subtree to be reduced according to the properties of the root section of the subtree
|
79
|
+
'''
|
80
|
+
section = subtree_root_ref.sec
|
81
|
+
|
82
|
+
rm = 1.0 / section.gbar_Leak # in ohm * cm^2
|
83
|
+
# in secs, with conversion of the capacitance from uF/cm2 to F/cm2
|
84
|
+
RC = rm * (float(section.cm) / 1000000)
|
85
|
+
|
86
|
+
# defining q=sqrt(1+iwRC))
|
87
|
+
angular_freq = 2 * math.pi * frequency # = w
|
88
|
+
q_imaginary = angular_freq * RC
|
89
|
+
q = complex(1, q_imaginary) # q=1+iwRC
|
90
|
+
q = cmath.sqrt(q) # q = sqrt(1+iwRC)
|
91
|
+
|
92
|
+
return (section.cm,
|
93
|
+
rm,
|
94
|
+
section.Ra, # in ohm * cm
|
95
|
+
section.e_Leak,
|
96
|
+
q)
|
97
|
+
|
98
|
+
|
99
|
+
def calculate_nsegs(new_cable_properties, total_segments_manual):
|
100
|
+
|
101
|
+
new_cable_properties = [new_cable_properties]
|
102
|
+
|
103
|
+
if total_segments_manual > 1:
|
104
|
+
print('the number of segments in the reduced model will be set to `total_segments_manual`')
|
105
|
+
new_cables_nsegs = calculate_nsegs_from_manual_arg(new_cable_properties,
|
106
|
+
total_segments_manual)
|
107
|
+
else:
|
108
|
+
new_cables_nsegs = calculate_nsegs_from_lambda(new_cable_properties)
|
109
|
+
if total_segments_manual > 0:
|
110
|
+
print('from lambda')
|
111
|
+
original_cell_seg_n = (sum(i.nseg for i in list(original_cell.basal)) +
|
112
|
+
sum(i.nseg for i in list(
|
113
|
+
original_cell.apical))
|
114
|
+
)
|
115
|
+
min_reduced_seg_n = int(
|
116
|
+
round((total_segments_manual * original_cell_seg_n)))
|
117
|
+
if sum(new_cables_nsegs) < min_reduced_seg_n:
|
118
|
+
logger.debug(f"number of segments calculated using lambda is {sum(new_cables_nsegs)}, "
|
119
|
+
"the original cell had {original_cell_seg_n} segments. "
|
120
|
+
"The min reduced segments is set to {total_segments_manual * 100}% of reduced cell segments")
|
121
|
+
logger.debug("the reduced cell nseg is set to %s" %
|
122
|
+
min_reduced_seg_n)
|
123
|
+
new_cables_nsegs = calculate_nsegs_from_manual_arg(new_cable_properties,
|
124
|
+
min_reduced_seg_n)
|
125
|
+
else:
|
126
|
+
# print('Automatic segmentation')
|
127
|
+
pass
|
128
|
+
|
129
|
+
return new_cables_nsegs[0]
|