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,261 @@
1
+ import os
2
+ from typing import List, Dict
3
+ import shutil
4
+
5
+ class PathManager:
6
+ """
7
+ A manager class for handling file and directory paths related to models data.
8
+
9
+ Parameters
10
+ ----------
11
+ path_to_model : str
12
+ The path to the model directory.
13
+
14
+ Attributes
15
+ ----------
16
+ path_to_model : str
17
+ The path to the model directory.
18
+ paths : Dict[str, str]
19
+ A dictionary of paths for different file types.
20
+ """
21
+ def __init__(self, path_to_model: str):
22
+ if not os.path.isdir(path_to_model):
23
+ raise FileNotFoundError(f"Directory {path_to_model} does not exist.")
24
+ self.path_to_model = path_to_model
25
+ self.paths = {
26
+ 'default_mod': os.path.join(self.path_to_data, 'Default'),
27
+ 'templates': os.path.join(self.path_to_data, 'Templates'),
28
+ 'morphology': os.path.join(self.path_to_model, 'morphology'),
29
+ 'membrane': os.path.join(self.path_to_model, 'membrane'),
30
+ 'mod': os.path.join(self.path_to_model, 'membrane', 'mod'),
31
+ 'python': os.path.join(self.path_to_model, 'membrane', 'python'),
32
+ 'stimuli': os.path.join(self.path_to_model, 'stimuli'),
33
+ }
34
+ self._ensure_paths_exist()
35
+
36
+
37
+ def _ensure_paths_exist(self):
38
+ """
39
+ Ensure all necessary paths exist.
40
+ """
41
+ os.makedirs(self.path_to_model, exist_ok=True)
42
+ for path in self.paths.values():
43
+ os.makedirs(path, exist_ok=True)
44
+ # if empty, copy default mod files
45
+ if not os.listdir(self.paths['default_mod']):
46
+ self.copy_default_mod_files()
47
+ if not os.listdir(self.paths['templates']):
48
+ self.copy_template_files()
49
+
50
+ @property
51
+ def path_to_data(self):
52
+ """
53
+ The path to the data directory.
54
+ """
55
+ return os.path.dirname(self.path_to_model)
56
+
57
+ def __repr__(self):
58
+ return f"PathManager({self.path_to_model})"
59
+
60
+ def copy_default_mod_files(self):
61
+ """
62
+ Copy default mod files to the data directory.
63
+ """
64
+ # __file__ + 'membrane' + 'default_mod'
65
+ DEFAULT_MOD_DIR = os.path.join(os.path.dirname(__file__), 'membrane', 'default_mod')
66
+ for file_name in os.listdir(DEFAULT_MOD_DIR):
67
+ source = os.path.join(DEFAULT_MOD_DIR, file_name)
68
+ destination = os.path.join(self.paths['default_mod'], file_name)
69
+ shutil.copyfile(source, destination)
70
+
71
+ def copy_template_files(self):
72
+ """
73
+ Copy template files to the data directory.
74
+ """
75
+ # __file__ + 'membrane' + 'templates'
76
+ TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'membrane', 'default_templates')
77
+ for file_name in os.listdir(TEMPLATES_DIR):
78
+ source = os.path.join(TEMPLATES_DIR, file_name)
79
+ destination = os.path.join(self.paths['templates'], file_name)
80
+ shutil.copyfile(source, destination)
81
+
82
+
83
+ def get_path(self, file_type: str) -> str:
84
+ """
85
+ Get the path for a specific file type.
86
+
87
+ Parameters
88
+ ----------
89
+ file_type : str
90
+ The type of file (e.g., 'mod', 'swc').
91
+
92
+ Returns
93
+ -------
94
+ str
95
+ The full directory path.
96
+ """
97
+ path = self.paths.get(file_type, None)
98
+ if os.path.isdir(path):
99
+ return path
100
+ raise FileNotFoundError(f"Directory for {file_type} does not exist.")
101
+
102
+ def get_file_path(self, file_type: str, file_name: str, extension: str) -> str:
103
+ """
104
+ Construct a file path with an optional extension for a specific type.
105
+
106
+ Parameters
107
+ ----------
108
+ file_type : str
109
+ The type of file (e.g., 'morphology', 'stimuli').
110
+ file_name : str
111
+ The name of the file.
112
+ extension : str
113
+ The file extension (e.g., 'mod', 'swc').
114
+
115
+ Returns
116
+ -------
117
+ str
118
+ The full file path.
119
+ """
120
+ dir_path = self.get_path(file_type)
121
+ file_name = f"{file_name}.{extension}"
122
+ return os.path.join(dir_path, file_name)
123
+
124
+ def list_files(self, file_type: str, extension: str = "") -> List[str]:
125
+ """
126
+ List all files of a given type and optional archive.
127
+
128
+ Parameters
129
+ ----------
130
+ file_type : str
131
+ The type of file (e.g. 'morphology', 'stimuli').
132
+ extension : str
133
+ The file extension to filter by (e.g., 'mod', 'swc').
134
+
135
+ Returns
136
+ -------
137
+ List[str]
138
+ A list of file names.
139
+ """
140
+ directory = self.paths.get(file_type, "")
141
+ if not extension.startswith('.'): extension = f".{extension}"
142
+ if not os.path.isdir(directory):
143
+ return []
144
+ return [f.replace(extension, '')
145
+ for f in os.listdir(directory) if f.endswith(extension)]
146
+
147
+
148
+ def list_morphologies(self, extension: str = '.swc') -> List[str]:
149
+ """
150
+ List all SWC files.
151
+
152
+ Returns
153
+ -------
154
+ List[str]
155
+ A list of SWC file names.
156
+ """
157
+ return self.list_files('morphology', extension=extension)
158
+
159
+
160
+ def list_stimuli(self, extension: str = '.json') -> List[str]:
161
+ """
162
+ List all JSON files.
163
+
164
+ Returns
165
+ -------
166
+ List[str]
167
+ A list of JSON file names.
168
+ """
169
+ return self.list_files('stimuli', extension=extension)
170
+
171
+
172
+ def list_membrane(self):
173
+ """
174
+ List all membrane files.
175
+
176
+ Returns
177
+ -------
178
+ List[str]
179
+ A list of membrane file
180
+ """
181
+ return self.list_files('membrane', extension='.json')
182
+
183
+
184
+ def print_directory_tree(self, subfolder=None) -> None:
185
+ """
186
+ Print a directory tree for a given file type.
187
+
188
+ Parameters
189
+ ----------
190
+ file_type : str
191
+ The type of file (e.g., 'mod', 'swc').
192
+ """
193
+ base_path = self.paths.get('model') if not subfolder else self.paths.get(subfolder)
194
+ if not base_path or not os.path.isdir(base_path):
195
+ print(f"Directory for {file_type} does not exist.")
196
+ return
197
+
198
+ def print_tree(path, prefix=""):
199
+ items = os.listdir(path)
200
+ for idx, item in enumerate(sorted(items)):
201
+ is_last = idx == len(items) - 1
202
+ connector = "└──" if is_last else "├──"
203
+ item_path = os.path.join(path, item)
204
+ print(f"{prefix}{connector} {item}")
205
+ if os.path.isdir(item_path) and not item.startswith('x86_64'):
206
+ extension = "│ " if not is_last else " "
207
+ print_tree(item_path, prefix + extension)
208
+
209
+ print_tree(base_path)
210
+
211
+ def get_channel_paths(self, mechanism_name: str,
212
+ python_template_name: str = None) -> Dict[str, str]:
213
+ """
214
+ Get all necessary paths for creating a channel.
215
+
216
+ Parameters
217
+ ----------
218
+ mechanism_name : str
219
+ The name of the mechanism.
220
+ python_template_name : str, optional
221
+ The name of the Python template file.
222
+
223
+ Returns
224
+ -------
225
+ Dict[str, str]
226
+ A dictionary of paths.
227
+ """
228
+ python_template_name = python_template_name or "default"
229
+ return {
230
+ 'path_to_mod_file': self.get_file_path('mod', mechanism_name, 'mod'),
231
+ 'path_to_python_file': self.get_file_path('python', mechanism_name, 'py'),
232
+ 'path_to_python_template': self.get_file_path('templates', python_template_name, 'py'),
233
+ }
234
+
235
+ def get_standard_channel_paths(self, mechanism_name: str,
236
+ python_template_name: str = None,
237
+ mod_template_name: str = None) -> Dict[str, str]:
238
+ """
239
+ Get all necessary paths for creating a standard channel.
240
+
241
+ Parameters
242
+ ----------
243
+ mechanism_name : str
244
+ The name of the mechanism.
245
+ python_template_name : str, optional
246
+ The name of the Python template file.
247
+ mod_template_name : str, optional
248
+ The name of the MOD template file.
249
+
250
+ Returns
251
+ -------
252
+ Dict[str, str]
253
+ A dictionary of paths.
254
+ """
255
+ python_template_name = python_template_name or "default"
256
+ mod_template_name = mod_template_name or "standard_channel"
257
+ return {
258
+ # **self.get_channel_paths(mechanism_name, python_template_name),
259
+ 'path_to_mod_template': self.get_file_path('templates', mod_template_name, 'mod'),
260
+ 'path_to_standard_mod_file': self.get_file_path('mod', f"std{mechanism_name}", 'mod'),
261
+ }
@@ -0,0 +1,235 @@
1
+ from collections import defaultdict
2
+ import warnings
3
+
4
+ import matplotlib.pyplot as plt
5
+ import neuron
6
+ from neuron import h
7
+ from neuron.units import ms, mV
8
+ h.load_file('stdrun.hoc')
9
+ # h.load_file('import3d.hoc')
10
+ # h.load_file('nrngui.hoc')
11
+ # h.load_file('import3d')
12
+
13
+ import contextlib
14
+
15
+ @contextlib.contextmanager
16
+ def push_section(section):
17
+ section.push()
18
+ yield
19
+ h.pop_section()
20
+
21
+ def reset_neuron():
22
+
23
+ # h('forall delete_section()')
24
+ # h('forall delete_all()')
25
+ # h('forall delete()')
26
+
27
+ for sec in h.allsec():
28
+ with push_section(sec):
29
+ h.delete_section()
30
+
31
+ reset_neuron()
32
+
33
+ class Simulator:
34
+ """
35
+ A generic simulator class.
36
+ """
37
+ def __init__(self):
38
+ self.vs = None
39
+ self.t = None
40
+ self.dt = None
41
+ self.recordings = {}
42
+
43
+ def plot_voltage(self, ax=None, segments=None, **kwargs):
44
+ if ax is None:
45
+ fig, ax = plt.subplots()
46
+ if segments is None:
47
+ segments = self.recordings.keys()
48
+ for seg, v in self.vs.items():
49
+ if segments and seg not in segments:
50
+ continue
51
+ ax.plot(self.t, v, label=f'{seg.domain} {seg.idx}', **kwargs)
52
+
53
+ if len(segments) < 10:
54
+ ax.legend()
55
+ # ax.set_ylim(-100, 60)
56
+ ax.set_xlabel('Time (ms)')
57
+ ax.set_ylabel('Voltage (mV)')
58
+
59
+
60
+ class NEURONSimulator(Simulator):
61
+ """
62
+ A class to represent the NEURON simulator.
63
+
64
+ Parameters
65
+ ----------
66
+ temperature : float
67
+ The temperature of the simulation in Celsius.
68
+ v_init : float
69
+ The initial membrane potential of the neuron in mV.
70
+ dt : float
71
+ The time step of the simulation in ms.
72
+ cvode : bool
73
+ Whether to use the CVode variable time step integrator.
74
+
75
+ Attributes
76
+ ----------
77
+ temperature : float
78
+ The temperature of the simulation in Celsius.
79
+ v_init : float
80
+ The initial membrane potential of the neuron in mV.
81
+ dt : float
82
+ The time step of the simulation in ms.
83
+ """
84
+
85
+ def __init__(self, temperature=37, v_init=-70, dt=0.025, cvode=False):
86
+ super().__init__()
87
+
88
+ self.temperature = temperature
89
+ self.v_init = v_init * mV
90
+ self._duration = 300
91
+
92
+ self.dt = dt
93
+ self._cvode = cvode
94
+
95
+
96
+ def add_recording(self, sec, loc, var='v'):
97
+ """
98
+ Add a recording to the simulator.
99
+
100
+ Parameters
101
+ ----------
102
+ sec : Section
103
+ The section to record from.
104
+ loc : float
105
+ The location along the normalized section length to record from.
106
+ var : str
107
+ The variable to record. Default is 'v' (voltage).
108
+ """
109
+ seg = sec(loc)
110
+ if self.recordings.get(seg):
111
+ self.remove_recording(sec, loc)
112
+ self.recordings[seg] = h.Vector().record(getattr(seg._ref, f'_ref_{var}'))
113
+
114
+ def remove_recording(self, sec, loc):
115
+ """
116
+ Remove a recording from the simulator.
117
+
118
+ Parameters
119
+ ----------
120
+ sec : Section
121
+ The section to remove the recording from.
122
+ loc : float
123
+ The location along the normalized section length to remove the recording from.
124
+ """
125
+ seg = sec(loc)
126
+ if self.recordings.get(seg):
127
+ self.recordings[seg] = None
128
+ self.recordings.pop(seg)
129
+
130
+ def remove_all_recordings(self):
131
+ """
132
+ Remove all recordings from the simulator.
133
+ """
134
+ for seg in list(self.recordings.keys()):
135
+ sec, loc = seg._section, seg.x
136
+ self.remove_recording(sec, loc)
137
+ if self.recordings:
138
+ warnings.warn(f'Not all recordings were removed: {self.recordings}')
139
+ self.recordings = {}
140
+
141
+
142
+ def _init_simulation(self):
143
+ h.CVode().active(self._cvode)
144
+ h.celsius = self.temperature
145
+ h.dt = self.dt
146
+ h.stdinit()
147
+ h.init()
148
+ h.finitialize(self.v_init)
149
+ if h.cvode.active():
150
+ h.cvode.re_init()
151
+ else:
152
+ h.fcurrent()
153
+ h.frecord_init()
154
+
155
+ def run(self, duration=300):
156
+ """
157
+ Run a simulation.
158
+
159
+ Parameters
160
+ ----------
161
+ duration : float
162
+ The duration of the simulation in milliseconds.
163
+ """
164
+ self._duration = duration
165
+
166
+
167
+ # vs = list(self.recordings.values())
168
+ Is = []
169
+
170
+ # for v in self.recordings.values():
171
+ # # v = h.Vector().record(seg._ref_v)
172
+ # vs.append(v)
173
+
174
+ t = h.Vector().record(h._ref_t)
175
+
176
+ # if self.ch is None:
177
+ # pass
178
+ # else:
179
+ # for seg in self.recordings.keys():
180
+ # if getattr(seg, f'_ref_i_{self.ch.suffix}', None) is None:
181
+ # logger.warning(
182
+ # f'No current recorded for {self.ch.suffix} at {seg}. Make i a RANGE variable in mod file.')
183
+ # continue
184
+ # I = h.Vector().record(getattr(seg, f'_ref_i_{self.ch.suffix}'))
185
+ # Is.append(I)
186
+
187
+ self._init_simulation()
188
+
189
+ h.continuerun(duration * ms)
190
+
191
+ self.t = t.to_python()
192
+ self.vs = {seg: v.to_python() for seg, v in self.recordings.items()}
193
+
194
+
195
+ def to_dict(self):
196
+ """
197
+ Convert the simulator to a dictionary.
198
+
199
+ Returns
200
+ -------
201
+ dict
202
+ A dictionary representation of the simulator.
203
+ """
204
+ return {
205
+ 'temperature': self.temperature,
206
+ 'v_init': self.v_init,
207
+ 'dt': self.dt,
208
+ 'duration': self._duration
209
+ }
210
+
211
+ def from_dict(self, data):
212
+ """
213
+ Create a simulator from a dictionary.
214
+
215
+ Parameters
216
+ ----------
217
+ data : dict
218
+ The dictionary representation of the simulator.
219
+ """
220
+ self.temperature = data['temperature']
221
+ self.v_init = data['v_init']
222
+ self.dt = data['dt']
223
+ self._duration = data['duration']
224
+
225
+
226
+ class JaxleySimulator(Simulator):
227
+ """
228
+ A class to represent a Jaxley simulator.
229
+ """
230
+
231
+ def __init__(self):
232
+ super().__init__()
233
+ ...
234
+
235
+
@@ -0,0 +1,3 @@
1
+ from dendrotweaks.stimuli.iclamps import IClamp
2
+ from dendrotweaks.stimuli.synapses import Synapse
3
+ from dendrotweaks.stimuli.populations import Population
@@ -0,0 +1,73 @@
1
+ from neuron import h
2
+ h.load_file('stdrun.hoc')
3
+
4
+ class IClamp():
5
+ """
6
+ A current clamp stimulus.
7
+
8
+ Parameters
9
+ ----------
10
+ sec : Section
11
+ The section to place the stimulus on.
12
+ loc : float
13
+ The location along the section to place the stimulus.
14
+ Can be a float between 0 and 1.
15
+ amp : float
16
+ The amplitude of the stimulus, in nA.
17
+ delay : int
18
+ The delay of the stimulus, in ms.
19
+ dur : int
20
+ The duration of the stimulus, in ms.
21
+
22
+ Attributes
23
+ ----------
24
+ sec : Section
25
+ The section to place the stimulus on.
26
+ loc : float
27
+ The location along the section to place the stimulus.
28
+ """
29
+
30
+ def __init__(self, sec, loc, amp=0, delay=100, dur=100):
31
+ self.sec = sec
32
+ self.loc = loc
33
+ self._iclamp = h.IClamp(sec(loc)._ref)
34
+ self._iclamp.amp = amp
35
+ self._iclamp.delay = delay
36
+ self._iclamp.dur = dur
37
+
38
+ def __repr__(self):
39
+ return f"<IClamp(sec[{self.sec.idx}]({self.loc:.2f}))>"
40
+
41
+ @property
42
+ def amp(self):
43
+ """
44
+ The amplitude of the stimulus, in nA.
45
+ """
46
+ return self._iclamp.amp
47
+
48
+ @amp.setter
49
+ def amp(self, new_amp):
50
+ self._iclamp.amp = new_amp
51
+
52
+
53
+ @property
54
+ def delay(self):
55
+ """
56
+ The delay of the stimulus, in ms.
57
+ """
58
+ return self._iclamp.delay
59
+
60
+ @delay.setter
61
+ def delay(self, new_delay):
62
+ self._iclamp.delay = new_delay
63
+
64
+ @property
65
+ def dur(self):
66
+ """
67
+ The duration of the stimulus, in ms.
68
+ """
69
+ return self._iclamp.dur
70
+
71
+ @dur.setter
72
+ def dur(self, new_dur):
73
+ self._iclamp.dur = new_dur