From 960f49006dd308aea9d2c2478c7b44987825f54e Mon Sep 17 00:00:00 2001 From: urdapile <68215700+urdapile@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:04:26 -0300 Subject: [PATCH] Extracellular stimulation via .mod Added the extracellular stimulation based on xtra.mod, useful for large networks. The xtra.mod should be added in some repository --- CHANGES.md | 2 + doc/source/user_documentation.rst | 32 +++++---- netpyne/cell/compartCell.py | 72 +++++++++++++------ netpyne/network/stim.py | 115 +++++++++++++++++++++++------- 4 files changed, 159 insertions(+), 62 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f078f3dd9..85cc57570 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,5 @@ +- Added extracellular stimulation with a supporting xtra.mod (which includes a global variable defining the temporal modulation and a pointer to link the extracellular mechanism in NEURON). Useful for large networks + - Solved small bug when plotting colorbar in the raster plot colored by the LFP phase - Solved conflict between diversity and segment coordinates (per population), used in LFP setups diff --git a/doc/source/user_documentation.rst b/doc/source/user_documentation.rst index 68c39ff78..47cbcfc97 100644 --- a/doc/source/user_documentation.rst +++ b/doc/source/user_documentation.rst @@ -835,13 +835,24 @@ The code below shows an example of how to create different types of stimulation For ``'class': 'uniform'``, it should be specified ``referencePoint`` (optional - the point where the field is 0) and ``fieldDirection`` (the direction of the field as a list of two angles: the polar angle -between 0 and 180- and the azimuthal angle -between 0 and 360-). - * ``amp`` (optional): the amplitude of the external stimulation (mA for point source stimulation - V/m for uniform electrical field). If not specified, it is set to 0 (no stimulation). + * ``waveform``: a dictionary that specifies the temporal modulation. It is composed by: - * ``del`` (optional): starting time of the stimulation. If not specified, it is the simulation start time. + * ``type``: Temporal shape of the external stimulation. Available options: ``sinusoidal``, ``pulse``, ``external``. If not specified, the external stimulation is set to 0 (ground). - * ``dur`` (optional): duration (following starting time) of the stimulation. If not specified, it is the simulation end time. + * ``amp`` (optional): the amplitude of the external stimulation (mA for point source stimulation - V/m for uniform electrical field), valid for ``sinusoidal`` or ``pulse``. If not specified, it is set to 0 (no stimulation). + + * ``del`` (optional): starting time of the stimulation, valid for ``sinusoidal`` or ``pulse``. If not specified, it is the simulation start time. + + * ``dur`` (optional): duration (following starting time) of the stimulation, valid for ``sinusoidal`` or ``pulse``. If not specified, it is the simulation end time. + + * ``freq`` (optional): frequency of the ``sinusoidal`` stimulation. If not specified, it is set to 0 (no stimulation). + + * ``time`` (optional): when the stimulation is uploaded externally (``type``: ``external``), then this entry specifies either a pickle file with a list of times, or a numpy array with the same information. The (fixed) time interval between consecutive time points should be consistent to the integration timestep. + + * ``signal`` (optional): when the stimulation is uploaded externally (``type``: ``external``), then this entry specifies either a pickle file with a list of values corresponding to the amplitudes (mA for point source stimulation - V/m for uniform electrical field) of the external signal, paired to the ``time`` list, or a numpy array with the same information. + + * ``mod_based``: Boolean (default: False). It specifies if the simulation is based an external mod file (xtra.mod with a POINTER/RANGE called "ex" and a GLOBAL called "is"), following the guidelines in the NEURON forum. It is appropriate for large networks, althought is restricted to a single temporal modulation. With ``mod_based``: False, you can set superposition of many sources. - * ``waveform`` (optional): Temporal shape of the external stimulation. Available options: ``sinusoidal`` and ``pulse``. If not specified, the external stimulation is set to 0 (ground). If ``'waveform': 'sinusoidal'``, then the frequency should be specified via ``freq``. Otherwise it is set to 0 (no stimulation). In addition, in ``stimSourceParams`` we should set the ``source`` (the label of the stimulation source specifying the extracellular stimulation) and ``conds`` with the conditions that should satisfy target cells, typically ``'conds': {'cellList': 'all'}`` would provide the global stimulation represented by an extracellular (ubiquitous) source. @@ -856,19 +867,14 @@ The code below shows a detailed example of superimposed external stimulations (w netParams.stimSourceParams['XStim1'] = { 'type': 'XStim', 'field': {'class': 'pointSource', 'location': [100,-100,0], 'sigma': 0.276}, - 'amp' : 0.020, - 'del' : 20, - 'dur' : 80, - 'waveform': 'sinusoidal', - 'freq': 250} + 'waveform': {'type': 'sinusoidal', 'amp' : 0.020, 'del' : 20, 'dur' : 80, 'freq': 250} + } netParams.stimSourceParams['XStim2'] = { 'type': 'XStim', 'field': {'class': 'uniform', 'referencePoint': [0,-netParams.sizeY,0], 'fieldDirection': [180,0]}, - 'amp' : 100.0, - 'del' : 50, - 'dur' : 20, - 'waveform': 'pulse'} + 'waveform': {'type': 'pulse', 'amp' : 10.0, 'del' : 50, 'dur' : 20} + } ## Stimulation target parameters netParams.stimTargetParams['XStim1->all'] = { diff --git a/netpyne/cell/compartCell.py b/netpyne/cell/compartCell.py index c23cb74d1..e0835c880 100644 --- a/netpyne/cell/compartCell.py +++ b/netpyne/cell/compartCell.py @@ -1392,36 +1392,63 @@ def addStim(self, params): if not h.ismembrane('extracellular',sec = hSec): hSec.insert('extracellular') - # saving in case we have multiple stimulations - self.secs[secLabel]['geom']['xtra_stim'] = [] + # if the extracellular mechanism is associated to the xtra.mod, then + if 'mod_based' in params and params['mod_based']==True: + hSec.insert('xtra') + else: + # not using .mod + # saving in case we have multiple stimulations (only defined with params['mod_based'] == False) + self.secs[secLabel]['geom']['xtra_stim'] = [] + + # for development (only saves properly for single stimulation) + #self.secs[secLabel]['geom'].update({'r05':[]}) + #self.secs[secLabel]['geom'].update({'rel_05':[]}) + #self.secs[secLabel]['geom'].update({'tr':[]}) + + #h.psection(hSec) - # for development (only saves properly for single stimulation) - #self.secs[secLabel]['geom'].update({'r05':[]}) - #self.secs[secLabel]['geom'].update({'rel_05':[]}) - #self.secs[secLabel]['geom'].update({'tr':[]}) for ns, seg in enumerate(hSec): - #self.secs[secLabel]['geom']['r05'].append(r05[:,nseg]) - #self.secs[secLabel]['geom']['rel_05'].append(rel_05[:,nseg]) - #self.secs[secLabel]['geom']['tr'].append(tr[nseg]) + # if the extracellular mechanism is associated to the xtra.mod, then + if 'mod_based' in params and params['mod_based']==True: + # using xtra.mod - link extracellular and xtra + sourceVar = seg.xtra._ref_ex + targetVar = seg._ref_e_extracellular + sim.pc.source_var(sourceVar, sim.net.lastPointerId) + sim.pc.target_var(targetVar, sim.net.lastPointerId) + sim.net.lastPointerId += 1 - vec = [val*tr[nseg]*(1e6) for val in params['stim']] - hStim = h.Vector(vec) # add stim object to dict in stims list - - self.stims.append(Dict(params)) # add to python structure - self.stims[-1]['sec_xtra'] = secLabel - self.stims[-1]['seg_xtra'] = ns - self.stims[-1]['hObj'] = hStim + # # setting coordinates in xtra (not neccesary) + # seg.xtra.x = r05[0,nseg] + # seg.xtra.y = r05[1,nseg] + # seg.xtra.z = r05[2,nseg] + + # # setting transfer resistance in xtra + seg.xtra.rx = tr[nseg] - self.secs[secLabel]['geom']['xtra_stim'].append({}) - self.secs[secLabel]['geom']['xtra_stim'][ns].update({'vec' : vec, - 'stim_index': len(self.stims)-1}) + else: + # not using .mod + #self.secs[secLabel]['geom']['r05'].append(r05[:,nseg]) + #self.secs[secLabel]['geom']['rel_05'].append(rel_05[:,nseg]) + #self.secs[secLabel]['geom']['tr'].append(tr[nseg]) - self.hvec.append(hStim) - self.hvec[nseg].play(seg._ref_e_extracellular, params['time'],1) + vec = [val*tr[nseg]*(1e6) for val in params['stim']] + hStim = h.Vector(vec) # add stim object to dict in stims list + + self.stims.append(Dict(params)) # add to python structure + self.stims[-1]['sec_xtra'] = secLabel + self.stims[-1]['seg_xtra'] = ns + self.stims[-1]['hObj'] = hStim + + self.secs[secLabel]['geom']['xtra_stim'].append({}) + self.secs[secLabel]['geom']['xtra_stim'][ns].update({'vec' : vec, + 'stim_index': len(self.stims)-1}) + + self.hvec.append(hStim) + self.hvec[nseg].play(seg._ref_e_extracellular, params['time'],1) nseg += 1 - + else: ## Extracellular mechanism already inserted: Putatively multiple stimulation #print("Multiple stimulation") for ns, seg in enumerate(hSec): @@ -1445,7 +1472,6 @@ def addStim(self, params): nseg += 1 - h.pop_section() #print(nseg) diff --git a/netpyne/network/stim.py b/netpyne/network/stim.py index e18870ec7..2bcb55699 100644 --- a/netpyne/network/stim.py +++ b/netpyne/network/stim.py @@ -119,46 +119,107 @@ def addStims(self): # Creating the temporal pattern for external stimulation (common to all cells) times = np.arange(0,sim.cfg.duration,sim.cfg.dt) - if 'del' in source: - t_start = source['del'] - else: - t_start = 0 - - if 'del' in source and 'dur' in source: - t_end = source['del'] + source['dur'] - else: - t_end = sim.cfg.duration + if 'waveform' in source: + if source['waveform']['type']=='pulse' or source['waveform']['type']=='sinusoidal': - if t_start > sim.cfg.duration: - print(" Extracellular stimulation defined beyond simulation time") - t_start = 0 + if 'amp' in source['waveform']: + amp = source['waveform']['amp'] + else: + amp = 0 - if t_end > sim.cfg.duration: - print(" Extracellular stimulation defined beyond simulation time") - t_end = sim.cfg.duration + if 'del' in source['waveform']: + t_start = source['waveform']['del'] + else: + t_start = 0 + + if 'del' in source['waveform'] and 'dur' in source['waveform']: + t_end = source['waveform']['del'] + source['waveform']['dur'] + else: + t_end = sim.cfg.duration - if 'amp' in source: - amp = source['amp'] - else: - amp = 0 + if t_start > sim.cfg.duration: + print(" Extracellular stimulation defined beyond simulation time") + t_start = 0 - if 'waveform' in source: - if source['waveform']=='sinusoidal': - if 'freq' in source: - freq = source['freq'] + if t_end > sim.cfg.duration: + print(" Extracellular stimulation defined beyond simulation time") + t_end = sim.cfg.duration + + if source['waveform']['type']=='sinusoidal': + if 'freq' in source['waveform']: + freq = source['waveform']['freq'] else: print(" Extracellular stimulation (Sinusoidal). No frequency given") freq = 0 # no stimulation signal = np.array([amp*np.sin(2*np.pi*freq*t/1000) if t>t_start and tt_start and t len(times): + times = np.array(times_ext[0:len(times)]) # rewrite times np array + signal = np.array(signal_ext[0:len(times)]) + elif len(times_ext) < len(times): + signal = [0]*len(times) + for nn in range(len(signal_ext)): + signal[nn] = signal_ext[nn] + signal = np.array(signal) + else: + times = np.array(times_ext) # rewrite times np array + signal = np.array(signal_ext) + else: signal = np.array([0 if t>t_start and t